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

Commit 2b4eabba authored by Taniya Das's avatar Taniya Das Committed by Naveen Yadav
Browse files

cpufreq: qcom: Add support to register for Limits Management interrupt



Update the scheduler with the frequency cap due to limits management
interrupt and update back when limits throttling is removed.

Change-Id: Iebf4033757853bedbd60f83e21176ff29389aff8
Signed-off-by: default avatarTaniya Das <tdas@codeaurora.org>
parent 37d0a368
Loading
Loading
Loading
Loading
+119 −0
Original line number Diff line number Diff line
@@ -8,9 +8,11 @@
#include <linux/cpu_cooling.h>
#include <linux/energy_model.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <linux/pm_opp.h>
#include <linux/sched.h>
@@ -23,15 +25,24 @@
#define LUT_VOLT			GENMASK(11, 0)
#define LUT_ROW_SIZE			32
#define CLK_HW_DIV			2
#define EQ_IRQ_STATUS			BIT(0)
#define LT_IRQ_STATUS			BIT(1)
#define MAX_FN_SIZE			12
#define LIMITS_POLLING_DELAY_MS		10

#define CYCLE_CNTR_OFFSET(c, m, acc_count)				\
			(acc_count ? ((c - cpumask_first(m) + 1) * 4) : 0)

enum {
	REG_ENABLE,
	REG_FREQ_LUT,
	REG_VOLT_LUT,
	REG_PERF_STATE,
	REG_CYCLE_CNTR,
	REG_LLM_DCVS_VC_VOTE,
	REG_INTR_EN,
	REG_INTR_CLR,
	REG_INTR_STATUS,

	REG_ARRAY_SIZE,
};
@@ -45,6 +56,11 @@ struct cpufreq_qcom {
	struct cpufreq_frequency_table *table;
	void __iomem *base;
	cpumask_t related_cpus;
	struct delayed_work freq_poll_work;
	struct mutex dcvsh_lock;
	int dcvsh_irq;
	char dcvsh_irq_name[MAX_FN_SIZE];
	bool is_irq_enabled;
};

struct cpufreq_counter {
@@ -67,11 +83,90 @@ static const u16 cpufreq_qcom_epss_std_offsets[REG_ARRAY_SIZE] = {
	[REG_VOLT_LUT]		= 0x200,
	[REG_PERF_STATE]	= 0x320,
	[REG_CYCLE_CNTR]	= 0x3c4,
	[REG_LLM_DCVS_VC_VOTE]	= 0x024,
	[REG_INTR_EN]		= 0x304,
	[REG_INTR_CLR]		= 0x308,
	[REG_INTR_STATUS]	= 0x30C,
};

static struct cpufreq_qcom *qcom_freq_domain_map[NR_CPUS];
static struct cpufreq_counter qcom_cpufreq_counter[NR_CPUS];

static unsigned long limits_mitigation_notify(struct cpufreq_qcom *c)
{
	int i;
	u32 max_vc;

	max_vc = readl_relaxed(c->base + offsets[REG_LLM_DCVS_VC_VOTE]) &
						GENMASK(13, 8);

	for (i = 0; i < LUT_MAX_ENTRIES; i++) {
		if (c->table[i].driver_data != max_vc)
			continue;
		else {
			sched_update_cpu_freq_min_max(&c->related_cpus, 0,
					c->table[i].frequency);
			return c->table[i].frequency;
		}
	}

	return 0;
}

static void limits_dcvsh_poll(struct work_struct *work)
{
	struct cpufreq_qcom *c = container_of(work, struct cpufreq_qcom,
						freq_poll_work.work);
	struct cpufreq_policy *policy;
	unsigned long freq_limit;
	u32 regval, cpu;

	mutex_lock(&c->dcvsh_lock);

	cpu = cpumask_first(&c->related_cpus);
	policy = cpufreq_cpu_get_raw(cpu);

	freq_limit = limits_mitigation_notify(c);
	if (freq_limit != policy->cpuinfo.max_freq || !freq_limit) {
		mod_delayed_work(system_highpri_wq, &c->freq_poll_work,
				msecs_to_jiffies(LIMITS_POLLING_DELAY_MS));
	} else {
		regval = readl_relaxed(c->base + offsets[REG_INTR_CLR]);
		regval &= ~LT_IRQ_STATUS;
		writel_relaxed(regval, c->base + offsets[REG_INTR_CLR]);

		c->is_irq_enabled = true;
		enable_irq(c->dcvsh_irq);
	}

	mutex_unlock(&c->dcvsh_lock);
}

static irqreturn_t dcvsh_handle_isr(int irq, void *data)
{
	struct cpufreq_qcom *c = data;
	u32 regval;

	regval = readl_relaxed(c->base + offsets[REG_INTR_STATUS]);
	if (!(regval & LT_IRQ_STATUS))
		return IRQ_HANDLED;

	mutex_lock(&c->dcvsh_lock);

	if (c->is_irq_enabled) {
		c->is_irq_enabled = false;
		disable_irq_nosync(c->dcvsh_irq);
		limits_mitigation_notify(c);
		mod_delayed_work(system_highpri_wq, &c->freq_poll_work,
				msecs_to_jiffies(LIMITS_POLLING_DELAY_MS));

	}

	mutex_unlock(&c->dcvsh_lock);

	return IRQ_HANDLED;
}

static u64 qcom_cpufreq_get_cpu_cycle_counter(int cpu)
{
	struct cpufreq_counter *cpu_counter;
@@ -184,6 +279,21 @@ static int qcom_cpufreq_hw_cpu_init(struct cpufreq_policy *policy)

	dev_pm_opp_of_register_em(policy->cpus);

	if (c->dcvsh_irq > 0) {
		snprintf(c->dcvsh_irq_name, sizeof(c->dcvsh_irq_name),
					"dcvsh-irq-%d", policy->cpu);
		ret = devm_request_threaded_irq(cpu_dev, c->dcvsh_irq, NULL,
			dcvsh_handle_isr, IRQF_TRIGGER_HIGH | IRQF_ONESHOT |
			IRQF_NO_SUSPEND | IRQF_SHARED, c->dcvsh_irq_name, c);
		if (ret) {
			dev_err(cpu_dev, "Failed to register irq %d\n", ret);
			return ret;
		}

		c->is_irq_enabled = true;
		writel_relaxed(LT_IRQ_STATUS, c->base + offsets[REG_INTR_EN]);
	}

	return 0;
}

@@ -374,6 +484,15 @@ static int qcom_cpu_resources_init(struct platform_device *pdev,
		return ret;
	}

	if (of_find_property(dev->of_node, "interrupts", NULL)) {
		c->dcvsh_irq = of_irq_get(dev->of_node, index);
		if (c->dcvsh_irq > 0) {
			mutex_init(&c->dcvsh_lock);
			INIT_DEFERRABLE_WORK(&c->freq_poll_work,
					limits_dcvsh_poll);
		}
	}

	for_each_cpu(cpu_r, &c->related_cpus)
		qcom_freq_domain_map[cpu_r] = c;