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

Commit dab4ff1a authored by Vikram Mulukutla's avatar Vikram Mulukutla
Browse files

clk: msm: clock-cpu-8996: Add support for a droop detection mechanism



The CPU clock subsystem on the MSM8996 V3 has a droop detection
mechanism. Add support for it - this involves some initial
configuration, and using the droop-detection-enabled mux
selection instead of the primary PLL.

This also requires us to switch the GFMUX across DCVS changes
(both voltage and frequency). Furthermore, we disallow L2 GDHS
across DCVS by modifying the pm_qos latency request accordingly.

Change-Id: Icb455437c5251b057032b583ea145733cef5d8bc
Signed-off-by: default avatarVikram Mulukutla <markivx@codeaurora.org>
parent 0dab3364
Loading
Loading
Loading
Loading
+304 −18
Original line number Diff line number Diff line
@@ -28,10 +28,13 @@
#include <linux/pm_opp.h>
#include <linux/pm_qos.h>

#include <asm/cputype.h>

#include <soc/qcom/scm.h>
#include <soc/qcom/clock-pll.h>
#include <soc/qcom/clock-local2.h>
#include <soc/qcom/clock-alpha-pll.h>
#include <soc/qcom/kryo-l2-accessors.h>

#include <dt-bindings/clock/msm-clocks-8996.h>

@@ -124,6 +127,41 @@ DEFINE_CLK_DUMMY(alpha_xo_ao, 19200000);
DEFINE_EXT_CLK(sys_apcsaux_clk_gcc, NULL);
DEFINE_FIXED_SLAVE_DIV_CLK(sys_apcsaux_clk, 2, &sys_apcsaux_clk_gcc.c);

#define L2ACDCR_REG 0x580ULL
#define L2ACDTD_REG 0x581ULL
#define L2ACDDVMRC_REG 0x584ULL
#define L2ACDSSCR_REG 0x589ULL

/* ACD static settings */
static int acdtd_val_pwrcl = 0x00006A11;
static int acdtd_val_perfcl = 0x00006A11;
static int dvmrc_val = 0x000E0F0F;
static int acdsscr_val = 0x00000601;
static int acdcr_val_pwrcl = 0x002D5FFD;
module_param(acdcr_val_pwrcl, int, 0444);
static int acdcr_val_perfcl = 0x002D5FFD;
module_param(acdcr_val_perfcl, int, 0444);
int enable_acd;
module_param(enable_acd, int, 0444);

#define WRITE_L2ACDCR(val) \
	set_l2_indirect_reg(L2ACDCR_REG, (val))
#define WRITE_L2ACDTD(val) \
	set_l2_indirect_reg(L2ACDTD_REG, (val))
#define WRITE_L2ACDDVMRC(val) \
	set_l2_indirect_reg(L2ACDDVMRC_REG, (val))
#define WRITE_L2ACDSSCR(val) \
	set_l2_indirect_reg(L2ACDSSCR_REG, (val))

#define READ_L2ACDCR(reg) \
	(reg = get_l2_indirect_reg(L2ACDCR_REG))
#define READ_L2ACDTD(reg) \
	(reg = get_l2_indirect_reg(L2ACDTD_REG))
#define READ_L2ACDDVMRC(reg) \
	(reg = get_l2_indirect_reg(L2ACDDVMRC_REG))
#define READ_L2ACDSSCR(reg) \
	(reg = get_l2_indirect_reg(L2ACDSSCR_REG))

static struct pll_clk perfcl_pll = {
	.mode_reg = (void __iomem *)C1_PLL_MODE,
	.l_reg = (void __iomem *)C1_PLL_L_VAL,
@@ -408,6 +446,7 @@ static struct mux_clk pwrcl_lf_mux = {
static struct mux_clk pwrcl_hf_mux = {
	.offset = MUX_OFFSET,
	MUX_SRC_LIST(
		/* This may be changed by acd_init to select the ACD leg */
		{ &pwrcl_pll.c,     1 },
		{ &pwrcl_lf_mux.c,  0 },
	),
@@ -444,6 +483,7 @@ static struct mux_clk perfcl_lf_mux = {
static struct mux_clk perfcl_hf_mux = {
	.offset = MUX_OFFSET,
	MUX_SRC_LIST(
		/* This may be changed by acd_init to select the ACD leg */
		{ &perfcl_pll.c,     1 },
		{ &perfcl_lf_mux.c,  0 },
	),
@@ -487,6 +527,7 @@ struct cpu_clk_8996 {
	cpumask_t cpumask;
	struct pm_qos_request req;
	bool do_half_rate;
	bool has_acd;
};

static inline struct cpu_clk_8996 *to_cpu_clk_8996(struct clk *c)
@@ -520,14 +561,60 @@ static unsigned long alt_pll_perfcl_freqs[] = {

static void do_nothing(void *unused) { }

static int cpu_clk_8996_set_rate(struct clk *c, unsigned long rate)
/* ACD programming */
static struct cpu_clk_8996 perfcl_clk;
static struct cpu_clk_8996 pwrcl_clk;

static void cpu_clock_8996_acd_init(void);

static void cpu_clk_8996_disable(struct clk *c)
{
	struct cpu_clk_8996 *cpuclk = to_cpu_clk_8996(c);
	int ret, err_ret, i;
	unsigned long alt_pll_prev_rate;
	unsigned long alt_pll_rate;
	unsigned long n_alt_freqs = cpuclk->n_alt_pll_freqs;

	if (!enable_acd)
		return;

	/* Ensure that we switch to GPLL0 across D5 */
	if (cpuclk == &pwrcl_clk)
		writel_relaxed(0x3C, vbases[APC0_BASE] + MUX_OFFSET);
	else if (cpuclk == &perfcl_clk)
		writel_relaxed(0x3C, vbases[APC1_BASE] + MUX_OFFSET);
}

#define MAX_PLL_MAIN_FREQ 595200000

/*
 * Returns the max safe frequency that will guarantee we switch to main output
 */
unsigned long acd_safe_freq(unsigned long freq)
{
	/*
	 * If we're running at less than double the max PLL main rate,
	 * just return half the rate. This will ensure we switch to
	 * the main output, without violating voltage constraints
	 * that might happen if we choose to go with MAX_PLL_MAIN_FREQ.
	 */
	if (freq > MAX_PLL_MAIN_FREQ && freq <= MAX_PLL_MAIN_FREQ*2)
		return freq/2;

	/*
	 * We're higher than the max main output, and higher than twice
	 * the max main output. Safe to go to the max main output.
	 */
	if (freq > MAX_PLL_MAIN_FREQ)
		return MAX_PLL_MAIN_FREQ;

	/* Shouldn't get here, just return the safest rate possible */
	return sys_apcsaux_clk.c.rate;
}

static int cpu_clk_8996_pre_set_rate(struct clk *c, unsigned long rate)
{
	struct cpu_clk_8996 *cpuclk = to_cpu_clk_8996(c);
	int ret;
	bool hw_low_power_ctrl = cpuclk->hw_low_power_ctrl;
	bool on_acd_leg = c->rate > MAX_PLL_MAIN_FREQ;
	bool increase_freq = rate > c->rate;

	/*
	 * If hardware control of the clock tree is enabled during power
@@ -546,6 +633,46 @@ static int cpu_clk_8996_set_rate(struct clk *c, unsigned long rate)
						NULL, 1);
	}

	/* Select a non-ACD'ed safe source across voltage and freq switches */
	if (enable_acd && cpuclk->has_acd && on_acd_leg && increase_freq) {
		/*
		 * We're on the ACD leg, and the voltage will be
		 * scaled before clk_set_rate. Switch to the main output.
		 */
		return clk_set_rate(c->parent, acd_safe_freq(c->rate));
	}

	return 0;
}

static void cpu_clk_8996_post_set_rate(struct clk *c, unsigned long start_rate)
{
	struct cpu_clk_8996 *cpuclk = to_cpu_clk_8996(c);
	int ret;
	bool hw_low_power_ctrl = cpuclk->hw_low_power_ctrl;
	bool on_acd_leg = c->rate > MAX_PLL_MAIN_FREQ;
	bool decrease_freq = start_rate > c->rate;

	if (cpuclk->has_acd && enable_acd && on_acd_leg && decrease_freq) {
		ret = clk_set_rate(c->parent, c->rate);
		if (ret)
			pr_err("Unable to reset parent rate!\n");
	}

	if (hw_low_power_ctrl)
		pm_qos_remove_request(&cpuclk->req);
}

static int cpu_clk_8996_set_rate(struct clk *c, unsigned long rate)
{
	struct cpu_clk_8996 *cpuclk = to_cpu_clk_8996(c);
	int ret, err_ret, i;
	unsigned long alt_pll_prev_rate;
	unsigned long alt_pll_rate;
	unsigned long n_alt_freqs = cpuclk->n_alt_pll_freqs;
	bool on_acd_leg = rate > MAX_PLL_MAIN_FREQ;
	bool decrease_freq = rate < c->rate;

	if (cpuclk->alt_pll && (n_alt_freqs > 0)) {
		alt_pll_prev_rate = cpuclk->alt_pll->rate;
		alt_pll_rate = cpuclk->alt_pll_freqs[0];
@@ -604,8 +731,12 @@ static int cpu_clk_8996_set_rate(struct clk *c, unsigned long rate)
		goto set_rate_fail;
	}

	if (hw_low_power_ctrl)
		pm_qos_remove_request(&cpuclk->req);
	/*
	 * If we're on the ACD leg and decreasing freq, voltage will be changed
	 * after this function. Switch to main output.
	 */
	if (enable_acd && cpuclk->has_acd && decrease_freq && on_acd_leg)
		return clk_set_rate(c->parent, acd_safe_freq(c->rate));

	return 0;

@@ -634,22 +765,22 @@ fail:
				alt_pll_prev_rate, rate, c->dbg_name, err_ret);
	}
out:
	if (hw_low_power_ctrl)
		pm_qos_remove_request(&cpuclk->req);

	return ret;
}

static struct clk_ops clk_ops_cpu_8996 = {
	.disable = cpu_clk_8996_disable,
	.set_rate = cpu_clk_8996_set_rate,
	.pre_set_rate = cpu_clk_8996_pre_set_rate,
	.post_set_rate = cpu_clk_8996_post_set_rate,
	.round_rate = cpu_clk_8996_round_rate,
	.handoff = cpu_clk_8996_handoff,
};

DEFINE_VDD_REGS_INIT(vdd_pwrcl, 1);

#define PERFCL_LATENCY_NO_L2_PC_US (800 - 1)
#define PWRCL_LATENCY_NO_L2_PC_US (700 - 1)
#define PERFCL_LATENCY_NO_L2_PC_US (1)
#define PWRCL_LATENCY_NO_L2_PC_US (1)

static struct cpu_clk_8996 pwrcl_clk = {
	.cpu_reg_mask = 0x3,
@@ -685,6 +816,77 @@ static struct cpu_clk_8996 perfcl_clk = {
	},
};

static DEFINE_SPINLOCK(acd_lock);

static struct clk *mpidr_to_clk(void)
{
	u64 hwid = read_cpuid_mpidr() & 0xFFF;

	if ((hwid | pwrcl_clk.cpu_reg_mask) == pwrcl_clk.cpu_reg_mask)
		return &pwrcl_clk.c;
	if ((hwid | perfcl_clk.cpu_reg_mask) == perfcl_clk.cpu_reg_mask)
		return &perfcl_clk.c;

	return NULL;
}

#define SSSCTL_OFFSET 0x160

/*
 * This *has* to be called on the intended CPU
 */
static void cpu_clock_8996_acd_init(void)
{
	u64 l2acdtd;
	unsigned long flags;

	spin_lock_irqsave(&acd_lock, flags);

	if (!enable_acd) {
		spin_unlock_irqrestore(&acd_lock, flags);
		return;
	}

	READ_L2ACDTD(l2acdtd);

	/* If we have init'ed and the config is still present, return */
	if (mpidr_to_clk() == &pwrcl_clk.c && l2acdtd == acdtd_val_pwrcl) {
		spin_unlock_irqrestore(&acd_lock, flags);
		return;
	} else if (mpidr_to_clk() == &pwrcl_clk.c) {
		WRITE_L2ACDTD(acdtd_val_pwrcl);
	}

	if (mpidr_to_clk() == &perfcl_clk.c && l2acdtd == acdtd_val_perfcl) {
		spin_unlock_irqrestore(&acd_lock, flags);
		return;
	} else if (mpidr_to_clk() == &perfcl_clk.c) {
		WRITE_L2ACDTD(acdtd_val_perfcl);
	}

	/* Initial ACD for *this* cluster */
	WRITE_L2ACDDVMRC(dvmrc_val);
	WRITE_L2ACDSSCR(acdsscr_val);

	if (mpidr_to_clk() == &pwrcl_clk.c) {
		if (vbases[APC0_BASE])
			writel_relaxed(0x0000000F, vbases[APC0_BASE] +
				       SSSCTL_OFFSET);
		/* Ensure SSSCTL config goes through before enabling ACD. */
		mb();
		WRITE_L2ACDCR(acdcr_val_pwrcl);
	} else {
		WRITE_L2ACDCR(acdcr_val_perfcl);
		if (vbases[APC1_BASE])
			writel_relaxed(0x0000000F, vbases[APC1_BASE] +
				       SSSCTL_OFFSET);
		/* Ensure SSSCTL config goes through before enabling ACD. */
		mb();
	}

	spin_unlock_irqrestore(&acd_lock, flags);
}

static struct clk *logical_cpu_to_clk(int cpu)
{
	struct device_node *cpu_node;
@@ -1099,6 +1301,17 @@ static int cpu_clock_8996_driver_probe(struct platform_device *pdev)
	pwrcl_clk.hw_low_power_ctrl = true;
	perfcl_clk.hw_low_power_ctrl = true;

	/* If we mapped in APC bases for ACD, unmap them here. */
	if (vbases[APC0_BASE]) {
		iounmap(vbases[APC0_BASE]);
		vbases[APC0_BASE] = NULL;
	}

	if (vbases[APC1_BASE]) {
		iounmap(vbases[APC1_BASE]);
		vbases[APC1_BASE] = NULL;
	}

	ret = cpu_clock_8996_resources_init(pdev);
	if (ret) {
		dev_err(&pdev->dev, "resources init failed\n");
@@ -1202,6 +1415,10 @@ static int cpu_clock_8996_driver_probe(struct platform_device *pdev)
	clk_prepare_enable(&pwrcl_alt_pll.c);
	clk_prepare_enable(&cbf_pll.c);

	/* Set the early boot rate. This may also switch us to the ACD leg */
	clk_set_rate(&pwrcl_clk.c, pwrcl_early_boot_rate);
	clk_set_rate(&perfcl_clk.c, perfcl_early_boot_rate);

	populate_opp_table(pdev);

	put_online_cpus();
@@ -1243,6 +1460,7 @@ module_exit(cpu_clock_8996_exit);
#define AUX_BASE_PHY 0x09820050

#define CLK_CTL_OFFSET 0x44
#define PSCTL_OFFSET 0x164
#define AUTO_CLK_SEL_BIT BIT(8)
#define CBF_AUTO_CLK_SEL_BIT BIT(6)
#define AUTO_CLK_SEL_ALWAYS_ON_MASK BM(5, 4)
@@ -1258,6 +1476,30 @@ module_exit(cpu_clock_8996_exit);
static int use_alt_pll;
module_param(use_alt_pll, int, 0444);

static int clock_cpu_8996_cpu_callback(struct notifier_block *nfb,
		unsigned long action, void *hcpu)
{
	if (!enable_acd)
		return NOTIFY_OK;

	switch (action & ~CPU_TASKS_FROZEN) {

	case CPU_STARTING:
		/* This is needed for the first time that CPUs come up */
		cpu_clock_8996_acd_init();
		break;

	default:
		break;
	}

	return NOTIFY_OK;
}

static struct notifier_block __refdata clock_cpu_8996_cpu_notifier = {
	.notifier_call = clock_cpu_8996_cpu_callback,
};

int __init cpu_clock_8996_early_init(void)
{
	int ret = 0;
@@ -1496,10 +1738,6 @@ int __init cpu_clock_8996_early_init(void)
		/* Switch the clusters to use the alternate PLLs */
		writel_relaxed(0x33, vbases[APC0_BASE] + MUX_OFFSET);
		writel_relaxed(0x33, vbases[APC1_BASE] + MUX_OFFSET);
	} else {
		/* Switch the clusters to use the primary PLLs */
		writel_relaxed(0x31, vbases[APC0_BASE] + MUX_OFFSET);
		writel_relaxed(0x31, vbases[APC1_BASE] + MUX_OFFSET);
	}

	/* Switch the CBF to use the primary PLL */
@@ -1508,6 +1746,48 @@ int __init cpu_clock_8996_early_init(void)
	regval |= 0x1;
	writel_relaxed(regval, vbases[CBF_BASE] + CBF_MUX_OFFSET);

	if (!cpu_clocks_v3)
		enable_acd = 0;

	if (enable_acd) {
		int i;

		if (use_alt_pll)
			panic("Can't enable ACD on the the alternate PLL\n");

		perfcl_clk.has_acd = true;
		pwrcl_clk.has_acd = true;

		/* Enable ACD on this cluster if necessary */
		cpu_clock_8996_acd_init();

		/* Ensure we never use the non-ACD leg of the GFMUX */
		for (i = 0; i < pwrcl_hf_mux.num_parents; i++)
			if (pwrcl_hf_mux.parents[i].src == &pwrcl_pll.c)
				pwrcl_hf_mux.parents[i].sel = 2;

		for (i = 0; i < perfcl_hf_mux.num_parents; i++)
			if (perfcl_hf_mux.parents[i].src == &perfcl_pll.c)
				perfcl_hf_mux.parents[i].sel = 2;

		BUG_ON(register_hotcpu_notifier(&clock_cpu_8996_cpu_notifier));

		/* Pulse swallower and soft-start settings */
		writel_relaxed(0x00030005, vbases[APC0_BASE] + PSCTL_OFFSET);
		writel_relaxed(0x00030005, vbases[APC1_BASE] + PSCTL_OFFSET);

		/* Ensure all config above goes through before the ACD switch */
		mb();

		/* Switch the clusters to use the ACD leg */
		writel_relaxed(0x32, vbases[APC0_BASE] + MUX_OFFSET);
		writel_relaxed(0x32, vbases[APC1_BASE] + MUX_OFFSET);
	} else {
		/* Switch the clusters to use the primary PLLs */
		writel_relaxed(0x31, vbases[APC0_BASE] + MUX_OFFSET);
		writel_relaxed(0x31, vbases[APC1_BASE] + MUX_OFFSET);
	}

	/*
	 * One time print during boot - this is the earliest time
	 * that Linux configures the CPU clocks. It's critical for
@@ -1522,9 +1802,15 @@ auxbase_fail:
cbf_pll_map_fail:
	iounmap(vbases[CBF_BASE]);
cbf_map_fail:
	if (ret) {
		iounmap(vbases[APC1_BASE]);
		vbases[APC1_BASE] = NULL;
	}
apc1_fail:
	if (ret) {
		iounmap(vbases[APC0_BASE]);
		vbases[APC0_BASE] = NULL;
	}
fail:
	return ret;
}