/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 , * 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 , * 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 "); MODULE_AUTHOR("Bo Yan "); MODULE_DESCRIPTION("NVIDIA Tegra194 cpufreq driver"); MODULE_LICENSE("GPL v2");