// SPDX-License-Identifier: GPL-2.0
//
// cs35l45.c - CS35L45 ALSA SoC audio driver
//
// Copyright 2019-2022 Cirrus Logic, Inc.
//
// Author: James Schulman <james.schulman@cirrus.com>
#include <linux/gpio/consumer.h>
#include <linux/module.h>
#include <linux/pm_runtime.h>
#include <linux/property.h>
#include <linux/firmware.h>
#include <linux/regulator/consumer.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/tlv.h>
#include "cs35l45.h"
static bool cs35l45_check_cspl_mbox_sts(const enum cs35l45_cspl_mboxcmd cmd,
enum cs35l45_cspl_mboxstate sts)
{
switch (cmd) {
case CSPL_MBOX_CMD_NONE:
case CSPL_MBOX_CMD_UNKNOWN_CMD:
return true;
case CSPL_MBOX_CMD_PAUSE:
case CSPL_MBOX_CMD_OUT_OF_HIBERNATE:
return (sts == CSPL_MBOX_STS_PAUSED);
case CSPL_MBOX_CMD_RESUME:
return (sts == CSPL_MBOX_STS_RUNNING);
case CSPL_MBOX_CMD_REINIT:
return (sts == CSPL_MBOX_STS_RUNNING);
case CSPL_MBOX_CMD_STOP_PRE_REINIT:
return (sts == CSPL_MBOX_STS_RDY_FOR_REINIT);
case CSPL_MBOX_CMD_HIBERNATE:
return (sts == CSPL_MBOX_STS_HIBERNATE);
default:
return false;
}
}
static int cs35l45_set_cspl_mbox_cmd(struct cs35l45_private *cs35l45,
struct regmap *regmap,
const enum cs35l45_cspl_mboxcmd cmd)
{
unsigned int sts = 0, i;
int ret;
if (!cs35l45->dsp.cs_dsp.running) {
dev_err(cs35l45->dev, "DSP not running\n");
return -EPERM;
}
// Set mailbox cmd
ret = regmap_write(regmap, CS35L45_DSP_VIRT1_MBOX_1, cmd);
if (ret < 0) {
if (cmd != CSPL_MBOX_CMD_OUT_OF_HIBERNATE)
dev_err(cs35l45->dev, "Failed to write MBOX: %d\n", ret);
return ret;
}
// Read mailbox status and verify it is appropriate for the given cmd
for (i = 0; i < 5; i++) {
usleep_range(1000, 1100);
ret = regmap_read(regmap, CS35L45_DSP_MBOX_2, &sts);
if (ret < 0) {
dev_err(cs35l45->dev, "Failed to read MBOX STS: %d\n", ret);
continue;
}
if (!cs35l45_check_cspl_mbox_sts(cmd, sts))
dev_dbg(cs35l45->dev, "[%u] cmd %u returned invalid sts %u", i, cmd, sts);
else
return 0;
}
if (cmd != CSPL_MBOX_CMD_OUT_OF_HIBERNATE)
dev_err(cs35l45->dev, "Failed to set mailbox cmd %u (status %u)\n", cmd, sts);
return -ENOMSG;
}
static int cs35l45_global_en_ev(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
struct cs35l45_private *cs35l45 = snd_soc_component_get_drvdata(component);
dev_dbg(cs35l45->dev, "%s event : %x\n", __func__, event);
switch (event) {
case SND_SOC_DAPM_POST_PMU:
regmap_write(cs35l45->regmap, CS35L45_GLOBAL_ENABLES,
CS35L45_GLOBAL_EN_MASK);
usleep_range(CS35L45_POST_GLOBAL_EN_US, CS35L45_POST_GLOBAL_EN_US + 100);
break;
case SND_SOC_DAPM_PRE_PMD:
usleep_range(CS35L45_PRE_GLOBAL_DIS_US, CS35L45_PRE_GLOBAL_DIS_US + 100);
regmap_write(cs35l45->regmap, CS35L45_GLOBAL_ENABLES, 0);
break;
default:
break;
}
return 0;
}
static int cs35l45_dsp_preload_ev(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
struct cs35l45_private *cs35l45 = snd_soc_component_get_drvdata(component);
int ret;
switch (event) {
case SND_SOC_DAPM_PRE_PMU:
if (cs35l45->dsp.cs_dsp.booted)
return 0;
return wm_adsp_early_event(w, kcontrol, event);
case SND_SOC_DAPM_POST_PMU:
if (cs35l45->dsp.cs_dsp.running)
return 0;
regmap_set_bits(cs35l45->regmap, CS35L45_PWRMGT_CTL,
CS35L45_MEM_RDY_MASK);
return wm_adsp_event(w, kcontrol, event);
case SND_SOC_DAPM_PRE_PMD:
if (cs35l45->dsp.preloaded)
return 0;
if (cs35l45->dsp.cs_dsp.running) {
ret = wm_adsp_event(w, kcontrol, event);
if (ret)
return ret;
}
return wm_adsp_early_event(w, kcontrol, event);
default:
return 0;
}
}
static int cs35l45_dsp_audio_ev(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
struct cs35l45_private *cs35l45 = snd_soc_component_get_drvdata(component);
switch (event) {
case SND_SOC_DAPM_POST_PMU:
return cs35l45_set_cspl_mbox_cmd(cs35l45, cs35l45->regmap,
CSPL_MBOX_CMD_RESUME);
case SND_SOC_DAPM_PRE_PMD:
return cs35l45_set_cspl_mbox_cmd(cs35l45, cs35l45->regmap,
CSPL_MBOX_CMD_PAUSE);
default:
return 0;
}
return 0;
}
static int cs35l45_activate_ctl(struct snd_soc_component *component,
const char *ctl_name, bool active)
{
struct snd_card *card = component->card->snd_card;
struct snd_kcontrol *kcontrol;
struct snd_kcontrol_volatile *vd;
unsigned int index_offset;
char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
if (component->name_prefix)
snprintf(name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, "%s %s",
component->name_prefix, ctl_name);
else
snprintf(name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, "%s", ctl_name);
kcontrol = snd_soc_card_get_kcontrol(component->card, name);
if (!kcontrol) {
dev_err(component->dev, "Can't find kcontrol %s\n", name);
return -EINVAL;
}
index_offset = snd_ctl_get_ioff(kcontrol, &kcontrol->id);
vd = &kcontrol->vd[index_offset];
if (active)
vd->access |= SNDRV_CTL_ELEM_ACCESS_WRITE;
else
vd->access &= ~SNDRV_CTL_ELEM_ACCESS_WRITE;
snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_INFO, &kcontrol->id);
return 0;
}
static int cs35l45_amplifier_mode_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component =
snd_soc_kcontrol_component(kcontrol);
struct cs35l45_private *cs35l45 =
snd_soc_component_get_drvdata(component);
ucontrol->value.integer.value[0] = cs35l45->amplifier_mode;
return 0;
}
static int cs35l45_amplifier_mode_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component =
snd_soc_kcontrol_component(kcontrol);
struct cs35l45_private *cs35l45 =
snd_soc_component_get_drvdata(component);
struct snd_soc_dapm_context *dapm =
snd_soc_component_get_dapm(component);
unsigned int amp_state;
int ret;
if ((ucontrol->value.integer.value[0] == cs35l45->amplifier_mode) ||
(ucontrol->value.integer.value[0] > AMP_MODE_RCV))
return 0;
snd_soc_dapm_mutex_lock(dapm);
ret = regmap_read(cs35l45->regmap, CS35L45_BLOCK_ENABLES, &_state);
if (ret < 0) {
dev_err(cs35l45->dev, "Failed to read AMP state: %d\n", ret);
snd_soc_dapm_mutex_unlock(dapm);
return ret;
}
regmap_clear_bits(cs35l45->regmap, CS35L45_BLOCK_ENABLES,
CS35L45_AMP_EN_MASK);
snd_soc_component_disable_pin_unlocked(component, "SPK");
snd_soc_dapm_sync_unlocked(dapm);
if (ucontrol->value.integer.value[0] == AMP_MODE_SPK) {
regmap_clear_bits(cs35l45->regmap, CS35L45_BLOCK_ENABLES,
CS35L45_RCV_EN_MASK);
regmap_update_bits(cs35l45->regmap, CS35L45_BLOCK_ENABLES,
CS35L45_BST_EN_MASK,
CS35L45_BST_ENABLE << CS35L45_BST_EN_SHIFT);
regmap_update_bits(cs35l45->regmap, CS35L45_HVLV
|