/* Enable a clock with no locking, enabling parent clocks as needed. */
static int local_clk_enable_nolock(unsigned id)
{
	struct clk_local *clk = &soc_clk_local_tbl[id];
	int rc = 0;

	if (clk->type == RESET)
		return -EPERM;

	if (!clk->count) {
		rc = local_vote_sys_vdd(clk->current_freq->sys_vdd);
		if (rc)
			goto err_vdd;
		if (clk->parent != C(NONE)) {
			rc = local_clk_enable_nolock(clk->parent);
			if (rc)
				goto err_par;
		}
		rc = local_src_enable(clk->current_freq->src);
		if (rc)
			goto err_src;
		local_clk_enable_reg(id);
	}
	clk->count++;

	return rc;

err_src:
	if (clk->parent != C(NONE))
		rc = local_clk_disable_nolock(clk->parent);
err_par:
	local_unvote_sys_vdd(clk->current_freq->sys_vdd);
err_vdd:
	return rc;
}
/* Enable a clock and any related power rail. */
int local_clk_enable(unsigned id)
{
	int rc = 0;
	unsigned long flags;

	spin_lock_irqsave(&local_clock_reg_lock, flags);
	rc = local_clk_enable_nolock(id);
	spin_unlock_irqrestore(&local_clock_reg_lock, flags);

	return rc;
}
/* Enable a clock with no locking, enabling parent clocks as needed. */
static int local_clk_enable_nolock(unsigned id)
{
	struct clk_local *clk = &soc_clk_local_tbl[id];
	int rc = 0;

	if (clk->type == RESET)
		return -EPERM;

	if (!clk->count) {
		rc = local_vote_sys_vdd(clk->current_freq->sys_vdd);
		if (rc)
			goto err_vdd;
		if (clk->parent != C(NONE)) {
			rc = local_clk_enable_nolock(clk->parent);
			if (rc)
				goto err_par;
		}
		rc = local_src_enable(clk->current_freq->src);
		if (rc)
			goto err_src;
		local_clk_enable_reg(id);
		/*
		 * With remote rail control, the remote processor might modify
		 * the clock control register when the rail is enabled/disabled.
		 * Enable the rail inside the lock to protect against this.
		 */
		rc = soc_set_pwr_rail(id, 1);
		if (rc)
			goto err_pwr;
	}
	clk->count++;

	return rc;

err_pwr:
	local_clk_disable_reg(id);
err_src:
	if (clk->parent != C(NONE))
		rc = local_clk_disable_nolock(clk->parent);
err_par:
	local_unvote_sys_vdd(clk->current_freq->sys_vdd);
err_vdd:
	return rc;
}
/* Disable a clock with no locking, disabling unused parents, too. */
static int local_clk_disable_nolock(unsigned id)
{
	struct clk_local *clk = &soc_clk_local_tbl[id];
	int rc = 0;

	if (clk->count > 0)
		clk->count--;
	else {
		pr_warning("%s: Reference counts are incorrect for clock %d!\n",
			__func__, id);
		return rc;
	}

	if (clk->count == 0) {
		soc_set_pwr_rail(id, 0);
		local_clk_disable_reg(id);
		rc = local_src_disable(clk->current_freq->src);
		if (rc)
			goto err_src;
		if (clk->parent != C(NONE)) {
			rc = local_clk_disable_nolock(clk->parent);
			if (rc)
				goto err_par;
		}
		rc = local_unvote_sys_vdd(clk->current_freq->sys_vdd);
		if (rc)
			goto err_vdd;
	}

	return rc;

err_vdd:
	if (clk->parent != C(NONE))
		rc = local_clk_enable_nolock(clk->parent);
err_par:
	local_src_enable(clk->current_freq->src);
err_src:
	local_clk_enable_reg(id);
	clk->count++;

	return rc;
}
/* Enable a clock and any related power rail. */
int local_clk_enable(unsigned id)
{
	int rc = 0;
	unsigned long flags;

	spin_lock_irqsave(&local_clock_reg_lock, flags);
	rc = local_clk_enable_nolock(id);
	if (rc)
		goto unlock;
	/*
	 * With remote rail control, the remote processor might modify
	 * the clock control register when the rail is enabled/disabled.
	 * Enable the rail inside the lock to protect against this.
	 */
	rc = soc_set_pwr_rail(id, 1);
	if (rc)
		local_clk_disable_nolock(id);
unlock:
	spin_unlock_irqrestore(&local_clock_reg_lock, flags);

	return rc;
}