1239 lines
33 KiB
C
Executable File
1239 lines
33 KiB
C
Executable File
// SPDX-License-Identifier: GPL-2.0
|
|
//
|
|
// cs40l26.c -- ALSA SoC Audio driver for Cirrus Logic Haptic Device: CS40L26
|
|
//
|
|
// Copyright 2022 Cirrus Logic. Inc.
|
|
|
|
#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE
|
|
#include <linux/vibrator/cs40l26.h>
|
|
#else
|
|
#include <linux/mfd/cs40l26.h>
|
|
#endif
|
|
|
|
static const struct cs40l26_pll_sysclk_config cs40l26_pll_sysclk[] = {
|
|
{CS40L26_PLL_CLK_FRQ_32768, CS40L26_PLL_CLK_CFG_32768},
|
|
{CS40L26_PLL_CLK_FRQ_1536000, CS40L26_PLL_CLK_CFG_1536000},
|
|
{CS40L26_PLL_CLK_FRQ_3072000, CS40L26_PLL_CLK_CFG_3072000},
|
|
{CS40L26_PLL_CLK_FRQ_6144000, CS40L26_PLL_CLK_CFG_6144000},
|
|
{CS40L26_PLL_CLK_FRQ_9600000, CS40L26_PLL_CLK_CFG_9600000},
|
|
{CS40L26_PLL_CLK_FRQ_12288000, CS40L26_PLL_CLK_CFG_12288000},
|
|
};
|
|
|
|
static int cs40l26_get_clk_config(u32 freq, u8 *clk_cfg)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(cs40l26_pll_sysclk); i++) {
|
|
if (cs40l26_pll_sysclk[i].freq == freq) {
|
|
*clk_cfg = cs40l26_pll_sysclk[i].clk_cfg;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int cs40l26_swap_ext_clk(struct cs40l26_codec *codec, u8 clk_src)
|
|
{
|
|
struct regmap *regmap = codec->regmap;
|
|
struct device *dev = codec->dev;
|
|
u8 clk_cfg, clk_sel;
|
|
int error;
|
|
|
|
switch (clk_src) {
|
|
case CS40L26_PLL_REFCLK_BCLK:
|
|
clk_sel = CS40L26_PLL_CLK_SEL_BCLK;
|
|
error = cs40l26_get_clk_config(codec->sysclk_rate, &clk_cfg);
|
|
break;
|
|
case CS40L26_PLL_REFCLK_MCLK:
|
|
clk_sel = CS40L26_PLL_CLK_SEL_MCLK;
|
|
error = cs40l26_get_clk_config(CS40L26_PLL_CLK_FRQ_32768, &clk_cfg);
|
|
break;
|
|
case CS40L26_PLL_REFCLK_FSYNC:
|
|
error = -EPERM;
|
|
break;
|
|
default:
|
|
error = -EINVAL;
|
|
}
|
|
|
|
if (error) {
|
|
dev_err(dev, "Failed to get clock configuration\n");
|
|
return error;
|
|
}
|
|
|
|
error = cs40l26_set_pll_loop(codec->core, CS40L26_PLL_REFCLK_SET_OPEN_LOOP);
|
|
if (error)
|
|
return error;
|
|
|
|
error = regmap_update_bits(regmap, CS40L26_REFCLK_INPUT, CS40L26_PLL_REFCLK_FREQ_MASK |
|
|
CS40L26_PLL_REFCLK_SEL_MASK, (clk_cfg << CS40L26_PLL_REFCLK_FREQ_SHIFT) |
|
|
clk_sel);
|
|
if (error) {
|
|
dev_err(dev, "Failed to update REFCLK input\n");
|
|
return error;
|
|
}
|
|
|
|
return cs40l26_set_pll_loop(codec->core, CS40L26_PLL_REFCLK_SET_CLOSED_LOOP);
|
|
}
|
|
|
|
static int cs40l26_clk_en(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event)
|
|
{
|
|
struct cs40l26_codec *codec =
|
|
snd_soc_component_get_drvdata(snd_soc_dapm_to_component(w->dapm));
|
|
struct cs40l26_private *cs40l26 = codec->core;
|
|
struct device *dev = cs40l26->dev;
|
|
int error;
|
|
|
|
#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE
|
|
dev_info(dev, "%s: %s\n", __func__, event == SND_SOC_DAPM_POST_PMU ? "PMU" : "PMD");
|
|
#else
|
|
dev_dbg(dev, "%s: %s\n", __func__, event == SND_SOC_DAPM_POST_PMU ? "PMU" : "PMD");
|
|
#endif
|
|
|
|
switch (event) {
|
|
case SND_SOC_DAPM_POST_PMU:
|
|
mutex_lock(&cs40l26->lock);
|
|
cs40l26_vibe_state_update(cs40l26, CS40L26_VIBE_STATE_EVENT_ASP_START);
|
|
error = cs40l26_asp_start(cs40l26);
|
|
mutex_unlock(&cs40l26->lock);
|
|
if (error)
|
|
return error;
|
|
|
|
if (!completion_done(&cs40l26->i2s_cont)) {
|
|
if (!wait_for_completion_timeout(&cs40l26->i2s_cont,
|
|
msecs_to_jiffies(CS40L26_ASP_START_TIMEOUT)))
|
|
dev_warn(codec->dev, "SVC calibration not complete\n");
|
|
}
|
|
|
|
error = cs40l26_swap_ext_clk(codec, CS40L26_PLL_REFCLK_BCLK);
|
|
if (error)
|
|
return error;
|
|
break;
|
|
case SND_SOC_DAPM_PRE_PMD:
|
|
error = cs40l26_swap_ext_clk(codec, CS40L26_PLL_REFCLK_MCLK);
|
|
if (error)
|
|
return error;
|
|
|
|
mutex_lock(&cs40l26->lock);
|
|
cs40l26_vibe_state_update(cs40l26, CS40L26_VIBE_STATE_EVENT_ASP_STOP);
|
|
mutex_unlock(&cs40l26->lock);
|
|
|
|
break;
|
|
default:
|
|
dev_err(dev, "Invalid event: %d\n", event);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cs40l26_dsp_tx(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event)
|
|
{ struct cs40l26_codec *codec =
|
|
snd_soc_component_get_drvdata(snd_soc_dapm_to_component(w->dapm));
|
|
struct cs40l26_private *cs40l26 = codec->core;
|
|
struct device *dev = cs40l26->dev;
|
|
const struct firmware *fw;
|
|
int error;
|
|
u32 reg;
|
|
|
|
#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE
|
|
dev_info(dev, "%s: %s\n", __func__, event == SND_SOC_DAPM_POST_PMU ? "PMU" : "PMD");
|
|
#else
|
|
dev_dbg(dev, "%s: %s\n", __func__, event == SND_SOC_DAPM_POST_PMU ? "PMU" : "PMD");
|
|
#endif
|
|
|
|
if (codec->dsp_bypass) {
|
|
dev_err(dev, "Cannot use A2H while bypassing DSP\n");
|
|
return -EPERM;
|
|
}
|
|
|
|
error = cl_dsp_get_reg(cs40l26->dsp, "A2HEN", CL_DSP_XM_UNPACKED_TYPE, CS40L26_A2H_ALGO_ID,
|
|
®);
|
|
if (error)
|
|
return error;
|
|
|
|
switch (event) {
|
|
case SND_SOC_DAPM_POST_PMU:
|
|
if (codec->tuning != codec->tuning_prev) {
|
|
error = request_firmware(&fw, codec->bin_file, dev);
|
|
if (error) {
|
|
dev_err(codec->dev, "Failed to request %s\n", codec->bin_file);
|
|
return error;
|
|
}
|
|
|
|
error = cl_dsp_coeff_file_parse(cs40l26->dsp, fw);
|
|
release_firmware(fw);
|
|
if (error) {
|
|
dev_warn(dev, "Failed to load %s, %d. Continuing...",
|
|
codec->bin_file, error);
|
|
return error;
|
|
}
|
|
|
|
dev_info(dev, "%s Loaded Successfully\n", codec->bin_file);
|
|
|
|
codec->tuning_prev = codec->tuning;
|
|
|
|
error = cs40l26_mailbox_write(cs40l26, CS40L26_DSP_MBOX_CMD_A2H_REINIT);
|
|
if (error)
|
|
return error;
|
|
}
|
|
return regmap_write(cs40l26->regmap, reg, 1);
|
|
case SND_SOC_DAPM_PRE_PMD:
|
|
return regmap_write(cs40l26->regmap, reg, 0);
|
|
default:
|
|
dev_err(dev, "Invalid A2H event: %d\n", event);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
static int cs40l26_asp_rx(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event)
|
|
{ struct cs40l26_codec *codec =
|
|
snd_soc_component_get_drvdata(snd_soc_dapm_to_component(w->dapm));
|
|
bool is_revid_b2 = (codec->core->revid == (CS40L26_REVID_B2)) ? true : false;
|
|
struct cs40l26_private *cs40l26 = codec->core;
|
|
struct regmap *regmap = cs40l26->regmap;
|
|
struct device *dev = cs40l26->dev;
|
|
u32 flags = 0, reg = 0;
|
|
u8 data_src;
|
|
int error;
|
|
|
|
#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE
|
|
dev_info(dev, "%s: %s\n", __func__, event == SND_SOC_DAPM_POST_PMU ? "PMU" : "PMD");
|
|
#else
|
|
dev_dbg(dev, "%s: %s\n", __func__, event == SND_SOC_DAPM_POST_PMU ? "PMU" : "PMD");
|
|
#endif
|
|
|
|
mutex_lock(&cs40l26->lock);
|
|
|
|
data_src = codec->dsp_bypass ? CS40L26_DATA_SRC_ASPRX1 : CS40L26_DATA_SRC_DSP1TX1;
|
|
|
|
if (is_revid_b2) {
|
|
error = cl_dsp_get_reg(cs40l26->dsp, "FLAGS", CL_DSP_XM_UNPACKED_TYPE,
|
|
cs40l26->fw_id, ®);
|
|
if (error)
|
|
goto err_mutex;
|
|
|
|
error = regmap_read(regmap, reg, &flags);
|
|
if (error)
|
|
goto err_mutex;
|
|
}
|
|
|
|
switch (event) {
|
|
case SND_SOC_DAPM_POST_PMU:
|
|
error = regmap_update_bits(regmap, CS40L26_DACPCM1_INPUT,
|
|
CS40L26_DATA_SRC_MASK, data_src);
|
|
if (error) {
|
|
dev_err(dev, "Failed to set DAC PCM input\n");
|
|
goto err_mutex;
|
|
}
|
|
|
|
error = regmap_update_bits(regmap, CS40L26_ASPTX1_INPUT, CS40L26_DATA_SRC_MASK,
|
|
data_src);
|
|
if (error) {
|
|
dev_err(dev, "Failed to set ASPTX1 input\n");
|
|
goto err_mutex;
|
|
}
|
|
|
|
error = regmap_set_bits(regmap, CS40L26_ASP_ENABLES1, CS40L26_ASP_ENABLE_MASK);
|
|
if (error)
|
|
goto err_mutex;
|
|
|
|
/* Force open-loop if closed-loop not set */
|
|
if (!(flags & CS40L26_FLAGS_I2S_SVC_EN_MASK) && is_revid_b2) {
|
|
codec->svc_ol_forced = true;
|
|
error = regmap_set_bits(regmap, reg, CS40L26_FLAGS_I2S_SVC_EN_MASK |
|
|
CS40L26_FLAGS_I2S_SVC_LOOP_MASK);
|
|
if (error)
|
|
goto err_mutex;
|
|
} else {
|
|
codec->svc_ol_forced = false;
|
|
}
|
|
break;
|
|
case SND_SOC_DAPM_PRE_PMD:
|
|
error = cs40l26_mailbox_write(cs40l26, CS40L26_DSP_MBOX_CMD_STOP_I2S);
|
|
if (error)
|
|
goto err_mutex;
|
|
|
|
if (codec->svc_ol_forced) {
|
|
error = regmap_clear_bits(regmap, reg, CS40L26_FLAGS_I2S_SVC_EN_MASK |
|
|
CS40L26_FLAGS_I2S_SVC_LOOP_MASK);
|
|
if (error)
|
|
goto err_mutex;
|
|
}
|
|
|
|
error = regmap_clear_bits(regmap, CS40L26_ASP_ENABLES1, CS40L26_ASP_ENABLE_MASK);
|
|
if (error)
|
|
goto err_mutex;
|
|
|
|
error = regmap_update_bits(regmap, CS40L26_ASPTX1_INPUT, CS40L26_DATA_SRC_MASK,
|
|
CS40L26_DATA_SRC_VMON);
|
|
if (error)
|
|
dev_err(dev, "Failed to set ASPTX1 input\n");
|
|
break;
|
|
default:
|
|
dev_err(dev, "Invalid PCM event: %d\n", event);
|
|
error = -EINVAL;
|
|
}
|
|
|
|
err_mutex:
|
|
mutex_unlock(&cs40l26->lock);
|
|
|
|
return error;
|
|
}
|
|
|
|
static int cs40l26_i2s_vmon_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct cs40l26_codec *codec =
|
|
snd_soc_component_get_drvdata(snd_soc_kcontrol_component(kcontrol));
|
|
struct cs40l26_private *cs40l26 = codec->core;
|
|
int error;
|
|
u32 val;
|
|
|
|
#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE
|
|
dev_info(cs40l26->dev, "%s\n", __func__);
|
|
#endif
|
|
error = cs40l26_pm_enter(cs40l26->dev);
|
|
if (error)
|
|
return error;
|
|
|
|
error = regmap_read(cs40l26->regmap, CS40L26_SPKMON_VMON_DEC_OUT_DATA, &val);
|
|
if (error) {
|
|
dev_err(cs40l26->dev, "Failed to get VMON Data for I2S\n");
|
|
goto pm_err;
|
|
}
|
|
|
|
if (val & CS40L26_VMON_OVFL_FLAG_MASK) {
|
|
dev_err(cs40l26->dev, "I2S VMON overflow detected\n");
|
|
error = -EOVERFLOW;
|
|
goto pm_err;
|
|
}
|
|
|
|
ucontrol->value.enumerated.item[0] = val & CS40L26_VMON_DEC_OUT_DATA_MASK;
|
|
|
|
pm_err:
|
|
cs40l26_pm_exit(cs40l26->dev);
|
|
|
|
return error;
|
|
}
|
|
|
|
static int cs40l26_dsp_bypass_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct cs40l26_codec *codec =
|
|
snd_soc_component_get_drvdata(snd_soc_kcontrol_component(kcontrol));
|
|
struct cs40l26_private *cs40l26 = codec->core;
|
|
|
|
#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE
|
|
dev_info(cs40l26->dev, "%s\n", __func__);
|
|
#endif
|
|
mutex_lock(&cs40l26->lock);
|
|
|
|
if (codec->dsp_bypass)
|
|
ucontrol->value.enumerated.item[0] = 1;
|
|
else
|
|
ucontrol->value.enumerated.item[0] = 0;
|
|
|
|
mutex_unlock(&cs40l26->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cs40l26_dsp_bypass_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct cs40l26_codec *codec =
|
|
snd_soc_component_get_drvdata(snd_soc_kcontrol_component(kcontrol));
|
|
struct cs40l26_private *cs40l26 = codec->core;
|
|
|
|
#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE
|
|
dev_info(cs40l26->dev, "%s\n", __func__);
|
|
#endif
|
|
mutex_lock(&cs40l26->lock);
|
|
|
|
if (ucontrol->value.enumerated.item[0])
|
|
codec->dsp_bypass = true;
|
|
else
|
|
codec->dsp_bypass = false;
|
|
|
|
mutex_unlock(&cs40l26->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cs40l26_svc_en_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct cs40l26_codec *codec =
|
|
snd_soc_component_get_drvdata(snd_soc_kcontrol_component(kcontrol));
|
|
struct cs40l26_private *cs40l26 = codec->core;
|
|
struct regmap *regmap = cs40l26->regmap;
|
|
unsigned int algo_id, val = 0, reg;
|
|
struct device *dev = cs40l26->dev;
|
|
int error;
|
|
#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE
|
|
dev_info(dev, "%s\n", __func__);
|
|
#endif
|
|
error = cs40l26_pm_enter(dev);
|
|
if (error)
|
|
return error;
|
|
|
|
error = cs40l26_get_ram_ext_algo_id(cs40l26, &algo_id);
|
|
if (error)
|
|
goto pm_err;
|
|
|
|
error = cl_dsp_get_reg(cs40l26->dsp, "FLAGS", CL_DSP_XM_UNPACKED_TYPE, algo_id,
|
|
®);
|
|
if (error)
|
|
goto pm_err;
|
|
|
|
error = regmap_read(regmap, reg, &val);
|
|
if (error) {
|
|
dev_err(cs40l26->dev, "Failed to read FLAGS\n");
|
|
goto pm_err;
|
|
}
|
|
|
|
if (val & CS40L26_SVC_EN_MASK)
|
|
ucontrol->value.enumerated.item[0] = 1;
|
|
else
|
|
ucontrol->value.enumerated.item[0] = 0;
|
|
|
|
pm_err:
|
|
cs40l26_pm_exit(dev);
|
|
|
|
return error;
|
|
}
|
|
|
|
static int cs40l26_svc_en_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_dapm_context *dapm =
|
|
snd_soc_component_get_dapm(snd_soc_kcontrol_component(kcontrol));
|
|
struct cs40l26_codec *codec =
|
|
snd_soc_component_get_drvdata(snd_soc_kcontrol_component(kcontrol));
|
|
struct cs40l26_private *cs40l26 = codec->core;
|
|
struct regmap *regmap = cs40l26->regmap;
|
|
struct device *dev = cs40l26->dev;
|
|
unsigned int algo_id, reg;
|
|
#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE
|
|
int error = 0;
|
|
#else
|
|
int error;
|
|
#endif
|
|
|
|
#if IS_ENABLED(CONFIG_SEC_FACTORY)
|
|
return error;
|
|
#endif
|
|
|
|
error = cs40l26_pm_enter(dev);
|
|
if (error)
|
|
return error;
|
|
|
|
error = cs40l26_get_ram_ext_algo_id(cs40l26, &algo_id);
|
|
if (error)
|
|
goto pm_err;
|
|
|
|
#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE
|
|
dev_info(dev, "%s\n", __func__);
|
|
#endif
|
|
error = cl_dsp_get_reg(cs40l26->dsp, "FLAGS", CL_DSP_XM_UNPACKED_TYPE, algo_id,
|
|
®);
|
|
if (error)
|
|
goto pm_err;
|
|
|
|
snd_soc_dapm_mutex_lock(dapm);
|
|
|
|
error = regmap_update_bits(regmap, reg, CS40L26_SVC_EN_MASK,
|
|
ucontrol->value.enumerated.item[0]);
|
|
if (error)
|
|
dev_err(cs40l26->dev, "Failed to specify SVC for streaming\n");
|
|
|
|
snd_soc_dapm_mutex_unlock(dapm);
|
|
|
|
pm_err:
|
|
cs40l26_pm_exit(dev);
|
|
|
|
return error;
|
|
}
|
|
|
|
static int cs40l26_invert_streaming_data_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct cs40l26_codec *codec =
|
|
snd_soc_component_get_drvdata(snd_soc_kcontrol_component(kcontrol));
|
|
struct cs40l26_private *cs40l26 = codec->core;
|
|
struct regmap *regmap = cs40l26->regmap;
|
|
unsigned int algo_id, val = 0, reg;
|
|
struct device *dev = cs40l26->dev;
|
|
int error;
|
|
|
|
#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE
|
|
dev_info(dev, "%s\n", __func__);
|
|
#endif
|
|
error = cs40l26_pm_enter(dev);
|
|
if (error)
|
|
return error;
|
|
|
|
error = cs40l26_get_ram_ext_algo_id(cs40l26, &algo_id);
|
|
if (error)
|
|
goto pm_err;
|
|
|
|
error = cl_dsp_get_reg(cs40l26->dsp, "SOURCE_INVERT",
|
|
CL_DSP_XM_UNPACKED_TYPE, algo_id, ®);
|
|
if (error)
|
|
goto pm_err;
|
|
|
|
error = regmap_read(regmap, reg, &val);
|
|
if (error) {
|
|
dev_err(cs40l26->dev, "Failed to read SOURCE_INVERT\n");
|
|
goto pm_err;
|
|
}
|
|
|
|
if (val)
|
|
ucontrol->value.enumerated.item[0] = 1;
|
|
else
|
|
ucontrol->value.enumerated.item[0] = 0;
|
|
|
|
pm_err:
|
|
cs40l26_pm_exit(dev);
|
|
|
|
return error;
|
|
}
|
|
|
|
static int cs40l26_invert_streaming_data_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_dapm_context *dapm =
|
|
snd_soc_component_get_dapm(snd_soc_kcontrol_component(kcontrol));
|
|
struct cs40l26_codec *codec =
|
|
snd_soc_component_get_drvdata(snd_soc_kcontrol_component(kcontrol));
|
|
struct cs40l26_private *cs40l26 = codec->core;
|
|
struct regmap *regmap = cs40l26->regmap;
|
|
struct device *dev = cs40l26->dev;
|
|
unsigned int algo_id, reg;
|
|
int error;
|
|
|
|
#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE
|
|
dev_info(dev, "%s\n", __func__);
|
|
#endif
|
|
error = cs40l26_pm_enter(dev);
|
|
if (error)
|
|
return error;
|
|
|
|
error = cs40l26_get_ram_ext_algo_id(cs40l26, &algo_id);
|
|
if (error)
|
|
goto pm_err;
|
|
|
|
error = cl_dsp_get_reg(cs40l26->dsp, "SOURCE_INVERT",
|
|
CL_DSP_XM_UNPACKED_TYPE, algo_id, ®);
|
|
if (error)
|
|
goto pm_err;
|
|
|
|
snd_soc_dapm_mutex_lock(dapm);
|
|
|
|
error = regmap_write(regmap, reg, ucontrol->value.enumerated.item[0]);
|
|
if (error)
|
|
dev_err(cs40l26->dev, "Failed to specify invert streaming data\n");
|
|
|
|
snd_soc_dapm_mutex_unlock(dapm);
|
|
|
|
pm_err:
|
|
cs40l26_pm_exit(dev);
|
|
|
|
return error;
|
|
}
|
|
|
|
static int cs40l26_tuning_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct cs40l26_codec *codec =
|
|
snd_soc_component_get_drvdata(snd_soc_kcontrol_component(kcontrol));
|
|
#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE
|
|
struct cs40l26_private *cs40l26 = codec->core;
|
|
|
|
dev_info(cs40l26->dev, "%s\n", __func__);
|
|
#endif
|
|
ucontrol->value.enumerated.item[0] = codec->tuning;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cs40l26_tuning_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct cs40l26_codec *codec =
|
|
snd_soc_component_get_drvdata(snd_soc_kcontrol_component(kcontrol));
|
|
struct cs40l26_private *cs40l26 = codec->core;
|
|
|
|
#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE
|
|
dev_info(cs40l26->dev, "%s\n", __func__);
|
|
#endif
|
|
if (ucontrol->value.enumerated.item[0] == codec->tuning)
|
|
return 0;
|
|
|
|
if (cs40l26->asp_enable)
|
|
return -EBUSY;
|
|
|
|
codec->tuning = ucontrol->value.enumerated.item[0];
|
|
|
|
memset(codec->bin_file, 0, PAGE_SIZE);
|
|
codec->bin_file[PAGE_SIZE - 1] = '\0';
|
|
|
|
if (codec->tuning > 0)
|
|
snprintf(codec->bin_file, PAGE_SIZE, "cs40l26-a2h%d.bin", codec->tuning);
|
|
else
|
|
snprintf(codec->bin_file, PAGE_SIZE, "cs40l26-a2h.bin");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cs40l26_a2h_level_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct cs40l26_codec *codec =
|
|
snd_soc_component_get_drvdata(snd_soc_kcontrol_component(kcontrol));
|
|
struct cs40l26_private *cs40l26 = codec->core;
|
|
struct regmap *regmap = cs40l26->regmap;
|
|
struct device *dev = cs40l26->dev;
|
|
unsigned int val = 0, reg;
|
|
int error;
|
|
|
|
#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE
|
|
dev_info(dev, "%s\n", __func__);
|
|
#endif
|
|
error = cl_dsp_get_reg(cs40l26->dsp, "VOLUMELEVEL", CL_DSP_XM_UNPACKED_TYPE,
|
|
CS40L26_A2H_ALGO_ID, ®);
|
|
if (error)
|
|
return error;
|
|
|
|
error = cs40l26_pm_enter(dev);
|
|
if (error)
|
|
return error;
|
|
|
|
error = regmap_read(regmap, reg, &val);
|
|
if (error) {
|
|
dev_err(dev, "Failed to get VOLUMELEVEL\n");
|
|
goto pm_err;
|
|
}
|
|
|
|
ucontrol->value.integer.value[0] = val;
|
|
|
|
pm_err:
|
|
cs40l26_pm_exit(dev);
|
|
|
|
return error;
|
|
}
|
|
|
|
static int cs40l26_a2h_level_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_dapm_context *dapm =
|
|
snd_soc_component_get_dapm(snd_soc_kcontrol_component(kcontrol));
|
|
struct cs40l26_codec *codec =
|
|
snd_soc_component_get_drvdata(snd_soc_kcontrol_component(kcontrol));
|
|
struct cs40l26_private *cs40l26 = codec->core;
|
|
struct regmap *regmap = cs40l26->regmap;
|
|
struct device *dev = cs40l26->dev;
|
|
unsigned int val = 0, reg;
|
|
int error;
|
|
|
|
#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE
|
|
dev_info(dev, "%s\n", __func__);
|
|
#endif
|
|
error = cl_dsp_get_reg(cs40l26->dsp, "VOLUMELEVEL", CL_DSP_XM_UNPACKED_TYPE,
|
|
CS40L26_A2H_ALGO_ID, ®);
|
|
if (error)
|
|
return error;
|
|
|
|
error = cs40l26_pm_enter(dev);
|
|
if (error)
|
|
return error;
|
|
|
|
snd_soc_dapm_mutex_lock(dapm);
|
|
|
|
if (ucontrol->value.integer.value[0] > CS40L26_A2H_LEVEL_MAX)
|
|
val = CS40L26_A2H_LEVEL_MAX;
|
|
else if (ucontrol->value.integer.value[0] < CS40L26_A2H_LEVEL_MIN)
|
|
val = CS40L26_A2H_LEVEL_MIN;
|
|
else
|
|
val = ucontrol->value.integer.value[0];
|
|
|
|
error = regmap_write(regmap, reg, val);
|
|
if (error)
|
|
dev_err(dev, "Failed to set VOLUMELEVEL\n");
|
|
|
|
snd_soc_dapm_mutex_unlock(dapm);
|
|
|
|
cs40l26_pm_exit(dev);
|
|
|
|
return error;
|
|
}
|
|
|
|
static int cs40l26_i2s_atten_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct cs40l26_codec *codec =
|
|
snd_soc_component_get_drvdata(snd_soc_kcontrol_component(kcontrol));
|
|
struct cs40l26_private *cs40l26 = codec->core;
|
|
struct regmap *regmap = cs40l26->regmap;
|
|
struct device *dev = cs40l26->dev;
|
|
unsigned int val = 0, reg;
|
|
int error;
|
|
|
|
error = cl_dsp_get_reg(cs40l26->dsp, "I2S_ATTENUATION", CL_DSP_XM_UNPACKED_TYPE,
|
|
cs40l26->fw_id, ®);
|
|
if (error)
|
|
goto pm_err;
|
|
|
|
error = cs40l26_pm_enter(dev);
|
|
if (error)
|
|
return error;
|
|
|
|
error = regmap_read(regmap, reg, &val);
|
|
if (error)
|
|
goto pm_err;
|
|
|
|
ucontrol->value.integer.value[0] = val;
|
|
|
|
pm_err:
|
|
cs40l26_pm_exit(dev);
|
|
|
|
return error;
|
|
}
|
|
|
|
static int cs40l26_i2s_atten_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol);
|
|
struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(comp);
|
|
struct cs40l26_codec *codec = snd_soc_component_get_drvdata(comp);
|
|
struct cs40l26_private *cs40l26 = codec->core;
|
|
struct regmap *regmap = cs40l26->regmap;
|
|
struct device *dev = cs40l26->dev;
|
|
u32 val = 0, reg;
|
|
int error;
|
|
|
|
error = cs40l26_pm_enter(dev);
|
|
if (error)
|
|
return error;
|
|
|
|
error = cl_dsp_get_reg(cs40l26->dsp, "I2S_ATTENUATION", CL_DSP_XM_UNPACKED_TYPE,
|
|
cs40l26->fw_id, ®);
|
|
if (error)
|
|
goto pm_err;
|
|
|
|
snd_soc_dapm_mutex_lock(dapm);
|
|
|
|
if (ucontrol->value.integer.value[0] > CS40L26_I2S_ATTENUATION_MAX)
|
|
val = CS40L26_I2S_ATTENUATION_MAX;
|
|
else if (ucontrol->value.integer.value[0] < 0)
|
|
val = 0;
|
|
else
|
|
val = ucontrol->value.integer.value[0];
|
|
|
|
error = regmap_write(regmap, reg, val);
|
|
|
|
snd_soc_dapm_mutex_unlock(dapm);
|
|
pm_err:
|
|
cs40l26_pm_exit(dev);
|
|
|
|
return error;
|
|
}
|
|
|
|
static int cs40l26_a2h_delay_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct cs40l26_codec *codec =
|
|
snd_soc_component_get_drvdata(snd_soc_kcontrol_component(kcontrol));
|
|
struct cs40l26_private *cs40l26 = codec->core;
|
|
struct regmap *regmap = cs40l26->regmap;
|
|
struct device *dev = cs40l26->dev;
|
|
unsigned int val = 0, reg;
|
|
int error;
|
|
|
|
#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE
|
|
dev_info(dev, "%s\n", __func__);
|
|
#endif
|
|
error = cl_dsp_get_reg(cs40l26->dsp, "LRADELAYSAMPS",
|
|
CL_DSP_XM_UNPACKED_TYPE, CS40L26_A2H_ALGO_ID, ®);
|
|
if (error)
|
|
return error;
|
|
|
|
error = cs40l26_pm_enter(dev);
|
|
if (error)
|
|
return error;
|
|
|
|
error = regmap_read(regmap, reg, &val);
|
|
if (error) {
|
|
dev_err(dev, "Failed to get LRADELAYSAMPS\n");
|
|
goto err;
|
|
}
|
|
|
|
ucontrol->value.integer.value[0] = val;
|
|
|
|
err:
|
|
cs40l26_pm_exit(dev);
|
|
|
|
return error;
|
|
}
|
|
|
|
static int cs40l26_a2h_delay_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_dapm_context *dapm =
|
|
snd_soc_component_get_dapm(snd_soc_kcontrol_component(kcontrol));
|
|
struct cs40l26_codec *codec =
|
|
snd_soc_component_get_drvdata(snd_soc_kcontrol_component(kcontrol));
|
|
struct cs40l26_private *cs40l26 = codec->core;
|
|
struct regmap *regmap = cs40l26->regmap;
|
|
struct device *dev = cs40l26->dev;
|
|
unsigned int val = 0, reg;
|
|
int error;
|
|
|
|
#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE
|
|
dev_info(dev, "%s\n", __func__);
|
|
#endif
|
|
error = cl_dsp_get_reg(cs40l26->dsp, "LRADELAYSAMPS",
|
|
CL_DSP_XM_UNPACKED_TYPE, CS40L26_A2H_ALGO_ID, ®);
|
|
if (error)
|
|
return error;
|
|
|
|
error = cs40l26_pm_enter(dev);
|
|
if (error)
|
|
return error;
|
|
|
|
snd_soc_dapm_mutex_lock(dapm);
|
|
|
|
if (ucontrol->value.integer.value[0] > CS40L26_A2H_DELAY_MAX)
|
|
val = CS40L26_A2H_DELAY_MAX;
|
|
else if (ucontrol->value.integer.value[0] < 0)
|
|
val = 0;
|
|
else
|
|
val = ucontrol->value.integer.value[0];
|
|
|
|
error = regmap_write(regmap, reg, val);
|
|
if (error)
|
|
dev_err(dev, "Failed to set LRADELAYSAMPS\n");
|
|
|
|
snd_soc_dapm_mutex_unlock(dapm);
|
|
|
|
cs40l26_pm_exit(dev);
|
|
|
|
return error;
|
|
}
|
|
|
|
static int cs40l26_boost_disable_delay_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct cs40l26_codec *codec =
|
|
snd_soc_component_get_drvdata(snd_soc_kcontrol_component(kcontrol));
|
|
struct cs40l26_private *cs40l26 = codec->core;
|
|
u32 algo_id, delay, reg;
|
|
int error;
|
|
|
|
error = cs40l26_pm_enter(cs40l26->dev);
|
|
if (error)
|
|
return error;
|
|
|
|
error = cs40l26_get_ram_ext_algo_id(cs40l26, &algo_id);
|
|
if (error)
|
|
goto pm_err;
|
|
|
|
error = cl_dsp_get_reg(cs40l26->dsp, "BOOST_DISABLE_DELAY", CL_DSP_XM_UNPACKED_TYPE,
|
|
algo_id, ®);
|
|
if (error)
|
|
goto pm_err;
|
|
|
|
error = regmap_read(cs40l26->regmap, reg, &delay);
|
|
if (error)
|
|
goto pm_err;
|
|
|
|
ucontrol->value.integer.value[0] = delay;
|
|
|
|
pm_err:
|
|
cs40l26_pm_exit(cs40l26->dev);
|
|
|
|
return error;
|
|
}
|
|
|
|
static int cs40l26_boost_disable_delay_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol);
|
|
struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(comp);
|
|
struct cs40l26_codec *codec = snd_soc_component_get_drvdata(comp);
|
|
struct cs40l26_private *cs40l26 = codec->core;
|
|
u32 algo_id, delay, reg;
|
|
int error;
|
|
|
|
error = cs40l26_pm_enter(cs40l26->dev);
|
|
if (error)
|
|
return error;
|
|
|
|
error = cs40l26_get_ram_ext_algo_id(cs40l26, &algo_id);
|
|
if (error)
|
|
goto pm_err;
|
|
|
|
error = cl_dsp_get_reg(cs40l26->dsp, "BOOST_DISABLE_DELAY", CL_DSP_XM_UNPACKED_TYPE,
|
|
algo_id, ®);
|
|
if (error)
|
|
goto pm_err;
|
|
|
|
snd_soc_dapm_mutex_lock(dapm);
|
|
|
|
delay = ucontrol->value.integer.value[0];
|
|
|
|
error = regmap_write(cs40l26->regmap, reg, delay);
|
|
|
|
snd_soc_dapm_mutex_unlock(dapm);
|
|
|
|
pm_err:
|
|
cs40l26_pm_exit(cs40l26->dev);
|
|
|
|
return error;
|
|
}
|
|
|
|
static const struct snd_kcontrol_new cs40l26_controls[] = {
|
|
SOC_SINGLE_EXT("A2H Tuning", 0, 0, CS40L26_A2H_MAX_TUNINGS, 0, cs40l26_tuning_get,
|
|
cs40l26_tuning_put),
|
|
SOC_SINGLE_EXT("A2H Level", 0, 0, CS40L26_A2H_LEVEL_MAX, 0, cs40l26_a2h_level_get,
|
|
cs40l26_a2h_level_put),
|
|
SOC_SINGLE_EXT("SVC Algo Enable", 0, 0, 1, 0, cs40l26_svc_en_get, cs40l26_svc_en_put),
|
|
SOC_SINGLE_EXT("Invert streaming data", 0, 0, 1, 0, cs40l26_invert_streaming_data_get,
|
|
cs40l26_invert_streaming_data_put),
|
|
SOC_SINGLE_EXT("I2S VMON", 0, 0, CS40L26_VMON_DEC_OUT_DATA_MAX, 0,
|
|
cs40l26_i2s_vmon_get, NULL),
|
|
SOC_SINGLE_EXT("DSP Bypass", 0, 0, 1, 0, cs40l26_dsp_bypass_get, cs40l26_dsp_bypass_put),
|
|
SOC_SINGLE_EXT("A2H Delay", 0, 0, CS40L26_A2H_DELAY_MAX, 0, cs40l26_a2h_delay_get,
|
|
cs40l26_a2h_delay_put),
|
|
SOC_SINGLE_EXT("Boost Disable Delay", 0, 0, CS40L26_BOOST_DISABLE_DELAY_MAX, 0,
|
|
cs40l26_boost_disable_delay_get, cs40l26_boost_disable_delay_put),
|
|
};
|
|
|
|
static const struct snd_kcontrol_new cs40l26_b2_controls[] = {
|
|
SOC_SINGLE_EXT("I2S Attenuation", 0, 0, CS40L26_I2S_ATTENUATION_MAX, 0,
|
|
cs40l26_i2s_atten_get, cs40l26_i2s_atten_put),
|
|
};
|
|
|
|
static const char * const cs40l26_out_mux_texts[] = { "Off", "PCM", "A2H" };
|
|
static SOC_ENUM_SINGLE_VIRT_DECL(cs40l26_out_mux_enum, cs40l26_out_mux_texts);
|
|
static const struct snd_kcontrol_new cs40l26_out_mux =
|
|
SOC_DAPM_ENUM("Haptics Source", cs40l26_out_mux_enum);
|
|
|
|
static const struct snd_soc_dapm_widget cs40l26_dapm_widgets[] = {
|
|
SND_SOC_DAPM_SUPPLY_S("ASP PLL", 0, SND_SOC_NOPM, 0, 0, cs40l26_clk_en,
|
|
SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
|
|
SND_SOC_DAPM_AIF_IN("ASPRX1", NULL, 0, SND_SOC_NOPM, 0, 0),
|
|
SND_SOC_DAPM_AIF_IN("ASPRX2", NULL, 0, SND_SOC_NOPM, 0, 0),
|
|
|
|
SND_SOC_DAPM_PGA_E("PCM", SND_SOC_NOPM, 0, 0, NULL, 0, cs40l26_asp_rx,
|
|
SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
|
|
SND_SOC_DAPM_MIXER_E("A2H", SND_SOC_NOPM, 0, 0, NULL, 0, cs40l26_dsp_tx,
|
|
SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
|
|
|
|
SND_SOC_DAPM_MUX("Haptics Source", SND_SOC_NOPM, 0, 0, &cs40l26_out_mux),
|
|
SND_SOC_DAPM_OUTPUT("OUT"),
|
|
};
|
|
|
|
static const struct snd_soc_dapm_route cs40l26_dapm_routes[] = {
|
|
{ "ASP Playback", NULL, "ASP PLL" },
|
|
{ "ASPRX1", NULL, "ASP Playback" },
|
|
{ "ASPRX2", NULL, "ASP Playback" },
|
|
|
|
{ "PCM", NULL, "ASPRX1" },
|
|
{ "PCM", NULL, "ASPRX2" },
|
|
{ "A2H", NULL, "PCM" },
|
|
|
|
{ "Haptics Source", "PCM", "PCM" },
|
|
{ "Haptics Source", "A2H", "A2H" },
|
|
{ "OUT", NULL, "Haptics Source" },
|
|
};
|
|
|
|
static int cs40l26_component_set_sysclk(struct snd_soc_component *component,
|
|
int clk_id, int source, unsigned int freq, int dir)
|
|
{
|
|
struct cs40l26_codec *codec = snd_soc_component_get_drvdata(component);
|
|
struct device *dev = codec->dev;
|
|
u8 clk_cfg;
|
|
int error;
|
|
|
|
#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE
|
|
dev_info(dev, "%s clk_id(%d) source(%d) freq(%u) dir(%d)\n",
|
|
__func__, clk_id, source, freq, dir);
|
|
#endif
|
|
error = cs40l26_get_clk_config((u32) (CS40L26_PLL_CLK_FREQ_MASK & freq), &clk_cfg);
|
|
if (error) {
|
|
dev_err(dev, "Invalid Clock Frequency: %u Hz\n", freq);
|
|
return error;
|
|
}
|
|
|
|
if (clk_id != 0) {
|
|
dev_err(dev, "Invalid Input Clock (ID: %d)\n", clk_id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
codec->sysclk_rate = (u32) (CS40L26_PLL_CLK_FREQ_MASK & freq);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cs40l26_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
|
|
{
|
|
struct cs40l26_codec *codec =
|
|
snd_soc_component_get_drvdata(codec_dai->component);
|
|
|
|
#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE
|
|
dev_info(codec->dev, "%s fmt(%u)\n", __func__, fmt);
|
|
#endif
|
|
if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS) {
|
|
dev_err(codec->dev, "Device can not be master\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
|
|
case SND_SOC_DAIFMT_NB_NF:
|
|
codec->daifmt = 0;
|
|
break;
|
|
case SND_SOC_DAIFMT_NB_IF:
|
|
codec->daifmt = CS40L26_ASP_FSYNC_INV_MASK;
|
|
break;
|
|
case SND_SOC_DAIFMT_IB_NF:
|
|
codec->daifmt = CS40L26_ASP_BCLK_INV_MASK;
|
|
break;
|
|
case SND_SOC_DAIFMT_IB_IF:
|
|
codec->daifmt = CS40L26_ASP_FSYNC_INV_MASK | CS40L26_ASP_BCLK_INV_MASK;
|
|
break;
|
|
default:
|
|
dev_err(codec->dev, "Invalid DAI clock INV\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
|
|
case SND_SOC_DAIFMT_DSP_A:
|
|
codec->daifmt |= ((CS40L26_ASP_FMT_TDM1_DSPA << CS40L26_ASP_FMT_SHIFT) &
|
|
CS40L26_ASP_FMT_MASK);
|
|
break;
|
|
case SND_SOC_DAIFMT_I2S:
|
|
codec->daifmt |= ((CS40L26_ASP_FMT_I2S << CS40L26_ASP_FMT_SHIFT) &
|
|
CS40L26_ASP_FMT_MASK);
|
|
break;
|
|
default:
|
|
dev_err(codec->dev, "Invalid DAI format: 0x%X\n", fmt & SND_SOC_DAIFMT_FORMAT_MASK);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cs40l26_pcm_hw_params(struct snd_pcm_substream *substream,
|
|
struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
|
|
{
|
|
struct cs40l26_codec *codec = snd_soc_component_get_drvdata(dai->component);
|
|
u32 asp_rx_wl, asp_rx_width;
|
|
int error;
|
|
|
|
#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE
|
|
dev_info(codec->dev, "%s\n", __func__);
|
|
#endif
|
|
error = cs40l26_pm_enter(codec->dev);
|
|
if (error)
|
|
return error;
|
|
|
|
switch (params_rate(params)) {
|
|
case 48000:
|
|
error = regmap_clear_bits(codec->regmap, CS40L26_MONITOR_FILT,
|
|
CS40L26_VIMON_DUAL_RATE_MASK);
|
|
break;
|
|
case 96000:
|
|
error = regmap_set_bits(codec->regmap, CS40L26_MONITOR_FILT,
|
|
CS40L26_VIMON_DUAL_RATE_MASK);
|
|
break;
|
|
default:
|
|
dev_err(codec->dev, "Invalid sample rate: %d Hz\n", params_rate(params));
|
|
error = -EINVAL;
|
|
}
|
|
if (error) {
|
|
dev_err(codec->dev, "%s Failed with error %d\n", __func__, error);
|
|
goto err_pm;
|
|
}
|
|
|
|
asp_rx_wl = (u8) (params_width(params) & 0xFF);
|
|
error = regmap_update_bits(codec->regmap, CS40L26_ASP_DATA_CONTROL5,
|
|
CS40L26_ASP_RX_WL_MASK, asp_rx_wl);
|
|
if (error) {
|
|
dev_err(codec->dev, "Failed to update ASP RX WL\n");
|
|
goto err_pm;
|
|
}
|
|
|
|
if (!codec->tdm_width)
|
|
asp_rx_width = asp_rx_wl;
|
|
else
|
|
asp_rx_width = (u8) (codec->tdm_width & 0xFF);
|
|
|
|
codec->daifmt |= ((asp_rx_width << CS40L26_ASP_RX_WIDTH_SHIFT) &
|
|
CS40L26_ASP_RX_WIDTH_MASK);
|
|
|
|
error = regmap_update_bits(codec->regmap, CS40L26_ASP_CONTROL2,
|
|
CS40L26_ASP_FSYNC_INV_MASK | CS40L26_ASP_BCLK_INV_MASK |
|
|
CS40L26_ASP_FMT_MASK | CS40L26_ASP_RX_WIDTH_MASK, codec->daifmt);
|
|
if (error) {
|
|
dev_err(codec->dev, "Failed to update ASP RX width\n");
|
|
goto err_pm;
|
|
}
|
|
|
|
error = regmap_update_bits(codec->regmap, CS40L26_ASP_FRAME_CONTROL5,
|
|
CS40L26_ASP_RX1_SLOT_MASK | CS40L26_ASP_RX2_SLOT_MASK,
|
|
codec->tdm_slot[0] | (codec->tdm_slot[1] << CS40L26_ASP_RX2_SLOT_SHIFT));
|
|
if (error) {
|
|
dev_err(codec->dev, "Failed to update ASP slot number\n");
|
|
goto err_pm;
|
|
}
|
|
|
|
#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE
|
|
dev_info(codec->dev, "ASP: %d bits in %d bit slots, slot #s: %d, %d\n",
|
|
asp_rx_wl, asp_rx_width, codec->tdm_slot[0], codec->tdm_slot[1]);
|
|
#else
|
|
dev_dbg(codec->dev, "ASP: %d bits in %d bit slots, slot #s: %d, %d\n",
|
|
asp_rx_wl, asp_rx_width, codec->tdm_slot[0], codec->tdm_slot[1]);
|
|
#endif
|
|
|
|
err_pm:
|
|
cs40l26_pm_exit(codec->dev);
|
|
|
|
return error;
|
|
}
|
|
|
|
static int cs40l26_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
|
|
unsigned int rx_mask, int slots, int slot_width)
|
|
{
|
|
struct cs40l26_codec *codec =
|
|
snd_soc_component_get_drvdata(dai->component);
|
|
|
|
#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE
|
|
dev_info(codec->dev, "%s\n", __func__);
|
|
#endif
|
|
|
|
codec->tdm_width = slot_width;
|
|
codec->tdm_slots = slots;
|
|
|
|
/* Reset to slots 0,1 if TDM is being disabled, and catch the case
|
|
* where both RX1 and RX2 would be set to slot 0 since that causes
|
|
* hardware to flag an error
|
|
*/
|
|
if (!slots || rx_mask == 0x1)
|
|
rx_mask = 0x3;
|
|
|
|
codec->tdm_slot[0] = ffs(rx_mask) - 1;
|
|
rx_mask &= ~(1 << codec->tdm_slot[0]);
|
|
codec->tdm_slot[1] = ffs(rx_mask) - 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct snd_soc_dai_ops cs40l26_dai_ops = {
|
|
.set_fmt = cs40l26_set_dai_fmt,
|
|
.set_tdm_slot = cs40l26_set_tdm_slot,
|
|
.hw_params = cs40l26_pcm_hw_params,
|
|
};
|
|
|
|
static struct snd_soc_dai_driver cs40l26_dai[] = {
|
|
{
|
|
.name = "cs40l26-pcm",
|
|
.id = 0,
|
|
.playback = {
|
|
.stream_name = "ASP Playback",
|
|
.channels_min = 1,
|
|
.channels_max = 2,
|
|
.rates = CS40L26_RATES,
|
|
.formats = CS40L26_FORMATS,
|
|
},
|
|
.ops = &cs40l26_dai_ops,
|
|
.symmetric_rate = 1,
|
|
},
|
|
};
|
|
|
|
static int cs40l26_codec_probe(struct snd_soc_component *component)
|
|
{
|
|
struct cs40l26_codec *codec = snd_soc_component_get_drvdata(component);
|
|
|
|
#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE
|
|
dev_info(codec->dev, "%s\n", __func__);
|
|
#endif
|
|
codec->bin_file = devm_kzalloc(codec->dev, PAGE_SIZE, GFP_KERNEL);
|
|
if (!codec->bin_file)
|
|
return -ENOMEM;
|
|
|
|
codec->bin_file[PAGE_SIZE - 1] = '\0';
|
|
snprintf(codec->bin_file, PAGE_SIZE, "cs40l26-a2h.bin");
|
|
|
|
/* Default audio SCLK frequency */
|
|
codec->sysclk_rate = CS40L26_PLL_CLK_FRQ_1536000;
|
|
|
|
codec->tdm_slot[0] = 0;
|
|
codec->tdm_slot[1] = 1;
|
|
|
|
if (codec->core->revid == CS40L26_REVID_B2)
|
|
snd_soc_add_component_controls(component, cs40l26_b2_controls,
|
|
ARRAY_SIZE(cs40l26_b2_controls));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct snd_soc_component_driver soc_codec_dev_cs40l26 = {
|
|
.probe = cs40l26_codec_probe,
|
|
.set_sysclk = cs40l26_component_set_sysclk,
|
|
|
|
.dapm_widgets = cs40l26_dapm_widgets,
|
|
.num_dapm_widgets = ARRAY_SIZE(cs40l26_dapm_widgets),
|
|
.dapm_routes = cs40l26_dapm_routes,
|
|
.num_dapm_routes = ARRAY_SIZE(cs40l26_dapm_routes),
|
|
.controls = cs40l26_controls,
|
|
.num_controls = ARRAY_SIZE(cs40l26_controls),
|
|
};
|
|
|
|
static int cs40l26_codec_driver_probe(struct platform_device *pdev)
|
|
{
|
|
struct cs40l26_private *cs40l26 = dev_get_drvdata(pdev->dev.parent);
|
|
struct cs40l26_codec *codec;
|
|
int error;
|
|
|
|
#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE
|
|
dev_info(cs40l26->dev, "%s\n", __func__);
|
|
#endif
|
|
codec = devm_kzalloc(&pdev->dev, sizeof(struct cs40l26_codec), GFP_KERNEL);
|
|
if (!codec)
|
|
return -ENOMEM;
|
|
|
|
codec->core = cs40l26;
|
|
codec->regmap = cs40l26->regmap;
|
|
codec->dev = &pdev->dev;
|
|
|
|
platform_set_drvdata(pdev, codec);
|
|
|
|
pm_runtime_enable(&pdev->dev);
|
|
|
|
error = snd_soc_register_component(&pdev->dev, &soc_codec_dev_cs40l26,
|
|
cs40l26_dai, ARRAY_SIZE(cs40l26_dai));
|
|
if (error < 0)
|
|
dev_err(&pdev->dev, "Failed to register codec: %d\n", error);
|
|
|
|
return error;
|
|
}
|
|
|
|
static int cs40l26_codec_driver_remove(struct platform_device *pdev)
|
|
{
|
|
struct cs40l26_codec *codec = dev_get_drvdata(&pdev->dev);
|
|
|
|
#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE
|
|
dev_info(codec->dev, "%s\n", __func__);
|
|
#endif
|
|
pm_runtime_disable(codec->dev);
|
|
|
|
snd_soc_unregister_component(codec->dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver cs40l26_codec_driver = {
|
|
.driver = {
|
|
.name = "cs40l26-codec",
|
|
},
|
|
.probe = cs40l26_codec_driver_probe,
|
|
.remove = cs40l26_codec_driver_remove,
|
|
};
|
|
module_platform_driver(cs40l26_codec_driver);
|
|
|
|
MODULE_DESCRIPTION("ASoC CS40L26 driver");
|
|
MODULE_AUTHOR("Fred Treven <fred.treven@cirrus.com>");
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_ALIAS("platform:cs40l26-codec");
|