Merge "ASoC: wcd934x: Add support for digital core power collapse"
This commit is contained in:
commit
9aa659eeb2
2 changed files with 180 additions and 2 deletions
|
@ -1872,6 +1872,10 @@ static bool wcd934x_is_volatile_register(struct device *dev, unsigned int reg)
|
||||||
case WCD934X_CPE_SS_PWR_SYS_PSTATE_CTL_1:
|
case WCD934X_CPE_SS_PWR_SYS_PSTATE_CTL_1:
|
||||||
case WCD934X_CPE_SS_CPAR_CTL:
|
case WCD934X_CPE_SS_CPAR_CTL:
|
||||||
case WCD934X_CPE_SS_STATUS:
|
case WCD934X_CPE_SS_STATUS:
|
||||||
|
case WCD934X_CODEC_RPM_RST_CTL:
|
||||||
|
case WCD934X_SIDO_NEW_VOUT_A_STARTUP:
|
||||||
|
case WCD934X_SIDO_NEW_VOUT_D_STARTUP:
|
||||||
|
case WCD934X_CODEC_RPM_PWR_CDC_DIG_HM_CTL:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -123,6 +123,9 @@ static const struct snd_kcontrol_new name##_mux = \
|
||||||
#define WCD934X_DEC_PWR_LVL_DF 0x00
|
#define WCD934X_DEC_PWR_LVL_DF 0x00
|
||||||
#define WCD934X_STRING_LEN 100
|
#define WCD934X_STRING_LEN 100
|
||||||
|
|
||||||
|
#define WCD934X_DIG_CORE_REG_MIN WCD934X_CDC_ANC0_CLK_RESET_CTL
|
||||||
|
#define WCD934X_DIG_CORE_REG_MAX 0xFFF
|
||||||
|
|
||||||
#define WCD934X_MAX_MICBIAS 4
|
#define WCD934X_MAX_MICBIAS 4
|
||||||
#define DAPM_MICBIAS1_STANDALONE "MIC BIAS1 Standalone"
|
#define DAPM_MICBIAS1_STANDALONE "MIC BIAS1 Standalone"
|
||||||
#define DAPM_MICBIAS2_STANDALONE "MIC BIAS2 Standalone"
|
#define DAPM_MICBIAS2_STANDALONE "MIC BIAS2 Standalone"
|
||||||
|
@ -141,6 +144,24 @@ static const struct snd_kcontrol_new name##_mux = \
|
||||||
|
|
||||||
#define TAVIL_VERSION_ENTRY_SIZE 17
|
#define TAVIL_VERSION_ENTRY_SIZE 17
|
||||||
|
|
||||||
|
#define WCD934X_DIG_CORE_COLLAPSE_TIMER_MS (5 * 1000)
|
||||||
|
|
||||||
|
enum {
|
||||||
|
POWER_COLLAPSE,
|
||||||
|
POWER_RESUME,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int dig_core_collapse_enable = 1;
|
||||||
|
module_param(dig_core_collapse_enable, int,
|
||||||
|
S_IRUGO | S_IWUSR | S_IWGRP);
|
||||||
|
MODULE_PARM_DESC(dig_core_collapse_enable, "enable/disable power gating");
|
||||||
|
|
||||||
|
/* dig_core_collapse timer in seconds */
|
||||||
|
static int dig_core_collapse_timer = (WCD934X_DIG_CORE_COLLAPSE_TIMER_MS/1000);
|
||||||
|
module_param(dig_core_collapse_timer, int,
|
||||||
|
S_IRUGO | S_IWUSR | S_IWGRP);
|
||||||
|
MODULE_PARM_DESC(dig_core_collapse_timer, "timer for power gating");
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
VI_SENSE_1,
|
VI_SENSE_1,
|
||||||
VI_SENSE_2,
|
VI_SENSE_2,
|
||||||
|
@ -530,6 +551,9 @@ struct tavil_priv {
|
||||||
struct wcd934x_swr swr;
|
struct wcd934x_swr swr;
|
||||||
struct mutex micb_lock;
|
struct mutex micb_lock;
|
||||||
|
|
||||||
|
struct delayed_work power_gate_work;
|
||||||
|
struct mutex power_lock;
|
||||||
|
|
||||||
struct clk *wcd_ext_clk;
|
struct clk *wcd_ext_clk;
|
||||||
|
|
||||||
/* mbhc module */
|
/* mbhc module */
|
||||||
|
@ -563,6 +587,8 @@ struct tavil_priv {
|
||||||
int main_clk_users[WCD934X_NUM_INTERPOLATORS];
|
int main_clk_users[WCD934X_NUM_INTERPOLATORS];
|
||||||
struct tavil_dsd_config *dsd_config;
|
struct tavil_dsd_config *dsd_config;
|
||||||
struct tavil_idle_detect_config idle_det_cfg;
|
struct tavil_idle_detect_config idle_det_cfg;
|
||||||
|
|
||||||
|
int power_active_ref;
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct tavil_reg_mask_val tavil_spkr_default[] = {
|
static const struct tavil_reg_mask_val tavil_spkr_default[] = {
|
||||||
|
@ -7067,6 +7093,136 @@ static struct snd_soc_dai_driver tavil_dai[] = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static void tavil_codec_power_gate_digital_core(struct tavil_priv *tavil)
|
||||||
|
{
|
||||||
|
struct snd_soc_codec *codec = tavil->codec;
|
||||||
|
|
||||||
|
if (!codec)
|
||||||
|
return;
|
||||||
|
|
||||||
|
mutex_lock(&tavil->power_lock);
|
||||||
|
dev_dbg(codec->dev, "%s: Entering power gating function, %d\n",
|
||||||
|
__func__, tavil->power_active_ref);
|
||||||
|
|
||||||
|
if (tavil->power_active_ref > 0)
|
||||||
|
goto exit;
|
||||||
|
|
||||||
|
wcd9xxx_set_power_state(tavil->wcd9xxx,
|
||||||
|
WCD_REGION_POWER_COLLAPSE_BEGIN,
|
||||||
|
WCD9XXX_DIG_CORE_REGION_1);
|
||||||
|
snd_soc_update_bits(codec, WCD934X_CODEC_RPM_PWR_CDC_DIG_HM_CTL,
|
||||||
|
0x04, 0x04);
|
||||||
|
snd_soc_update_bits(codec, WCD934X_CODEC_RPM_PWR_CDC_DIG_HM_CTL,
|
||||||
|
0x01, 0x00);
|
||||||
|
snd_soc_update_bits(codec, WCD934X_CODEC_RPM_PWR_CDC_DIG_HM_CTL,
|
||||||
|
0x02, 0x00);
|
||||||
|
wcd9xxx_set_power_state(tavil->wcd9xxx, WCD_REGION_POWER_DOWN,
|
||||||
|
WCD9XXX_DIG_CORE_REGION_1);
|
||||||
|
exit:
|
||||||
|
dev_dbg(codec->dev, "%s: Exiting power gating function, %d\n",
|
||||||
|
__func__, tavil->power_active_ref);
|
||||||
|
mutex_unlock(&tavil->power_lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tavil_codec_power_gate_work(struct work_struct *work)
|
||||||
|
{
|
||||||
|
struct tavil_priv *tavil;
|
||||||
|
struct delayed_work *dwork;
|
||||||
|
struct snd_soc_codec *codec;
|
||||||
|
|
||||||
|
dwork = to_delayed_work(work);
|
||||||
|
tavil = container_of(dwork, struct tavil_priv, power_gate_work);
|
||||||
|
codec = tavil->codec;
|
||||||
|
|
||||||
|
if (!codec)
|
||||||
|
return;
|
||||||
|
|
||||||
|
tavil_codec_power_gate_digital_core(tavil);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* called under power_lock acquisition */
|
||||||
|
static int tavil_dig_core_remove_power_collapse(struct snd_soc_codec *codec)
|
||||||
|
{
|
||||||
|
struct tavil_priv *tavil = snd_soc_codec_get_drvdata(codec);
|
||||||
|
|
||||||
|
snd_soc_write(codec, WCD934X_CODEC_RPM_PWR_CDC_DIG_HM_CTL, 0x5);
|
||||||
|
snd_soc_write(codec, WCD934X_CODEC_RPM_PWR_CDC_DIG_HM_CTL, 0x7);
|
||||||
|
snd_soc_write(codec, WCD934X_CODEC_RPM_PWR_CDC_DIG_HM_CTL, 0x3);
|
||||||
|
snd_soc_update_bits(codec, WCD934X_CODEC_RPM_RST_CTL, 0x02, 0x00);
|
||||||
|
snd_soc_update_bits(codec, WCD934X_CODEC_RPM_RST_CTL, 0x02, 0x02);
|
||||||
|
|
||||||
|
wcd9xxx_set_power_state(tavil->wcd9xxx,
|
||||||
|
WCD_REGION_POWER_COLLAPSE_REMOVE,
|
||||||
|
WCD9XXX_DIG_CORE_REGION_1);
|
||||||
|
regcache_mark_dirty(codec->component.regmap);
|
||||||
|
regcache_sync_region(codec->component.regmap,
|
||||||
|
WCD934X_DIG_CORE_REG_MIN,
|
||||||
|
WCD934X_DIG_CORE_REG_MAX);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int tavil_dig_core_power_collapse(struct tavil_priv *tavil,
|
||||||
|
int req_state)
|
||||||
|
{
|
||||||
|
struct snd_soc_codec *codec;
|
||||||
|
int cur_state;
|
||||||
|
|
||||||
|
/* Exit if feature is disabled */
|
||||||
|
if (!dig_core_collapse_enable)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
mutex_lock(&tavil->power_lock);
|
||||||
|
if (req_state == POWER_COLLAPSE)
|
||||||
|
tavil->power_active_ref--;
|
||||||
|
else if (req_state == POWER_RESUME)
|
||||||
|
tavil->power_active_ref++;
|
||||||
|
else
|
||||||
|
goto unlock_mutex;
|
||||||
|
|
||||||
|
if (tavil->power_active_ref < 0) {
|
||||||
|
dev_dbg(tavil->dev, "%s: power_active_ref is negative\n",
|
||||||
|
__func__);
|
||||||
|
goto unlock_mutex;
|
||||||
|
}
|
||||||
|
|
||||||
|
codec = tavil->codec;
|
||||||
|
if (!codec)
|
||||||
|
goto unlock_mutex;
|
||||||
|
|
||||||
|
if (req_state == POWER_COLLAPSE) {
|
||||||
|
if (tavil->power_active_ref == 0) {
|
||||||
|
schedule_delayed_work(&tavil->power_gate_work,
|
||||||
|
msecs_to_jiffies(dig_core_collapse_timer * 1000));
|
||||||
|
}
|
||||||
|
} else if (req_state == POWER_RESUME) {
|
||||||
|
if (tavil->power_active_ref == 1) {
|
||||||
|
/*
|
||||||
|
* At this point, there can be two cases:
|
||||||
|
* 1. Core already in power collapse state
|
||||||
|
* 2. Timer kicked in and still did not expire or
|
||||||
|
* waiting for the power_lock
|
||||||
|
*/
|
||||||
|
cur_state = wcd9xxx_get_current_power_state(
|
||||||
|
tavil->wcd9xxx,
|
||||||
|
WCD9XXX_DIG_CORE_REGION_1);
|
||||||
|
if (cur_state == WCD_REGION_POWER_DOWN) {
|
||||||
|
tavil_dig_core_remove_power_collapse(codec);
|
||||||
|
} else {
|
||||||
|
mutex_unlock(&tavil->power_lock);
|
||||||
|
cancel_delayed_work_sync(
|
||||||
|
&tavil->power_gate_work);
|
||||||
|
mutex_lock(&tavil->power_lock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unlock_mutex:
|
||||||
|
mutex_unlock(&tavil->power_lock);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int tavil_cdc_req_mclk_enable(struct tavil_priv *tavil,
|
static int tavil_cdc_req_mclk_enable(struct tavil_priv *tavil,
|
||||||
bool enable)
|
bool enable)
|
||||||
{
|
{
|
||||||
|
@ -7108,15 +7264,15 @@ static int __tavil_cdc_mclk_enable_locked(struct tavil_priv *tavil,
|
||||||
dev_dbg(tavil->dev, "%s: mclk_enable = %u\n", __func__, enable);
|
dev_dbg(tavil->dev, "%s: mclk_enable = %u\n", __func__, enable);
|
||||||
|
|
||||||
if (enable) {
|
if (enable) {
|
||||||
|
tavil_dig_core_power_collapse(tavil, POWER_RESUME);
|
||||||
tavil_vote_svs(tavil, true);
|
tavil_vote_svs(tavil, true);
|
||||||
ret = tavil_cdc_req_mclk_enable(tavil, true);
|
ret = tavil_cdc_req_mclk_enable(tavil, true);
|
||||||
if (ret)
|
if (ret)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
||||||
set_bit(AUDIO_NOMINAL, &tavil->status_mask);
|
|
||||||
} else {
|
} else {
|
||||||
tavil_cdc_req_mclk_enable(tavil, false);
|
tavil_cdc_req_mclk_enable(tavil, false);
|
||||||
tavil_vote_svs(tavil, false);
|
tavil_vote_svs(tavil, false);
|
||||||
|
tavil_dig_core_power_collapse(tavil, POWER_COLLAPSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
done:
|
done:
|
||||||
|
@ -8050,6 +8206,9 @@ static int tavil_suspend(struct device *dev)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
dev_dbg(dev, "%s: system suspend\n", __func__);
|
dev_dbg(dev, "%s: system suspend\n", __func__);
|
||||||
|
if (delayed_work_pending(&tavil->power_gate_work) &&
|
||||||
|
cancel_delayed_work_sync(&tavil->power_gate_work))
|
||||||
|
tavil_codec_power_gate_digital_core(tavil);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8551,6 +8710,7 @@ static int tavil_probe(struct platform_device *pdev)
|
||||||
struct tavil_priv *tavil;
|
struct tavil_priv *tavil;
|
||||||
struct clk *wcd_ext_clk;
|
struct clk *wcd_ext_clk;
|
||||||
struct wcd9xxx_resmgr_v2 *resmgr;
|
struct wcd9xxx_resmgr_v2 *resmgr;
|
||||||
|
struct wcd9xxx_power_region *cdc_pwr;
|
||||||
|
|
||||||
tavil = devm_kzalloc(&pdev->dev, sizeof(struct tavil_priv),
|
tavil = devm_kzalloc(&pdev->dev, sizeof(struct tavil_priv),
|
||||||
GFP_KERNEL);
|
GFP_KERNEL);
|
||||||
|
@ -8561,6 +8721,8 @@ static int tavil_probe(struct platform_device *pdev)
|
||||||
|
|
||||||
tavil->wcd9xxx = dev_get_drvdata(pdev->dev.parent);
|
tavil->wcd9xxx = dev_get_drvdata(pdev->dev.parent);
|
||||||
tavil->dev = &pdev->dev;
|
tavil->dev = &pdev->dev;
|
||||||
|
INIT_DELAYED_WORK(&tavil->power_gate_work, tavil_codec_power_gate_work);
|
||||||
|
mutex_init(&tavil->power_lock);
|
||||||
INIT_WORK(&tavil->tavil_add_child_devices_work,
|
INIT_WORK(&tavil->tavil_add_child_devices_work,
|
||||||
tavil_add_child_devices);
|
tavil_add_child_devices);
|
||||||
mutex_init(&tavil->micb_lock);
|
mutex_init(&tavil->micb_lock);
|
||||||
|
@ -8577,6 +8739,18 @@ static int tavil_probe(struct platform_device *pdev)
|
||||||
*/
|
*/
|
||||||
tavil->svs_ref_cnt = 1;
|
tavil->svs_ref_cnt = 1;
|
||||||
|
|
||||||
|
cdc_pwr = devm_kzalloc(&pdev->dev, sizeof(struct wcd9xxx_power_region),
|
||||||
|
GFP_KERNEL);
|
||||||
|
if (!cdc_pwr) {
|
||||||
|
ret = -ENOMEM;
|
||||||
|
goto err_resmgr;
|
||||||
|
}
|
||||||
|
tavil->wcd9xxx->wcd9xxx_pwr[WCD9XXX_DIG_CORE_REGION_1] = cdc_pwr;
|
||||||
|
cdc_pwr->pwr_collapse_reg_min = WCD934X_DIG_CORE_REG_MIN;
|
||||||
|
cdc_pwr->pwr_collapse_reg_max = WCD934X_DIG_CORE_REG_MAX;
|
||||||
|
wcd9xxx_set_power_state(tavil->wcd9xxx,
|
||||||
|
WCD_REGION_POWER_COLLAPSE_REMOVE,
|
||||||
|
WCD9XXX_DIG_CORE_REGION_1);
|
||||||
/*
|
/*
|
||||||
* Init resource manager so that if child nodes such as SoundWire
|
* Init resource manager so that if child nodes such as SoundWire
|
||||||
* requests for clock, resource manager can honor the request
|
* requests for clock, resource manager can honor the request
|
||||||
|
|
Loading…
Add table
Reference in a new issue