Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 87cdd5d4 authored by Phani Kumar Uppalapati's avatar Phani Kumar Uppalapati
Browse files

ASoC: wcd9335: Add support for digital core power collapse



When no audio use-cases are active, codec digital core
can be put into power collapse to reduce the power consumption
during codec stand-by. Add support to put core into power
collapse and remove out of the collapse when audio use-cases
are triggered.

Change-Id: Ib72b72239712d1b20542950f05b698647edcb9fa
Signed-off-by: default avatarPhani Kumar Uppalapati <phaniu@codeaurora.org>
parent 871d3667
Loading
Loading
Loading
Loading
+108 −0
Original line number Diff line number Diff line
@@ -204,6 +204,35 @@ static int wcd9xxx_page_write(struct wcd9xxx *wcd9xxx, unsigned short *reg)
	return ret;
}

static bool is_wcd9xxx_reg_power_down(struct wcd9xxx *wcd9xxx, u16 rreg)
{
	bool ret = false;
	int i;
	struct wcd9xxx_power_region *wcd9xxx_pwr;

	if (!wcd9xxx)
		return ret;

	for (i = 0; i < WCD9XXX_MAX_PWR_REGIONS; i++) {
		wcd9xxx_pwr = wcd9xxx->wcd9xxx_pwr[i];
		if (!wcd9xxx_pwr)
			continue;
		if (((wcd9xxx_pwr->pwr_collapse_reg_min == 0) &&
		     (wcd9xxx_pwr->pwr_collapse_reg_max == 0)) ||
		    (wcd9xxx_pwr->power_state ==
		     WCD_REGION_POWER_COLLAPSE_REMOVE))
			ret = false;
		else if (((wcd9xxx_pwr->power_state ==
			   WCD_REGION_POWER_DOWN) ||
			  (wcd9xxx_pwr->power_state ==
			   WCD_REGION_POWER_COLLAPSE_BEGIN)) &&
			 (rreg >= wcd9xxx_pwr->pwr_collapse_reg_min) &&
			 (rreg <= wcd9xxx_pwr->pwr_collapse_reg_max))
			ret = true;
	}
	return ret;
}

static int regmap_slim_read(void *context, const void *reg, size_t reg_size,
			    void *val, size_t val_size)
{
@@ -231,6 +260,10 @@ static int regmap_slim_read(void *context, const void *reg, size_t reg_size,
	c_reg = *(u16 *)reg;
	rreg = c_reg;

	if (is_wcd9xxx_reg_power_down(wcd9xxx, rreg)) {
		ret = 0;
		goto err;
	}
	ret = wcd9xxx_page_write(wcd9xxx, &c_reg);
	if (ret)
		goto err;
@@ -323,6 +356,10 @@ static int regmap_slim_gather_write(void *context,
	c_reg = *(u16 *)reg;
	rreg = c_reg;

	if (is_wcd9xxx_reg_power_down(wcd9xxx, rreg)) {
		ret = 0;
		goto err;
	}
	ret = wcd9xxx_page_write(wcd9xxx, &c_reg);
	if (ret)
		goto err;
@@ -498,6 +535,77 @@ int wcd9xxx_bulk_write(
}
EXPORT_SYMBOL(wcd9xxx_bulk_write);

/*
 * wcd9xxx_get_current_power_state: Get power state of the region
 * @wcd9xxx: handle to wcd core
 * @region: region index
 *
 * Returns current power state of the region or error code for failure
 */
int wcd9xxx_get_current_power_state(struct wcd9xxx *wcd9xxx,
				    enum wcd_power_regions region)
{
	int state;

	if (!wcd9xxx) {
		pr_err("%s: wcd9xxx is NULL\n", __func__);
		return -EINVAL;
	}

	if ((region < 0) || (region >= WCD9XXX_MAX_PWR_REGIONS)) {
		dev_err(wcd9xxx->dev, "%s: region index %d out of bounds\n",
			__func__, region);
		return -EINVAL;
	}
	if (!wcd9xxx->wcd9xxx_pwr[region]) {
		dev_err(wcd9xxx->dev, "%s: memory not created for region: %d\n",
			__func__, region);
		return -EINVAL;
	}

	mutex_lock(&wcd9xxx->io_lock);
	state = wcd9xxx->wcd9xxx_pwr[region]->power_state;
	mutex_unlock(&wcd9xxx->io_lock);

	return state;
}
EXPORT_SYMBOL(wcd9xxx_get_current_power_state);

/*
 * wcd9xxx_set_power_state: set power state for the region
 * @wcd9xxx: handle to wcd core
 * @state: power state to be set
 * @region: region index
 *
 * Returns error code in case of failure or 0 for success
 */
int wcd9xxx_set_power_state(struct wcd9xxx *wcd9xxx,
			    enum codec_power_states state,
			    enum wcd_power_regions region)
{
	if (!wcd9xxx) {
		pr_err("%s: wcd9xxx is NULL\n", __func__);
		return -EINVAL;
	}

	if ((region < 0) || (region >= WCD9XXX_MAX_PWR_REGIONS)) {
		dev_err(wcd9xxx->dev, "%s: region index %d out of bounds\n",
			__func__, region);
		return -EINVAL;
	}
	if (!wcd9xxx->wcd9xxx_pwr[region]) {
		dev_err(wcd9xxx->dev, "%s: memory not created for region: %d\n",
			__func__, region);
		return -EINVAL;
	}
	mutex_lock(&wcd9xxx->io_lock);
	wcd9xxx->wcd9xxx_pwr[region]->power_state = state;
	mutex_unlock(&wcd9xxx->io_lock);

	return 0;
}
EXPORT_SYMBOL(wcd9xxx_set_power_state);

static int wcd9xxx_slim_read_device(struct wcd9xxx *wcd9xxx, unsigned short reg,
				int bytes, void *dest, bool interface)
{
+22 −0
Original line number Diff line number Diff line
@@ -218,6 +218,17 @@ enum wcd9xxx_chipid_major {
	TASHA_MAJOR = cpu_to_le16(0x0),
};

enum codec_power_states {
	WCD_REGION_POWER_COLLAPSE_REMOVE,
	WCD_REGION_POWER_COLLAPSE_BEGIN,
	WCD_REGION_POWER_DOWN,
};

enum wcd_power_regions {
	WCD9XXX_DIG_CORE_REGION_1,
	WCD9XXX_MAX_PWR_REGIONS,
};

struct wcd9xxx_codec_type {
	u16 id_major;
	u16 id_minor;
@@ -229,6 +240,12 @@ struct wcd9xxx_codec_type {
	u16 i2c_chip_status;
};

struct wcd9xxx_power_region {
	enum codec_power_states power_state;
	u16 pwr_collapse_reg_min;
	u16 pwr_collapse_reg_max;
};

struct wcd9xxx {
	struct device *dev;
	struct slim_device *slim;
@@ -270,6 +287,7 @@ struct wcd9xxx {
	const struct wcd9xxx_codec_type *codec_type;
	bool prev_pg_valid;
	u8 prev_pg;
	struct wcd9xxx_power_region *wcd9xxx_pwr[WCD9XXX_MAX_PWR_REGIONS];
};

int wcd9xxx_interface_reg_read(struct wcd9xxx *wcd9xxx, unsigned short reg);
@@ -280,6 +298,10 @@ int wcd9xxx_slim_write_repeat(struct wcd9xxx *wcd9xxx, unsigned short reg,
			     int bytes, void *src);
int wcd9xxx_slim_reserve_bw(struct wcd9xxx *wcd9xxx,
			    u32 bw_ops, bool commit);
int wcd9xxx_set_power_state(struct wcd9xxx *, enum codec_power_states,
			    enum wcd_power_regions);
int wcd9xxx_get_current_power_state(struct wcd9xxx *,
				    enum wcd_power_regions);

#if defined(CONFIG_WCD9310_CODEC) || \
	defined(CONFIG_WCD9304_CODEC) || \
+174 −0
Original line number Diff line number Diff line
@@ -95,6 +95,9 @@
#define CPE_FLL_CLK_150MHZ 150000000
#define WCD9335_REG_BITS 8

#define TASHA_DIG_CORE_REG_MIN  WCD9335_CDC_ANC0_CLK_RESET_CTL
#define TASHA_DIG_CORE_REG_MAX  0xDFF

/* Convert from vout ctl to micbias voltage in mV */
#define WCD_VOUT_CTL_TO_MICB(v) (1000 + v * 50)

@@ -113,6 +116,24 @@ module_param(cpe_debug_mode, int,
	     S_IRUGO | S_IWUSR | S_IWGRP);
MODULE_PARM_DESC(cpe_debug_mode, "boot cpe in debug mode");

#define TASHA_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 = (TASHA_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");

static struct afe_param_slimbus_slave_port_cfg tasha_slimbus_slave_port_cfg = {
	.minor_version = 1,
	.slimbus_dev_id = AFE_SLIMBUS_DEVICE_1,
@@ -559,6 +580,8 @@ struct tasha_priv {
	int spl_src_users[SPLINE_SRC_MAX];

	struct wcd9xxx_resmgr_v2 *resmgr;
	struct delayed_work power_gate_work;
	struct mutex power_lock;

	/* mbhc module */
	struct wcd_mbhc mbhc;
@@ -574,6 +597,7 @@ struct tasha_priv {

	struct snd_info_entry *entry;
	struct snd_info_entry *version_entry;
	int power_active_ref;
};

int tasha_enable_efuse_sensing(struct snd_soc_codec *codec)
@@ -8050,6 +8074,136 @@ static struct snd_soc_dai_driver tasha_dai[] = {
	},
};

static void tasha_codec_power_gate_work(struct work_struct *work)
{
	struct tasha_priv *tasha;
	struct delayed_work *dwork;
	struct snd_soc_codec *codec;

	dwork = to_delayed_work(work);
	tasha = container_of(dwork, struct tasha_priv, power_gate_work);
	codec = tasha->codec;

	mutex_lock(&tasha->power_lock);
	dev_dbg(codec->dev, "%s: Entering power gating function, %d\n",
		__func__, tasha->power_active_ref);

	if (tasha->power_active_ref > 0)
		goto exit;

	wcd9xxx_set_power_state(tasha->wcd9xxx,
			WCD_REGION_POWER_COLLAPSE_BEGIN,
			WCD9XXX_DIG_CORE_REGION_1);
	if (TASHA_IS_1_0(tasha->wcd9xxx->version)) {
		snd_soc_update_bits(codec, WCD9335_CODEC_RPM_PWR_CDC_DIG_HM_CTL,
				0x04, 0x04);
		snd_soc_update_bits(codec, WCD9335_CODEC_RPM_PWR_CDC_DIG_HM_CTL,
				0x01, 0x01);
		snd_soc_update_bits(codec, WCD9335_CODEC_RPM_PWR_CDC_DIG_HM_CTL,
				0x02, 0x02);
	} else {
		snd_soc_update_bits(codec, WCD9335_CODEC_RPM_PWR_CDC_DIG_HM_CTL,
				0x04, 0x04);
		snd_soc_update_bits(codec, WCD9335_CODEC_RPM_PWR_CDC_DIG_HM_CTL,
				0x01, 0x00);
		snd_soc_update_bits(codec, WCD9335_CODEC_RPM_PWR_CDC_DIG_HM_CTL,
				0x02, 0x00);
	}
	wcd9xxx_set_power_state(tasha->wcd9xxx, WCD_REGION_POWER_DOWN,
				WCD9XXX_DIG_CORE_REGION_1);
exit:
	dev_dbg(codec->dev, "%s: Exiting power gating function, %d\n",
		__func__, tasha->power_active_ref);
	mutex_unlock(&tasha->power_lock);
}

/* called under power_lock acquisition */
static int tasha_dig_core_remove_power_collapse(struct snd_soc_codec *codec)
{
	struct tasha_priv *tasha = snd_soc_codec_get_drvdata(codec);
	struct wcd9xxx *wcd9xxx = tasha->wcd9xxx;

	if (TASHA_IS_1_0(wcd9xxx->version)) {
		snd_soc_write(codec, WCD9335_CODEC_RPM_PWR_CDC_DIG_HM_CTL,
			      0x03);
	} else {
		snd_soc_write(codec, WCD9335_CODEC_RPM_PWR_CDC_DIG_HM_CTL, 0x5);
		snd_soc_write(codec, WCD9335_CODEC_RPM_PWR_CDC_DIG_HM_CTL, 0x7);
		snd_soc_write(codec, WCD9335_CODEC_RPM_PWR_CDC_DIG_HM_CTL, 0x3);
	}
	snd_soc_update_bits(codec, WCD9335_CODEC_RPM_RST_CTL, 0x02, 0x00);
	snd_soc_update_bits(codec, WCD9335_CODEC_RPM_RST_CTL, 0x02, 0x02);

	wcd9xxx_set_power_state(tasha->wcd9xxx,
			WCD_REGION_POWER_COLLAPSE_REMOVE,
			WCD9XXX_DIG_CORE_REGION_1);
	regcache_mark_dirty(codec->component.regmap);
	regcache_sync(codec->component.regmap);

	return 0;
}

static int tasha_dig_core_power_collapse(struct tasha_priv *tasha,
					 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(&tasha->power_lock);
	if (req_state == POWER_COLLAPSE)
		tasha->power_active_ref--;
	else if (req_state == POWER_RESUME)
		tasha->power_active_ref++;
	else
		goto unlock_mutex;

	if (tasha->power_active_ref < 0) {
		dev_dbg(tasha->dev, "%s: power_active_ref is negative\n",
			__func__);
		goto unlock_mutex;
	}

	codec = tasha->codec;
	if (!codec)
		goto unlock_mutex;

	if (req_state == POWER_COLLAPSE) {
		if (tasha->power_active_ref == 0) {
			schedule_delayed_work(&tasha->power_gate_work,
			msecs_to_jiffies(dig_core_collapse_timer * 1000));
		}
	} else if (req_state == POWER_RESUME) {
		if (tasha->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(
						tasha->wcd9xxx,
						WCD9XXX_DIG_CORE_REGION_1);
			if (cur_state == WCD_REGION_POWER_DOWN)
				tasha_dig_core_remove_power_collapse(codec);
			else {
				mutex_unlock(&tasha->power_lock);
				cancel_delayed_work_sync(
						&tasha->power_gate_work);
				mutex_lock(&tasha->power_lock);
			}
		}
	}

unlock_mutex:
	mutex_unlock(&tasha->power_lock);

	return 0;
}

static int __tasha_cdc_mclk_enable_locked(struct tasha_priv *tasha,
					  bool enable)
{
@@ -8063,6 +8217,7 @@ static int __tasha_cdc_mclk_enable_locked(struct tasha_priv *tasha,
	dev_dbg(tasha->dev, "%s: mclk_enable = %u\n", __func__, enable);

	if (enable) {
		tasha_dig_core_power_collapse(tasha, POWER_RESUME);
		ret = clk_prepare_enable(tasha->wcd_ext_clk);
		if (ret) {
			dev_err(tasha->dev, "%s: ext clk enable failed\n",
@@ -8079,6 +8234,7 @@ static int __tasha_cdc_mclk_enable_locked(struct tasha_priv *tasha,
		/* put BG */
		wcd_resmgr_disable_master_bias(tasha->resmgr);
		clk_disable_unprepare(tasha->wcd_ext_clk);
		tasha_dig_core_power_collapse(tasha, POWER_COLLAPSE);
	}

err:
@@ -9434,6 +9590,7 @@ static int tasha_probe(struct platform_device *pdev)
	struct tasha_priv *tasha;
	struct clk *wcd_ext_clk;
	struct wcd9xxx_resmgr_v2 *resmgr;
	struct wcd9xxx_power_region *cdc_pwr;

	tasha = devm_kzalloc(&pdev->dev, sizeof(struct tasha_priv),
			    GFP_KERNEL);
@@ -9446,12 +9603,27 @@ static int tasha_probe(struct platform_device *pdev)

	tasha->wcd9xxx = dev_get_drvdata(pdev->dev.parent);
	tasha->dev = &pdev->dev;
	INIT_DELAYED_WORK(&tasha->power_gate_work, tasha_codec_power_gate_work);
	mutex_init(&tasha->power_lock);
	INIT_WORK(&tasha->swr_add_devices_work, wcd_swr_ctrl_add_devices);
	mutex_init(&tasha->micb_lock);
	mutex_init(&tasha->swr_read_lock);
	mutex_init(&tasha->swr_write_lock);
	mutex_init(&tasha->swr_clk_lock);

	cdc_pwr = devm_kzalloc(&pdev->dev, sizeof(struct wcd9xxx_power_region),
			       GFP_KERNEL);
	if (!cdc_pwr) {
		ret = -ENOMEM;
		goto cdc_pwr_fail;
	}
	tasha->wcd9xxx->wcd9xxx_pwr[WCD9XXX_DIG_CORE_REGION_1] = cdc_pwr;
	cdc_pwr->pwr_collapse_reg_min = TASHA_DIG_CORE_REG_MIN;
	cdc_pwr->pwr_collapse_reg_max = TASHA_DIG_CORE_REG_MAX;
	wcd9xxx_set_power_state(tasha->wcd9xxx,
				WCD_REGION_POWER_COLLAPSE_REMOVE,
				WCD9XXX_DIG_CORE_REGION_1);

	if (wcd9xxx_get_intf_type() == WCD9XXX_INTERFACE_TYPE_SLIMBUS) {
		ret = snd_soc_register_codec(&pdev->dev, &soc_codec_dev_tasha,
					     tasha_dai, ARRAY_SIZE(tasha_dai));
@@ -9498,6 +9670,8 @@ unregister_codec:
	if (wcd9xxx_get_intf_type() == WCD9XXX_INTERFACE_TYPE_SLIMBUS)
		snd_soc_unregister_codec(&pdev->dev);
cdc_reg_fail:
	devm_kfree(&pdev->dev, cdc_pwr);
cdc_pwr_fail:
	devm_kfree(&pdev->dev, tasha);
	return ret;
}