Jetpack/kernel/nvidia/drivers/cpufreq/tegra194_cpufreq.c

1183 lines
28 KiB
C
Raw Normal View History

/*
* Copyright (c) 2017-2020, NVIDIA CORPORATION. All rights reserved.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <asm/cputype.h>
#include <asm/smp_plat.h>
#include <asm/cpu.h>
#include <linux/io.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <linux/uaccess.h>
#include <linux/cpufreq.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <soc/tegra/tegra_bpmp.h>
#include <soc/tegra/bpmp_abi.h>
#include <soc/tegra/chip-id.h>
#include <linux/delay.h>
#include <linux/pstore.h>
#include <linux/ptrace.h>
#include <linux/platform_device.h>
#include <linux/platform/tegra/emc_bwmgr.h>
#include <linux/tegra-mce.h>
#include <linux/version.h>
#include <linux/pm_qos.h>
#include <linux/workqueue.h>
#include <linux/tegra-cpufreq.h>
#include "cpufreq_cpu_emc_table.h"
/* cpufreq transisition latency */
#define TEGRA_CPUFREQ_TRANSITION_LATENCY (300 * 1000) /* unit in nanoseconds */
#define CC3_NDIV_WIDTH 9 /* the number of bits for ndiv for auto-cc3 */
#define KHZ_TO_HZ 1000
#define REF_CLK_MHZ 408 /* 408 MHz */
#define US_DELAY 500
#define US_DELAY_MIN 2
#define CPUFREQ_TBL_STEP_HZ (50 * KHZ_TO_HZ * KHZ_TO_HZ)
#define LOOP_FOR_EACH_CLUSTER(cl) for (cl = 0; \
cl < MAX_CLUSTERS; cl++)
static struct cpu_emc_mapping *cpu_emc_map_ptr;
static uint8_t tegra_hypervisor_mode;
static int cpufreq_single_policy;
static enum cpuhp_state hp_online;
enum cluster {
CLUSTER0,
CLUSTER1,
CLUSTER2,
CLUSTER3,
MAX_CLUSTERS,
};
struct cc3_params {
u32 ndiv;
u32 freq;
bool enable;
};
struct per_cluster_data {
struct cpufreq_frequency_table *clft;
struct mrq_cpu_ndiv_limits_response ndiv_limits_tbl;
struct tegra_bwmgr_client *bwmgr;
struct cpumask cpu_mask;
struct cc3_params cc3;
uint8_t configured;
};
struct tegra_cpufreq_data {
struct per_cluster_data pcluster[MAX_CLUSTERS];
struct mutex mlock; /* lock protecting cc3 params */
uint32_t freq_compute_delay; /* delay in reading clock counters */
};
static struct tegra_cpufreq_data tfreq_data;
struct tegra_cpu_ctr {
uint32_t cpu;
uint32_t delay;
uint32_t coreclk_cnt, last_coreclk_cnt;
uint32_t refclk_cnt, last_refclk_cnt;
};
static struct workqueue_struct *read_counters_wq;
struct read_counters_work {
struct work_struct work;
struct tegra_cpu_ctr c;
};
static enum cluster get_cpu_cluster(uint8_t cpu)
{
return MPIDR_AFFINITY_LEVEL(cpu_logical_map(cpu), 1);
}
static uint64_t read_freq_feedback(void)
{
uint64_t val;
asm volatile("mrs %0, s3_0_c15_c0_5" : "=r" (val) : );
return val;
}
static inline uint16_t clamp_ndiv(struct mrq_cpu_ndiv_limits_response *nltbl,
uint16_t ndiv)
{
uint16_t min = nltbl->ndiv_min;
uint16_t max = nltbl->ndiv_max;
if (!ndiv || (ndiv < min))
ndiv = min;
if (ndiv > max)
ndiv = max;
return ndiv;
}
static inline uint16_t map_freq_to_ndiv(struct mrq_cpu_ndiv_limits_response
*nltbl, uint32_t freq)
{
return DIV_ROUND_UP(freq * nltbl->pdiv * nltbl->mdiv,
nltbl->ref_clk_hz / KHZ_TO_HZ);
}
static inline uint32_t map_ndiv_to_freq(struct mrq_cpu_ndiv_limits_response
*nltbl, uint16_t ndiv)
{
return nltbl->ref_clk_hz / KHZ_TO_HZ * ndiv /
(nltbl->pdiv * nltbl->mdiv);
}
static void tegra_read_counters(struct work_struct *work)
{
uint64_t val = 0;
struct tegra_cpu_ctr *c;
struct read_counters_work *read_counters_work;
/*
* ref_clk_counter(32 bit counter) runs from constant clk,
* pll_p(408MHz).
* It will take = 2 ^ 32 / 408 MHz to overflow ref clk counter
* = 10526880 usec = 10527 msec to overflow
*
* Like wise core_clk_counter(32 bit counter) runs from
* crab_clk(ctu_clk). ctu_clk, runs at full freq of cluster,
* Assuming max cluster clock ~2000MHz
* It will take = 2 ^ 32 / 2000 MHz to overflow core clk counter
* = 2 sec to overflow
*/
read_counters_work = container_of(work, struct read_counters_work,
work);
c = &read_counters_work->c;
val = read_freq_feedback();
c->last_refclk_cnt = (uint32_t)(val & 0xffffffff);
c->last_coreclk_cnt = (uint32_t) (val >> 32);
udelay(c->delay);
val = read_freq_feedback();
c->refclk_cnt = (uint32_t)(val & 0xffffffff);
c->coreclk_cnt = (uint32_t) (val >> 32);
}
/**
* Return instantaneous cpu speed
* Instantaneous freq is calculated as -
* -Takes sample on every query of getting the freq.
* - Read core and ref clock counters;
* - Delay for X us
* - Read above cycle counters again
* - Calculates freq by subtracting current and previous counters
* divided by the delay time or eqv. of ref_clk_counter in delta time
* - Return Kcycles/second, freq in KHz
*
* - delta time period = x sec
* = delta ref_clk_counter / (408 * 10^6) sec
* freq in Hz = cycles/sec
* = (delta cycles / x sec
* = (delta cycles * 408 * 10^6) / delta ref_clk_counter
* in KHz = (delta cycles * 408 * 10^3) / delta ref_clk_counter
*
* @cpu - logical cpu whose freq to be updated
* Returns freq in KHz on success, 0 if cpu is offline
*/
static unsigned int tegra194_get_speed_common(uint32_t cpu, uint32_t delay)
{
uint32_t delta_ccnt = 0;
uint32_t delta_refcnt = 0;
unsigned long rate_mhz = 0;
struct tegra_cpu_ctr c;
struct read_counters_work read_counters_work;
read_counters_work.c.cpu = cpu;
read_counters_work.c.delay = delay;
INIT_WORK_ONSTACK(&read_counters_work.work, tegra_read_counters);
queue_work_on(cpu, read_counters_wq, &read_counters_work.work);
flush_work(&read_counters_work.work);
c = read_counters_work.c;
delta_ccnt = c.coreclk_cnt - c.last_coreclk_cnt;
if (!delta_ccnt)
goto err_out;
/* ref clock is 32 bits */
delta_refcnt = c.refclk_cnt - c.last_refclk_cnt;
if (!delta_refcnt) {
pr_err("cpufreq: %d is idle, delta_refcnt: 0\n", cpu);
goto err_out;
}
rate_mhz = ((unsigned long) delta_ccnt * REF_CLK_MHZ) / delta_refcnt;
err_out:
return (unsigned int) (rate_mhz * 1000); /* in KHz */
}
static unsigned int tegra194_get_speed(uint32_t cpu)
{
return tegra194_get_speed_common(cpu, tfreq_data.freq_compute_delay);
}
static unsigned int tegra194_fast_get_speed(uint32_t cpu)
{
return tegra194_get_speed_common(cpu, US_DELAY_MIN);
}
/* Set emc clock by referring cpu_to_emc freq mapping */
static void set_cpufreq_to_emcfreq(enum cluster cl, uint32_t cluster_freq)
{
unsigned long emc_freq;
emc_freq = tegra_cpu_to_emc_freq(cluster_freq, cpu_emc_map_ptr);
tegra_bwmgr_set_emc(tfreq_data.pcluster[cl].bwmgr,
emc_freq * KHZ_TO_HZ, TEGRA_BWMGR_SET_EMC_FLOOR);
pr_debug("cluster %d, emc freq(KHz): %lu cluster_freq(KHz): %u\n",
cl, emc_freq, cluster_freq);
}
static struct cpufreq_frequency_table *get_freqtable(uint8_t cpu)
{
enum cluster cur_cl = get_cpu_cluster(cpu);
return tfreq_data.pcluster[cur_cl].clft;
}
/* Write freq request in ndiv for a cpu */
static void write_ndiv_request(void *val)
{
uint64_t regval = *((uint64_t *) val);
asm volatile("msr s3_0_c15_c0_4, %0" : : "r" (regval));
}
#ifdef CONFIG_DEBUG_FS
/* Read freq request in ndiv for a cpu */
static void read_ndiv_request(void *ret)
{
uint64_t val = 0;
asm volatile("mrs %0, s3_0_c15_c0_4" : "=r" (val) : );
*((uint64_t *) ret) = val;
}
#endif
/**
* tegra_update_cpu_speed - update cpu freq
* @rate - in kHz
* @cpu - cpu whose freq to be updated
*/
static void tegra_update_cpu_speed(uint32_t rate, uint8_t cpu)
{
struct mrq_cpu_ndiv_limits_response *nltbl;
uint64_t val;
enum cluster cur_cl;
uint16_t ndiv;
cur_cl = get_cpu_cluster(cpu);
nltbl = &tfreq_data.pcluster[cur_cl].ndiv_limits_tbl;
if (!nltbl->ref_clk_hz)
return;
ndiv = map_freq_to_ndiv(nltbl, rate);
ndiv = clamp_ndiv(nltbl, ndiv);
val = (uint64_t)ndiv;
smp_call_function_single(cpu, write_ndiv_request, &val, 1);
}
/**
* tegra194_set_speed - Request freq to be set for policy->cpu
* @policy - cpufreq policy per cpu
* @index - freq table index
* Returns 0 on success, -ve on failure
*/
static int tegra194_set_speed(struct cpufreq_policy *policy, unsigned int index)
{
struct cpufreq_frequency_table *ftbl;
struct cpufreq_freqs freqs;
uint32_t tgt_freq;
enum cluster cl;
int cpu, ret = 0;
ftbl = get_freqtable(policy->cpu);
tgt_freq = ftbl[index].frequency;
freqs.old = policy->cur;
if (policy->cur == tgt_freq)
goto out;
freqs.new = tgt_freq;
cpufreq_freq_transition_begin(policy, &freqs);
cl = get_cpu_cluster(policy->cpu);
/*
* In hypervisor case cpufreq server will take care of
* updating frequency for each cpu in a cluster. So no
* need to run through the loop.
*/
if (tegra_hypervisor_mode)
t194_update_cpu_speed_hv(tgt_freq, policy->cpu);
else
for_each_cpu(cpu, policy->cpus)
tegra_update_cpu_speed(tgt_freq, cpu);
if (tfreq_data.pcluster[cl].bwmgr)
set_cpufreq_to_emcfreq(cl, tgt_freq);
cpufreq_freq_transition_end(policy, &freqs, ret);
out:
pr_debug("cpufreq: cpu: %d, ", policy->cpu);
pr_debug("oldfreq(kHz): %d, ", freqs.old);
pr_debug("req freq(kHz): %d ", tgt_freq);
pr_debug("final freq(kHz): %d ", policy->cur);
pr_debug("tgt_index %u\n", index);
return ret;
}
static void __tegra_mce_cc3_ctrl(void *data)
{
struct cc3_params *param = (struct cc3_params *)data;
tegra_mce_cc3_ctrl(param->ndiv, 0, (u8)param->enable);
}
static void enable_cc3(struct device_node *dn)
{
struct mrq_cpu_ndiv_limits_response *nltbl;
struct mrq_cpu_auto_cc3_request mreq;
struct mrq_cpu_auto_cc3_response mres;
struct cc3_params *cc3;
u32 freq = 0;
u16 ndiv;
int cl;
int ret = 0;
LOOP_FOR_EACH_CLUSTER(cl) {
if (!tfreq_data.pcluster[cl].configured)
continue;
nltbl = &tfreq_data.pcluster[cl].ndiv_limits_tbl;
cc3 = &tfreq_data.pcluster[cl].cc3;
mreq.cluster_id = cl;
ret = tegra_bpmp_send_receive(MRQ_CPU_AUTO_CC3, &mreq,
sizeof(struct mrq_cpu_auto_cc3_request),
&mres,
sizeof(struct mrq_cpu_auto_cc3_response));
if (ret || !(mres.auto_cc3_config & 1))
continue;
ret = of_property_read_u32_index(dn, "ndivida,autocc3-freq",
cl, &freq);
if (!ret && nltbl->ref_clk_hz) {
ndiv = map_freq_to_ndiv(nltbl, freq);
cc3->ndiv = clamp_ndiv(nltbl, ndiv);
} else
cc3->ndiv = (GENMASK(9, 1) & mres.auto_cc3_config) >> 1;
cc3->enable = 1;
ret = smp_call_function_any(&tfreq_data.pcluster[cl].cpu_mask,
__tegra_mce_cc3_ctrl,
cc3, 1);
WARN_ON_ONCE(ret);
}
}
#ifdef CONFIG_DEBUG_FS
#define RW_MODE (S_IWUSR | S_IRUGO)
#define RO_MODE (S_IRUGO)
static int get_delay(void *data, u64 *val)
{
*val = tfreq_data.freq_compute_delay;
return 0;
}
static int set_delay(void *data, u64 val)
{
uint32_t udelay = val;
if (udelay)
tfreq_data.freq_compute_delay = udelay;
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(freq_compute_fops, get_delay, set_delay,
"%llu\n");
static int freq_get(void *data, u64 *val)
{
uint64_t cpu = (uint64_t)data;
get_online_cpus();
if (cpu_online(cpu))
*val = tegra194_get_speed(cpu);
else
*val = 0LL;
put_online_cpus();
return 0;
}
/* Set freq in Khz for a cpu */
static int freq_set(void *data, u64 val)
{
uint64_t cpu = (uint64_t)data;
uint32_t freq = val;
if (val) {
if (tegra_hypervisor_mode)
t194_update_cpu_speed_hv(freq, cpu);
else
tegra_update_cpu_speed(freq, cpu);
}
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(freq_fops, freq_get, freq_set, "%llu\n");
/* Set ndiv for a cpu */
static int set_ndiv(void *data, u64 val)
{
uint64_t cpu = (uint64_t)data;
uint16_t ndiv = val & 0xffff;
if (!val)
return -EINVAL;
get_online_cpus();
if (cpu_online(cpu))
smp_call_function_single(cpu, write_ndiv_request, &ndiv, 1);
put_online_cpus();
return 0;
}
/* Get ndiv for a cpu */
static int get_ndiv(void *data, u64 *ndiv)
{
uint64_t cpu = (uint64_t)data;
get_online_cpus();
if (cpu_online(cpu)) {
smp_call_function_single(cpu, read_ndiv_request, ndiv, 1);
*ndiv = *ndiv & 0xffff;
} else
*ndiv = 0LL;
put_online_cpus();
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(ndiv_fops, get_ndiv, set_ndiv, "%04llx\n");
static void dump_ndiv_limits_tbl(struct seq_file *s,
struct mrq_cpu_ndiv_limits_response *nlt)
{
seq_printf(s, "reference clk(hz): %u\n", nlt->ref_clk_hz);
seq_printf(s, "pdiv: %u\n", nlt->pdiv);
seq_printf(s, "mdiv: %u\n", nlt->mdiv);
seq_printf(s, "ndiv_max: %u\n", nlt->ndiv_max);
seq_printf(s, "ndiv_min: %u\n", nlt->ndiv_min);
seq_puts(s, "\n");
}
static int show_bpmp_to_cpu_ndiv_limits(struct seq_file *s, void *data)
{
struct mrq_cpu_ndiv_limits_response *nlt;
enum cluster cl;
LOOP_FOR_EACH_CLUSTER(cl) {
if (!tfreq_data.pcluster[cl].configured)
continue;
nlt = &tfreq_data.pcluster[cl].ndiv_limits_tbl;
/*
* ndiv_limits for this cluster is not present.
* Could be single cluster or n cluster chip but for <cl>,
* current cluster, ndiv_limits is not sent by BPMP.
*/
if (!nlt->ref_clk_hz)
continue;
seq_printf(s, "cluster %d:\n", cl);
dump_ndiv_limits_tbl(s, nlt);
}
return 0;
}
static int stats_open(struct inode *inode, struct file *file)
{
return single_open(file, show_bpmp_to_cpu_ndiv_limits,
inode->i_private);
}
static const struct file_operations lut_fops = {
.open = stats_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int get_pcluster_cc3(void *data, u64 *val)
{
enum cluster cl = (enum cluster)data;
mutex_lock(&tfreq_data.mlock);
*val = tfreq_data.pcluster[cl].cc3.enable;
mutex_unlock(&tfreq_data.mlock);
return 0;
}
static int set_pcluster_cc3(void *data, u64 val)
{
enum cluster cl = (enum cluster)data;
int wait = 1;
int ret = 0;
mutex_lock(&tfreq_data.mlock);
if (tfreq_data.pcluster[cl].cc3.enable ^ (bool) val) {
tfreq_data.pcluster[cl].cc3.enable = (bool) val;
ret = smp_call_function_any(&tfreq_data.pcluster[cl].cpu_mask,
__tegra_mce_cc3_ctrl,
&tfreq_data.pcluster[cl].cc3, wait);
}
mutex_unlock(&tfreq_data.mlock);
return ret;
}
DEFINE_SIMPLE_ATTRIBUTE(pcl_cc3_ops, get_pcluster_cc3,
set_pcluster_cc3, "%llu\n");
static int get_cc3_ndiv(void *data, u64 *val)
{
enum cluster cl = (enum cluster)data;
mutex_lock(&tfreq_data.mlock);
*val = tfreq_data.pcluster[cl].cc3.ndiv;
mutex_unlock(&tfreq_data.mlock);
return 0;
}
static int set_cc3_ndiv(void *data, u64 val)
{
enum cluster cl = (enum cluster)data;
int wait = 1;
int ret = 0;
mutex_lock(&tfreq_data.mlock);
if (tfreq_data.pcluster[cl].cc3.ndiv != (u32) val) {
tfreq_data.pcluster[cl].cc3.ndiv = (u32) val;
ret = smp_call_function_any(&tfreq_data.pcluster[cl].cpu_mask,
__tegra_mce_cc3_ctrl,
&tfreq_data.pcluster[cl].cc3, wait);
}
mutex_unlock(&tfreq_data.mlock);
return ret;
}
DEFINE_SIMPLE_ATTRIBUTE(cc3_ndiv_ops, get_cc3_ndiv, set_cc3_ndiv,
"%llu\n");
static struct dentry *tegra_cpufreq_debugfs_root;
static int __init cc3_debug_init(void)
{
struct dentry *dir;
long int cl;
uint8_t buff[64];
LOOP_FOR_EACH_CLUSTER(cl) {
if (!tfreq_data.pcluster[cl].configured)
continue;
snprintf(buff, sizeof(buff), "CLUSTER%ld", cl);
dir = debugfs_create_dir(buff, tegra_cpufreq_debugfs_root);
if (!dir)
goto err_out;
snprintf(buff, sizeof(buff), "cc3");
dir = debugfs_create_dir(buff, dir);
if (!dir)
goto err_out;
if (!tfreq_data.pcluster[cl].cc3.enable) {
debugfs_create_bool("supported", RO_MODE, dir,
&tfreq_data.pcluster[cl].cc3.enable);
continue;
}
if (!debugfs_create_file("enable", RW_MODE, dir, (void *)cl,
&pcl_cc3_ops))
goto err_out;
if (!debugfs_create_file("ndiv", RW_MODE, dir, (void *)cl,
&cc3_ndiv_ops))
goto err_out;
}
return 0;
err_out:
return -EINVAL;
}
static int __init tegra_cpufreq_debug_init(void)
{
struct dentry *dir;
uint8_t buff[15];
uint64_t cpu;
tegra_cpufreq_debugfs_root = debugfs_create_dir("tegra_cpufreq", NULL);
if (!tegra_cpufreq_debugfs_root)
return -ENOMEM;
if (!debugfs_create_file("bpmp_cpu_ndiv_limits_table", RO_MODE,
tegra_cpufreq_debugfs_root,
NULL,
&lut_fops))
goto err_out;
if (!debugfs_create_file("freq_compute_delay", RW_MODE,
tegra_cpufreq_debugfs_root,
NULL,
&freq_compute_fops))
goto err_out;
if (!tegra_debugfs_create_cpu_emc_map(tegra_cpufreq_debugfs_root,
cpu_emc_map_ptr))
goto err_out;
if (cc3_debug_init())
goto err_out;
for_each_possible_cpu(cpu) {
snprintf(buff, sizeof(buff), "cpu%llu", cpu);
dir = debugfs_create_dir(buff, tegra_cpufreq_debugfs_root);
if (!dir)
goto err_out;
if (!debugfs_create_file("freq", RW_MODE, dir, (void *)cpu,
&freq_fops))
goto err_out;
if (!debugfs_create_file("ndiv", RW_MODE, dir,
(void *)cpu, &ndiv_fops))
goto err_out;
}
return 0;
err_out:
debugfs_remove_recursive(tegra_cpufreq_debugfs_root);
return -ENOMEM;
}
static void __exit tegra_cpufreq_debug_exit(void)
{
debugfs_remove_recursive(tegra_cpufreq_debugfs_root);
}
#endif
static int tegra194_cpufreq_init(struct cpufreq_policy *policy)
{
struct cpufreq_frequency_table *ftbl;
enum cluster cl;
if (policy->cpu >= CONFIG_NR_CPUS)
return -EINVAL;
ftbl = get_freqtable(policy->cpu);
cpufreq_table_validate_and_show(policy, ftbl);
policy->suspend_freq = policy->max;
policy->cur = tegra194_fast_get_speed(policy->cpu); /* boot freq */
cl = get_cpu_cluster(policy->cpu);
policy->cpuinfo.transition_latency =
TEGRA_CPUFREQ_TRANSITION_LATENCY;
if (cpufreq_single_policy)
cpumask_copy(policy->cpus, cpu_possible_mask);
else
cpumask_copy(policy->cpus, &tfreq_data.pcluster[cl].cpu_mask);
return 0;
}
static int tegra194_cpufreq_exit(struct cpufreq_policy *policy)
{
struct cpufreq_frequency_table *ftbl;
enum cluster cl;
ftbl = get_freqtable(policy->cpu);
cpufreq_frequency_table_cpuinfo(policy, ftbl);
cl = get_cpu_cluster(policy->cpu);
if (tfreq_data.pcluster[cl].bwmgr)
tegra_bwmgr_set_emc(tfreq_data.pcluster[cl].bwmgr, 0,
TEGRA_BWMGR_SET_EMC_FLOOR);
return 0;
}
static struct cpufreq_driver tegra_cpufreq_driver = {
.name = "tegra_cpufreq",
.flags = CPUFREQ_ASYNC_NOTIFICATION | CPUFREQ_STICKY |
CPUFREQ_CONST_LOOPS |
CPUFREQ_NEED_INITIAL_FREQ_CHECK,
.verify = cpufreq_generic_frequency_table_verify,
.target_index = tegra194_set_speed,
.get = tegra194_get_speed,
.init = tegra194_cpufreq_init,
.exit = tegra194_cpufreq_exit,
.attr = cpufreq_generic_attr,
.suspend = cpufreq_generic_suspend,
};
static int cpu_freq_notify(struct notifier_block *b,
unsigned long l, void *v)
{
struct cpufreq_policy *policy;
struct cpumask updated_cpus;
int cpu, ret = 0;
cpumask_clear(&updated_cpus);
for_each_online_cpu(cpu) {
/* Skip CPUs already covered by a previous update */
if (cpumask_test_cpu(cpu, &updated_cpus))
continue;
policy = cpufreq_cpu_get(cpu);
if (!policy)
continue;
(void)cpufreq_update_policy(policy->cpu);
cpumask_or(&updated_cpus, &updated_cpus, policy->cpus);
cpufreq_cpu_put(policy);
}
return notifier_from_errno(ret);
}
/* Clipping policy object's min/max to pmqos limits */
static int tegra_boundaries_policy_notifier(struct notifier_block *nb,
unsigned long event, void *data)
{
struct cpufreq_policy *policy = data;
unsigned int qmin = 0;
unsigned int qmax = UINT_MAX;
if (event != CPUFREQ_ADJUST)
return NOTIFY_OK;
qmin = pm_qos_read_min_bound(PM_QOS_CPU_FREQ_BOUNDS);
qmax = pm_qos_read_max_bound(PM_QOS_CPU_FREQ_BOUNDS);
/*
* Clamp pmqos to stay within sysfs upper boundary
* but allow pmqos cap override sysfs min freq settings
*/
qmin = min(qmin, policy->user_policy.max);
qmax = min(qmax, policy->user_policy.max);
/* Apply pmqos limits on top of existing limits */
policy->min = max(policy->min, qmin);
policy->max = min(policy->max, qmax);
if (policy->min > policy->max)
policy->min = policy->max;
return NOTIFY_OK;
}
static struct notifier_block cpu_freq_nb = {
.notifier_call = cpu_freq_notify,
};
static struct notifier_block tegra_boundaries_cpufreq_nb = {
.notifier_call = tegra_boundaries_policy_notifier,
};
static void __init pm_qos_register_notifier(void)
{
pm_qos_add_min_notifier(PM_QOS_CPU_FREQ_BOUNDS,
&cpu_freq_nb);
pm_qos_add_max_notifier(PM_QOS_CPU_FREQ_BOUNDS,
&cpu_freq_nb);
}
static int tegra194_cpufreq_offline(unsigned int cpu)
{
struct cpufreq_frequency_table *ftbl;
uint32_t tgt_freq;
ftbl = get_freqtable(cpu);
tgt_freq = ftbl[0].frequency;
if (tgt_freq != CPUFREQ_ENTRY_INVALID
&& tgt_freq != CPUFREQ_TABLE_END)
tegra_update_cpu_speed(tgt_freq, cpu);
return 0;
}
static void free_resources(void)
{
enum cluster cl;
LOOP_FOR_EACH_CLUSTER(cl) {
if (!tfreq_data.pcluster[cl].configured)
continue;
/* free table */
kfree(tfreq_data.pcluster[cl].clft);
/* unregister from emc bw manager */
tegra_bwmgr_unregister(tfreq_data.pcluster[cl].bwmgr);
}
flush_workqueue(read_counters_wq);
destroy_workqueue(read_counters_wq);
}
static void __init free_allocated_res_init(void)
{
free_resources();
}
static void __exit free_allocated_res_exit(void)
{
free_resources();
}
static int __init init_freqtbls(struct device_node *dn)
{
u16 freq_table_step_size;
u16 dt_freq_table_step_size = 0;
struct cpufreq_frequency_table *ftbl;
struct mrq_cpu_ndiv_limits_response *nltbl;
u16 ndiv, max_freq_steps, delta_ndiv;
enum cluster cl;
int ret = 0, index;
if (!of_property_read_u16(dn, "freq_table_step_size",
&dt_freq_table_step_size)) {
if (!dt_freq_table_step_size)
pr_info("cpufreq: Invalid freq_table_step_size 0\n");
}
LOOP_FOR_EACH_CLUSTER(cl) {
if (!tfreq_data.pcluster[cl].configured)
continue;
nltbl = &tfreq_data.pcluster[cl].ndiv_limits_tbl;
/*
* ndiv_limits for this cluster is not present.
* Could be single cluster or n cluster chip but for <cl>,
* current cluster, ndiv_limits is not sent by BPMP.
*/
if (!nltbl->ref_clk_hz) {
pr_warn("%s: cpufreq: ", __func__);
pr_warn("cluster %d has no ndiv_limits table\n", cl);
continue;
}
/*
* Make sure frequency table step is a multiple of mdiv to match
* vhint table granularity.
*/
freq_table_step_size = nltbl->mdiv * DIV_ROUND_UP(
CPUFREQ_TBL_STEP_HZ, nltbl->ref_clk_hz);
if (dt_freq_table_step_size) {
freq_table_step_size = nltbl->mdiv * DIV_ROUND_UP(
dt_freq_table_step_size, nltbl->mdiv);
if (freq_table_step_size != dt_freq_table_step_size) {
pr_info("cpufreq: cluster %d: table step:", cl);
pr_info(" dt: %u clipped: %u (to mdiv: %u)\n",
dt_freq_table_step_size,
freq_table_step_size,
nltbl->mdiv);
}
}
pr_debug("cpufreq: cluster %d: frequency table step size: %d\n",
cl, freq_table_step_size);
delta_ndiv = nltbl->ndiv_max - nltbl->ndiv_min;
if (unlikely(delta_ndiv == 0))
max_freq_steps = 1;
else {
/* We store both ndiv_min and ndiv_max hence the +1 */
max_freq_steps = delta_ndiv / freq_table_step_size + 1;
}
max_freq_steps += (delta_ndiv % freq_table_step_size) ? 1 : 0;
/* Allocate memory 1 + max_freq_steps to write END_OF_TABLE */
ftbl = kzalloc(sizeof(struct cpufreq_frequency_table) *
(max_freq_steps + 1), GFP_KERNEL);
if (!ftbl) {
ret = -ENOMEM;
while (cl--)
kfree(tfreq_data.pcluster[cl].clft);
goto err_out;
}
for (index = 0, ndiv = nltbl->ndiv_min;
ndiv < nltbl->ndiv_max;
index++, ndiv += freq_table_step_size)
ftbl[index].frequency = map_ndiv_to_freq(nltbl, ndiv);
ftbl[index++].frequency = map_ndiv_to_freq(nltbl,
nltbl->ndiv_max);
ftbl[index].frequency = CPUFREQ_TABLE_END;
tfreq_data.pcluster[cl].clft = ftbl;
}
err_out:
return ret;
}
static int __init get_ndiv_limits_tbl_from_bpmp(void)
{
struct mrq_cpu_ndiv_limits_request md;
struct mrq_cpu_ndiv_limits_response *nltbl;
uint32_t cl;
int ret = 0;
bool ok = false;
LOOP_FOR_EACH_CLUSTER(cl) {
if (!tfreq_data.pcluster[cl].configured)
continue;
nltbl = &tfreq_data.pcluster[cl].ndiv_limits_tbl;
md.cluster_id = cl;
ret = tegra_bpmp_send_receive(MRQ_CPU_NDIV_LIMITS, &md,
sizeof(struct mrq_cpu_ndiv_limits_request),
nltbl,
sizeof(struct mrq_cpu_ndiv_limits_response));
if (ret) {
pr_warn("%s: cpufreq: ", __func__);
pr_warn("cluster %u: ndiv_limits query failed!\n", cl);
goto err_out;
} else {
ok = true;
}
}
err_out:
return ok ? 0 : ret;
}
static void set_cpu_mask(void)
{
int cpu_num;
enum cluster cl;
LOOP_FOR_EACH_CLUSTER(cl) {
if (!tfreq_data.pcluster[cl].configured)
continue;
cpumask_clear(&tfreq_data.pcluster[cl].cpu_mask);
}
for_each_possible_cpu(cpu_num) {
cl = get_cpu_cluster(cpu_num);
if (!tfreq_data.pcluster[cl].configured)
continue;
cpumask_set_cpu(cpu_num,
&tfreq_data.pcluster[cl].cpu_mask);
}
}
static int __init register_with_emc_bwmgr(void)
{
enum tegra_bwmgr_client_id bw_id_array[MAX_CLUSTERS] = {
TEGRA_BWMGR_CLIENT_CPU_CLUSTER_0,
TEGRA_BWMGR_CLIENT_CPU_CLUSTER_1,
TEGRA_BWMGR_CLIENT_CPU_CLUSTER_2,
TEGRA_BWMGR_CLIENT_CPU_CLUSTER_3
};
struct tegra_bwmgr_client *bwmgr;
enum cluster cl;
int ret = 0;
LOOP_FOR_EACH_CLUSTER(cl) {
if (!tfreq_data.pcluster[cl].configured)
continue;
bwmgr = tegra_bwmgr_register(bw_id_array[cl]);
if (IS_ERR_OR_NULL(bwmgr)) {
pr_warn("cpufreq: emc bw manager registration failed");
pr_warn(" for cluster %d\n", cl);
ret = -ENODEV;
while (cl--)
tegra_bwmgr_unregister(
tfreq_data.pcluster[cl].bwmgr);
goto err_out;
}
tfreq_data.pcluster[cl].bwmgr = bwmgr;
}
err_out:
return ret;
}
static bool tegra_cpufreq_single_policy(struct device_node *dn)
{
struct property *prop;
prop = of_find_property(dn, "cpufreq_single_policy", NULL);
if (prop)
return 1;
else
return 0;
}
static int __init tegra194_cpufreq_probe(struct platform_device *pdev)
{
struct device_node *dn;
uint32_t cpu;
int ret = 0, cl = 0;
dn = pdev->dev.of_node;
cpu_emc_map_ptr = tegra_cpufreq_cpu_emc_map_dt_init(dn);
cpufreq_single_policy = tegra_cpufreq_single_policy(dn);
mutex_init(&tfreq_data.mlock);
tfreq_data.freq_compute_delay = US_DELAY;
tegra_hypervisor_mode = is_tegra_hypervisor_mode();
for_each_possible_cpu(cpu) {
cl = get_cpu_cluster(cpu);
if (!tfreq_data.pcluster[cl].configured)
tfreq_data.pcluster[cl].configured = 1;
}
set_cpu_mask();
if (tegra_hypervisor_mode) {
ret = parse_hv_dt_data(dn);
if (ret)
goto err_free_res;
}
if (tegra_hypervisor_mode)
tegra_cpufreq_driver.get = t194_get_cpu_speed_hv;
ret = register_with_emc_bwmgr();
if (ret) {
pr_err("tegra19x-cpufreq: fail to register emc bw manager\n");
goto err_out;
}
read_counters_wq = alloc_workqueue("read_counters_wq", __WQ_LEGACY, 1);
if (!read_counters_wq) {
pr_err("tegra19x-cpufreq: fail to create_workqueue\n");
goto err_free_res;
}
ret = get_ndiv_limits_tbl_from_bpmp();
if (ret)
goto err_free_res;
enable_cc3(dn);
#ifdef CONFIG_DEBUG_FS
ret = tegra_cpufreq_debug_init();
if (ret) {
pr_err("tegra19x-cpufreq: failed to create debugfs nodes\n");
goto err_out;
}
#endif
ret = init_freqtbls(dn);
if (ret)
goto err_free_res;
ret = cpufreq_register_driver(&tegra_cpufreq_driver);
if (ret)
goto err_free_res;
ret = cpuhp_setup_state_nocalls(CPUHP_AP_ONLINE_DYN,
"tegra194_cpufreq:online",
NULL,
tegra194_cpufreq_offline);
if (ret < 0) {
pr_err("tegra19x-cpufreq: failed to register cpuhp state\n");
goto err_free_res;
}
hp_online = ret;
ret = 0;
pm_qos_register_notifier();
cpufreq_register_notifier(&tegra_boundaries_cpufreq_nb,
CPUFREQ_POLICY_NOTIFIER);
goto err_out;
err_free_res:
free_allocated_res_init();
err_out:
pr_info("%s: platform driver Initialization: %s\n",
__func__, (ret ? "fail" : "pass"));
return ret;
}
static int __exit tegra194_cpufreq_remove(struct platform_device *pdev)
{
cpufreq_unregister_notifier(&tegra_boundaries_cpufreq_nb,
CPUFREQ_POLICY_NOTIFIER);
#ifdef CONFIG_DEBUG_FS
tegra_cpufreq_debug_exit();
#endif
cpuhp_remove_state_nocalls(hp_online);
cpufreq_unregister_driver(&tegra_cpufreq_driver);
free_allocated_res_exit();
return 0;
}
static const struct of_device_id tegra194_cpufreq_of_match[] = {
{. compatible = "nvidia,tegra194-cpufreq", },
{ }
};
MODULE_DEVICE_TABLE(of, tegra194_cpufreq_of_match);
static struct platform_driver tegra194_cpufreq_platform_driver __refdata = {
.driver = {
.name = "tegra194-cpufreq",
.of_match_table = tegra194_cpufreq_of_match,
},
.probe = tegra194_cpufreq_probe,
.remove = tegra194_cpufreq_remove,
};
module_platform_driver(tegra194_cpufreq_platform_driver);
MODULE_AUTHOR("Hoang Pham <hopham@nvidia.com>");
MODULE_AUTHOR("Bo Yan <byan@nvidia.com>");
MODULE_DESCRIPTION("NVIDIA Tegra194 cpufreq driver");
MODULE_LICENSE("GPL v2");