lpm-levels: Add RIMPS timer base rail/PLL turn off functionality

This patch adds the logic to enable the RIMPS timer based
rail-pc trigger functionality. This patch programs the RIMPS
timer when each core of corresponding clock domain entering
into the rail-pc LPM and enables the RIMPS timer feature so
that RIMPS allows to turn off rail/PLL based on the timer
threshold and timeout values.

Change-Id: Ib3589933d339a7224147a7c7afc006f260d8205b
Signed-off-by: Raghavendra Kakarla <rkakarla@codeaurora.org>
This commit is contained in:
Raghavendra Kakarla 2020-06-22 15:57:48 +05:30 • committed by Gerrit - the friendly Code Review server
commit 602ce57915
3 changed files with 126 additions and 1 deletions

View file

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2014-2019, The Linux Foundation. All rights reserved.
* Copyright (c) 2014-2020, The Linux Foundation. All rights reserved.
*/
#define pr_fmt(fmt) "%s: " fmt, KBUILD_MODNAME
@ -10,6 +10,7 @@
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/err.h>
#include <linux/sysfs.h>
#include <linux/device.h>
@ -542,6 +543,7 @@ static int parse_cpu_levels(struct device_node *dn, struct lpm_cluster *c)
if (!cpu)
return -ENOMEM;
spin_lock_init(&cpu->cpu_lock);
if (get_cpumask_for_node(dn, &cpu->related_cpus))
return -EINVAL;
@ -689,6 +691,37 @@ failed_parse_cluster:
return NULL;
}
static void add_rimps_tmr_register(struct device_node *dn,
struct lpm_cluster *cl)
{
struct lpm_cpu *lpm_cpu;
uint32_t rimps_threshold = 0;
static uint32_t i;
if (list_empty(&cl->cpu)) {
struct lpm_cluster *n;
list_for_each_entry(n, &cl->child, list)
add_rimps_tmr_register(dn, n);
}
list_for_each_entry(lpm_cpu, &cl->cpu, list) {
int idx = lpm_cpu->nlevels-1;
lpm_cpu->rimps_tmr_base = of_iomap(dn, i++);
if (!lpm_cpu->rimps_tmr_base) {
pr_debug("Unable to get rimps base address\n");
return;
}
rimps_threshold = lpm_cpu->levels[idx].pwr.min_residency;
rimps_threshold = us_to_ticks(rimps_threshold);
writel_relaxed(rimps_threshold, lpm_cpu->rimps_tmr_base
+ TIMER_THRESHOLD);
/* Ensure the write is complete before returning. */
wmb();
}
}
struct lpm_cluster *lpm_of_parse_cluster(struct platform_device *pdev)
{
struct device_node *top = NULL;
@ -702,6 +735,7 @@ struct lpm_cluster *lpm_of_parse_cluster(struct platform_device *pdev)
lpm_pdev = pdev;
c = parse_cluster(top, NULL);
add_rimps_tmr_register(pdev->dev.of_node, c);
of_node_put(top);
return c;
}

View file

@ -223,15 +223,88 @@ static void update_debug_pc_event(enum debug_event event, uint32_t arg1,
spin_unlock(&debug_lock);
}
uint32_t us_to_ticks(uint64_t sleep_val)
{
uint64_t sec, nsec;
sec = sleep_val;
do_div(sec, USEC_PER_SEC);
if (sec > 0) {
nsec = sleep_val - sec * USEC_PER_SEC;
sleep_val = sec * ARCH_TIMER_HZ;
if (nsec > 0) {
nsec = nsec * NSEC_PER_USEC;
do_div(nsec, NSEC_PER_SEC/ARCH_TIMER_HZ);
}
sleep_val = sleep_val + nsec;
} else {
sleep_val = sleep_val * ARCH_TIMER_HZ;
do_div(sleep_val, USEC_PER_SEC);
}
return sleep_val;
}
static uint32_t get_next_event(struct lpm_cpu *cpu)
{
ktime_t next_event = KTIME_MAX;
unsigned int next_cpu;
for_each_cpu(next_cpu, &cpu->related_cpus) {
ktime_t next_event_c = per_cpu(cpu_lpm, next_cpu)->next_hrtimer;
if (next_event > next_event_c)
next_event = next_event_c;
}
return ktime_to_us(ktime_sub(next_event, ktime_get()));
}
static void program_rimps_timer(struct lpm_cpu *cpu)
{
uint32_t ctrl_val, next_event;
struct cpumask cpu_lpm_mask;
struct lpm_cluster *cl = cpu->parent;
if (!cpu->rimps_tmr_base)
return;
cpumask_and(&cpu_lpm_mask, &cl->num_children_in_sync,
&cpu->related_cpus);
if (!cpumask_equal(&cpu_lpm_mask, &cpu->related_cpus))
return;
next_event = get_next_event(cpu);
if (!next_event)
return;
next_event = us_to_ticks(next_event);
spin_lock(&cpu->cpu_lock);
/* RIMPS timer pending should be read before programming timeout val */
readl_relaxed(cpu->rimps_tmr_base + TIMER_PENDING);
ctrl_val = readl_relaxed(cpu->rimps_tmr_base + TIMER_CTRL);
writel_relaxed(ctrl_val & ~(TIMER_CONTROL_EN),
cpu->rimps_tmr_base + TIMER_CTRL);
writel_relaxed(next_event, cpu->rimps_tmr_base + TIMER_VAL);
writel_relaxed(ctrl_val | (TIMER_CONTROL_EN),
cpu->rimps_tmr_base + TIMER_CTRL);
/* Ensure the write is complete before returning. */
wmb();
spin_unlock(&cpu->cpu_lock);
}
#ifdef CONFIG_SMP
static int lpm_dying_cpu(unsigned int cpu)
{
struct lpm_cluster *cluster = per_cpu(cpu_lpm, cpu)->parent;
struct lpm_cpu *lpm_cpu = per_cpu(cpu_lpm, cpu);
update_debug_pc_event(CPU_HP_DYING, cpu,
cluster->num_children_in_sync.bits[0],
cluster->child_cpus.bits[0], false);
cluster_prepare(cluster, get_cpu_mask(cpu), NR_LPM_LEVELS, false, 0);
program_rimps_timer(lpm_cpu);
return 0;
}
@ -1369,6 +1442,9 @@ static int lpm_cpuidle_enter(struct cpuidle_device *dev,
if (need_resched())
goto exit;
if (idx == cpu->nlevels - 1)
program_rimps_timer(cpu);
ret = psci_enter_sleep(cpu, idx, true);
success = (ret == 0);

View file

@ -24,6 +24,18 @@
#define PREMATURE_CNT_LOW 1
#define PREMATURE_CNT_HIGH 5
/* RIMPS registers */
#define TIMER_CTRL 0x0
#define TIMER_VAL 0x4
#define TIMER_PENDING 0x18
#define TIMER_THRESHOLD 0x1C
/* RIMPS registers offset */
#define TIMER_CONTROL_EN 0x1
/* RIMPS timer clock */
#define ARCH_TIMER_HZ 19200000
struct power_params {
uint32_t entry_latency; /* Entry latency */
uint32_t exit_latency; /* Exit latency */
@ -50,6 +62,8 @@ struct lpm_cpu {
uint32_t ref_premature_cnt;
uint32_t tmr_add;
bool lpm_prediction;
void __iomem *rimps_tmr_base;
spinlock_t cpu_lock;
bool ipi_prediction;
uint64_t bias;
struct cpuidle_driver *drv;
@ -122,6 +136,7 @@ struct lpm_cluster {
struct lpm_cluster *lpm_of_parse_cluster(struct platform_device *pdev);
void free_cluster_node(struct lpm_cluster *cluster);
void cluster_dt_walkthrough(struct lpm_cluster *cluster);
uint32_t us_to_ticks(uint64_t sleep_val);
int create_cluster_lvl_nodes(struct lpm_cluster *p, struct kobject *kobj);
int lpm_cpu_mode_allow(unsigned int cpu,