/* * linux/drivers/cpufreq/sunxi-iks-cpufreq.c * * Copyright(c) 2013-2015 Allwinnertech Co., Ltd. * http://www.allwinnertech.com * * Author: sunny * * allwinner sunxi iks cpufreq driver. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include #include #include #include #include #include //#include #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_DEBUG_FS #include #include #endif #include "sunxi-iks-cpufreq.h" #ifdef CONFIG_DEBUG_FS static unsigned long long c0_set_time_usecs; static unsigned long long c0_get_time_usecs; static unsigned long long c1_set_time_usecs; static unsigned long long c1_get_time_usecs; #endif #if defined(CONFIG_ARCH_SUN8IW6P1) || defined(CONFIG_ARCH_SUN8IW9P1) || defined(CONFIG_ARCH_SUN8IW17P1) #define PLL1_CLK "pll_cpu0" #define PLL2_CLK "pll_cpu1" #define CLUSTER0_CLK "cluster0" #define CLUSTER1_CLK "cluster1" #define ARISC_DVFS_VF_TABLE_MAX (16) static struct cpufreq_frequency_table sunxi_freq_table_ca7[] = { {0, 2016 * 1000}, {0, 1800 * 1000}, {0, 1608 * 1000}, {0, 1200 * 1000}, {0, 1128 * 1000}, {0, 1008 * 1000}, {0, 912 * 1000}, {0, 864 * 1000}, {0, 720 * 1000}, {0, 600 * 1000}, {0, 480 * 1000}, {0, CPUFREQ_TABLE_END}, }; typedef struct arisc_freq_voltage { u32 freq; u32 voltage; u32 axi_div; } arisc_freq_voltage_t; static struct arisc_freq_voltage arisc_vf_table[2][ARISC_DVFS_VF_TABLE_MAX] = { /* * cpu0 vdd is 1.20v if cpu freq is (600Mhz, 1008Mhz] * cpu0 vdd is 1.20v if cpu freq is (420Mhz, 600Mhz] * cpu0 vdd is 1.20v if cpu freq is (360Mhz, 420Mhz] * cpu0 vdd is 1.20v if cpu freq is (300Mhz, 360Mhz] * cpu0 vdd is 1.20v if cpu freq is (240Mhz, 300Mhz] * cpu0 vdd is 1.20v if cpu freq is (120Mhz, 240Mhz] * cpu0 vdd is 1.20v if cpu freq is (60Mhz, 120Mhz] * cpu0 vdd is 1.20v if cpu freq is (0Mhz, 60Mhz] */ { /* freq voltage axi_div */ {900000000, 1200, 3}, {600000000, 1200, 3}, {420000000, 1200, 3}, {360000000, 1200, 3}, {300000000, 1200, 3}, {240000000, 1200, 3}, {120000000, 1200, 3}, {60000000, 1200, 3}, {0, 1200, 3}, {0, 1200, 3}, {0, 1200, 3}, {0, 1200, 3}, {0, 1200, 3}, {0, 1200, 3}, {0, 1200, 3}, {0, 1200, 3}, }, /* * cpu0 vdd is 1.20v if cpu freq is (600Mhz, 1008Mhz] * cpu0 vdd is 1.20v if cpu freq is (420Mhz, 600Mhz] * cpu0 vdd is 1.20v if cpu freq is (360Mhz, 420Mhz] * cpu0 vdd is 1.20v if cpu freq is (300Mhz, 360Mhz] * cpu0 vdd is 1.20v if cpu freq is (240Mhz, 300Mhz] * cpu0 vdd is 1.20v if cpu freq is (120Mhz, 240Mhz] * cpu0 vdd is 1.20v if cpu freq is (60Mhz, 120Mhz] * cpu0 vdd is 1.20v if cpu freq is (0Mhz, 60Mhz] */ { /* freq voltage axi_div */ {900000000, 1200, 3}, {600000000, 1200, 3}, {420000000, 1200, 3}, {360000000, 1200, 3}, {300000000, 1200, 3}, {240000000, 1200, 3}, {120000000, 1200, 3}, {60000000, 1200, 3}, {0, 1200, 3}, {0, 1200, 3}, {0, 1200, 3}, {0, 1200, 3}, {0, 1200, 3}, {0, 1200, 3}, {0, 1200, 3}, {0, 1200, 3}, }, }; #define sunxi_freq_table_ca15 sunxi_freq_table_ca7 /* * Notice: * The the definition of the minimum frequnecy should be a valid value * in the frequnecy table, otherwise, there may be some power efficiency * lost in the interactive governor, when the cpufreq_interactive_idle_start * try to check the frequency status: * if (pcpu->target_freq != pcpu->policy->min) {} * the target_freq will never equal to the policy->min !!! Then, the timer * will wakeup the cpu frequently */ /* config the maximum frequency of sunxi core */ #define SUNXI_CPUFREQ_L_MAX (2016000000) /* config the minimum frequency of sunxi core */ #define SUNXI_CPUFREQ_L_MIN (480000000) /* config the maximum frequency of sunxi core */ #define SUNXI_CPUFREQ_B_MAX SUNXI_CPUFREQ_L_MAX /* config the minimum frequency of sunxi core */ #define SUNXI_CPUFREQ_B_MIN SUNXI_CPUFREQ_L_MIN #else static struct cpufreq_frequency_table sunxi_freq_table_ca7[] = { {0, 1200 * 1000}, {0, 1104 * 1000}, {0, 1008 * 1000}, {0, 912 * 1000}, {0, 816 * 1000}, {0, 720 * 1000}, {0, 600 * 1000}, {0, 480 * 1000}, {0, CPUFREQ_TABLE_END}, }; static struct cpufreq_frequency_table sunxi_freq_table_ca15[] = { {0, 1800 * 1000}, {0, 1704 * 1000}, {0, 1608 * 1000}, {0, 1512 * 1000}, {0, 1416 * 1000}, {0, 1320 * 1000}, {0, 1200 * 1000}, {0, 1104 * 1000}, {0, 1008 * 1000}, {0, 912 * 1000}, {0, 816 * 1000}, {0, 720 * 1000}, {0, 600 * 1000}, {0, CPUFREQ_TABLE_END}, }; /* * Notice: * The the definition of the minimum frequnecy should be a valid value * in the frequnecy table, otherwise, there may be some power efficiency * lost in the interactive governor, when the cpufreq_interactive_idle_start * try to check the frequency status: * if (pcpu->target_freq != pcpu->policy->min) {} * the target_freq will never equal to the policy->min !!! Then, the timer * will wakeup the cpu frequently */ /* config the maximum frequency of sunxi core */ #define SUNXI_CPUFREQ_L_MAX (1200000000) /* config the minimum frequency of sunxi core */ #define SUNXI_CPUFREQ_L_MIN (480000000) /* config the maximum frequency of sunxi core */ #define SUNXI_CPUFREQ_B_MAX (1800000000) /* config the minimum frequency of sunxi core */ #define SUNXI_CPUFREQ_B_MIN (600000000) #endif static unsigned int l_freq_max = SUNXI_CPUFREQ_L_MAX / 1000; static unsigned int l_freq_boot = SUNXI_CPUFREQ_L_MAX / 1000; static unsigned int l_freq_ext = SUNXI_CPUFREQ_L_MAX / 1000; static unsigned int l_freq_min = SUNXI_CPUFREQ_L_MIN / 1000; static unsigned int b_freq_max = SUNXI_CPUFREQ_B_MAX / 1000; static unsigned int b_freq_boot = SUNXI_CPUFREQ_B_MAX / 1000; static unsigned int b_freq_ext = SUNXI_CPUFREQ_B_MAX / 1000; static unsigned int b_freq_min = SUNXI_CPUFREQ_B_MIN / 1000; #ifdef CONFIG_BL_SWITCHER bool bL_switching_enabled; #endif int sunxi_dvfs_debug; int sunxi_boot_freq_lock; static struct clk *clk_pll1; /* pll1 clock handler */ static struct clk *clk_pll2; /* pll2 clock handler */ static struct clk *cluster_clk[MAX_CLUSTERS]; static unsigned int cluster_pll[MAX_CLUSTERS]; static struct cpufreq_frequency_table *freq_table[MAX_CLUSTERS + 1]; static struct regulator *cpu_vdd[MAX_CLUSTERS]; /* cpu vdd handler */ static DEFINE_PER_CPU(unsigned int, physical_cluster); static DEFINE_PER_CPU(unsigned int, cpu_last_req_freq); static struct mutex cluster_lock[MAX_CLUSTERS]; static unsigned int sunxi_cpufreq_set_rate(u32 cpu, u32 old_cluster, u32 new_cluster, u32 rate); static unsigned int find_cluster_maxfreq(int cluster) { int j; u32 max_freq = 0, cpu_freq; for_each_online_cpu(j) { cpu_freq = per_cpu(cpu_last_req_freq, j); if ((cluster == per_cpu(physical_cluster, j)) && (max_freq < cpu_freq)) max_freq = cpu_freq; } if (unlikely(sunxi_dvfs_debug)) CPUFREQ_DBG("%s: cluster:%d, max freq:%d\n", __func__, cluster, max_freq); return max_freq; } /* * get the current cpu vdd; * return: cpu vdd, based on mv; */ static int sunxi_cpufreq_getvolt(unsigned int cpu) { u32 cur_cluster = per_cpu(physical_cluster, cpu); return regulator_get_voltage(cpu_vdd[cur_cluster]) / 1000; } static unsigned int sunxi_clk_get_cpu_rate(unsigned int cpu) { u32 cur_cluster = per_cpu(physical_cluster, cpu), rate; u32 other_cluster_cpu_num = 0; #ifdef CONFIG_SCHED_SMP_DCMP u32 cpu_id; u32 other_cluster = MAX_CLUSTERS; u32 other_cluster_cpu = nr_cpu_ids; u32 other_cluster_rate; u32 tmp_cluster; #endif #ifdef CONFIG_DEBUG_FS ktime_t calltime = ktime_set(0, 0), delta, rettime; #endif #ifdef CONFIG_SCHED_SMP_DCMP for_each_online_cpu(cpu_id) { tmp_cluster = cpu_to_cluster(cpu_id); if (tmp_cluster != cur_cluster) { other_cluster_cpu_num++; if (other_cluster == MAX_CLUSTERS) { other_cluster_cpu = cpu_id; other_cluster = tmp_cluster; } } } #endif if (other_cluster_cpu_num) { mutex_lock(&cluster_lock[A15_CLUSTER]); mutex_lock(&cluster_lock[A7_CLUSTER]); } else { mutex_lock(&cluster_lock[cur_cluster]); } #ifdef CONFIG_DEBUG_FS calltime = ktime_get(); #endif if (cur_cluster == A7_CLUSTER) clk_get_rate(clk_pll1); else if (cur_cluster == A15_CLUSTER) clk_get_rate(clk_pll2); rate = clk_get_rate(cluster_clk[cur_cluster]) / 1000; /* For switcher we use virtual A15 clock rates */ if (is_bL_switching_enabled()) rate = VIRT_FREQ(cur_cluster, rate); #ifdef CONFIG_SCHED_SMP_DCMP if (other_cluster_cpu_num) { if (other_cluster == A7_CLUSTER) clk_get_rate(clk_pll1); else if (other_cluster == A15_CLUSTER) clk_get_rate(clk_pll2); other_cluster_rate = clk_get_rate(cluster_clk[other_cluster]); other_cluster_rate /= 1000; /* For switcher we use virtual A15 clock rates */ if (is_bL_switching_enabled()) other_cluster_rate = VIRT_FREQ(other_cluster, other_cluster_rate); } #endif #ifdef CONFIG_DEBUG_FS rettime = ktime_get(); delta = ktime_sub(rettime, calltime); if (cur_cluster == A7_CLUSTER) c0_get_time_usecs = ktime_to_ns(delta) >> 10; else if (cur_cluster == A15_CLUSTER) c1_get_time_usecs = ktime_to_ns(delta) >> 10; #endif if (unlikely(sunxi_dvfs_debug)) CPUFREQ_DBG("cpu:%d, cur_cluster:%d, cur_freq:%d\n", cpu, cur_cluster, rate); #ifdef CONFIG_SCHED_SMP_DCMP if (other_cluster_cpu_num) { mutex_unlock(&cluster_lock[A7_CLUSTER]); mutex_unlock(&cluster_lock[A15_CLUSTER]); if (other_cluster_rate != rate) { sunxi_cpufreq_set_rate(other_cluster_cpu, other_cluster, other_cluster, rate); } } else #endif mutex_unlock(&cluster_lock[cur_cluster]); return rate; } #ifdef CONFIG_SUNXI_ARISC static int clk_set_fail_notify(void *arg) { CPUFREQ_ERR("%s: cluster: %d\n", __func__, (u32)arg); /* maybe should do others */ return 0; } #endif static int sunxi_clk_set_cpu_rate(unsigned int cluster, unsigned int cpu, unsigned int rate) { #ifdef CONFIG_SUNXI_CPUFREQ_ASYN unsigned long timeout; #endif int ret; #ifdef CONFIG_DEBUG_FS ktime_t calltime = ktime_set(0, 0), delta, rettime; #endif if (unlikely(sunxi_dvfs_debug)) CPUFREQ_DBG("cpu:%d, cluster:%d, set freq:%u\n", cpu, cluster, rate); #ifdef CONFIG_DEBUG_FS calltime = ktime_get(); #endif #ifndef CONFIG_SUNXI_ARISC ret = 0; #else #ifdef CONFIG_SUNXI_CPUFREQ_ASYN ret = arisc_dvfs_set_cpufreq(rate, cluster_pll[cluster], ARISC_MESSAGE_ATTR_ASYN, clk_set_fail_notify, (void *)cluster); /* CPUS max latency for cpu freq*/ timeout = 15; while (timeout-- && ((A7_CLUSTER == cluster ? clk_get_rate(clk_pll1) : clk_get_rate(clk_pll2)) != rate*1000)) msleep(1); if ((A7_CLUSTER == cluster ? clk_get_rate(clk_pll1) : clk_get_rate(clk_pll2)) != rate*1000) ret = -1; #else ret = arisc_dvfs_set_cpufreq(rate, cluster_pll[cluster], ARISC_MESSAGE_ATTR_SOFTSYN, clk_set_fail_notify, (void *)cluster); #endif #endif #ifdef CONFIG_DEBUG_FS rettime = ktime_get(); delta = ktime_sub(rettime, calltime); if (cluster == A7_CLUSTER) c0_set_time_usecs = ktime_to_ns(delta) >> 10; else if (cluster == A15_CLUSTER) c1_set_time_usecs = ktime_to_ns(delta) >> 10; #endif return ret; } static unsigned int sunxi_cpufreq_set_rate(u32 cpu, u32 old_cluster, u32 new_cluster, u32 rate) { u32 new_rate, prev_rate; int ret; mutex_lock(&cluster_lock[new_cluster]); prev_rate = per_cpu(cpu_last_req_freq, cpu); per_cpu(cpu_last_req_freq, cpu) = rate; per_cpu(physical_cluster, cpu) = new_cluster; if (is_bL_switching_enabled()) { new_rate = find_cluster_maxfreq(new_cluster); new_rate = ACTUAL_FREQ(new_cluster, new_rate); } else new_rate = rate; if (unlikely(sunxi_dvfs_debug)) CPUFREQ_DBG("cpu:%d, old cluster:%d, new cluster:%d, target freq:%d\n", cpu, old_cluster, new_cluster, new_rate); ret = sunxi_clk_set_cpu_rate(new_cluster, cpu, new_rate); if (WARN_ON(ret)) { CPUFREQ_ERR("clk_set_rate failed:%d, new cluster:%d\n", ret, new_cluster); per_cpu(cpu_last_req_freq, cpu) = prev_rate; per_cpu(physical_cluster, cpu) = old_cluster; mutex_unlock(&cluster_lock[new_cluster]); return ret; } mutex_unlock(&cluster_lock[new_cluster]); if (is_bL_switching_enabled()) { /* Recalc freq for old cluster when switching clusters */ if (old_cluster != new_cluster) { if (unlikely(sunxi_dvfs_debug)) CPUFREQ_DBG("cpu:%d, switch from cluster-%d to cluster-%d\n", cpu, old_cluster, new_cluster); bL_switch_request(cpu, new_cluster); mutex_lock(&cluster_lock[old_cluster]); /* Set freq of old cluster if there are cpus left on it */ new_rate = find_cluster_maxfreq(old_cluster); new_rate = ACTUAL_FREQ(old_cluster, new_rate); if (new_rate) { if (unlikely(sunxi_dvfs_debug)) CPUFREQ_DBG("Updating rate of old cluster:%d, to freq:%d\n", old_cluster, new_rate); if (sunxi_clk_set_cpu_rate(old_cluster, cpu, new_rate)) CPUFREQ_ERR("clk_set_rate failed: %d, old cluster:%d\n", ret, old_cluster); } mutex_unlock(&cluster_lock[old_cluster]); } } return 0; } /* Validate policy frequency range */ static int sunxi_cpufreq_verify_policy(struct cpufreq_policy *policy) { u32 cur_cluster = cpu_to_cluster(policy->cpu); return cpufreq_frequency_table_verify(policy, freq_table[cur_cluster]); } /* Set clock frequency */ static int sunxi_cpufreq_set_target_index(struct cpufreq_policy *policy, unsigned int index) { struct cpufreq_freqs freqs; u32 cpu = policy->cpu; u32 target_freq; u32 cur_cluster, new_cluster, actual_cluster; int ret = 0; #ifdef CONFIG_SCHED_SMP_DCMP u32 i, other_cluster; #endif cur_cluster = cpu_to_cluster(cpu); new_cluster = actual_cluster = per_cpu(physical_cluster, cpu); freqs.old = sunxi_clk_get_cpu_rate(cpu); if (unlikely(sunxi_dvfs_debug)) CPUFREQ_DBG("request frequency is %u\n", target_freq); #if 0 if (unlikely(sunxi_boot_freq_lock)) { boot_freq = cur_cluster == A7_CLUSTER ? l_freq_boot : b_freq_boot; target_freq = target_freq > boot_freq ? boot_freq : target_freq; } #endif freqs.new = freq_table[cur_cluster][index].frequency; if (freqs.old == freqs.new) return 0; if (unlikely(sunxi_dvfs_debug)) CPUFREQ_DBG("target frequency find is %u, entry %u\n", freqs.new, index); if (is_bL_switching_enabled()) { if ((actual_cluster == A15_CLUSTER) && (freqs.new < SUNXI_BL_SWITCH_THRESHOLD)) new_cluster = A7_CLUSTER; else if ((actual_cluster == A7_CLUSTER) && (freqs.new > SUNXI_BL_SWITCH_THRESHOLD)) new_cluster = A15_CLUSTER; } ret = sunxi_cpufreq_set_rate(cpu, actual_cluster, new_cluster, freqs.new); #ifdef CONFIG_SCHED_SMP_DCMP for_each_online_cpu(i) { other_cluster = cpu_to_cluster(i); if (other_cluster != actual_cluster) { ret = sunxi_cpufreq_set_rate(i, other_cluster, other_cluster, freqs.new); break; } } #endif if (ret) return ret; policy->cur = freqs.new; if (unlikely(sunxi_dvfs_debug)) CPUFREQ_DBG("DVFS done! Freq[%uMHz] Volt[%umv] ok\n\n", sunxi_clk_get_cpu_rate(cpu) / 1000, sunxi_cpufreq_getvolt(cpu)); return ret; } static inline u32 get_table_count(struct cpufreq_frequency_table *table) { int count; for (count = 0; table[count].frequency != CPUFREQ_TABLE_END; count++) ; return count; } static int merge_cluster_tables(void) { int i, j, k = 0, count = 1; struct cpufreq_frequency_table *table; for (i = 0; i < MAX_CLUSTERS; i++) count += get_table_count(freq_table[i]); table = kzalloc(sizeof(*table) * count, GFP_KERNEL); if (!table) return -ENOMEM; freq_table[MAX_CLUSTERS] = table; /* Add in reverse order to get freqs in increasing order */ for (i = MAX_CLUSTERS - 1; i >= 0; i--) { for (j = 0; freq_table[i][j].frequency != CPUFREQ_TABLE_END; j++) { table[k].frequency = VIRT_FREQ(i, freq_table[i][j].frequency); CPUFREQ_DBG("%s: index: %d, freq: %d\n", __func__, k, table[k].frequency); k++; } } // table[k].index = k; table[k].frequency = CPUFREQ_TABLE_END; CPUFREQ_DBG("%s: End, table: %p, count: %d\n", __func__, table, k); return 0; } /*get the cpufreq table from dts * */ #ifdef CONFIG_ARCH_SUN8IW17P1 static int init_cpufreq_table_dt(void) { struct device_node *np; const struct property *prop; int cnt, i; const __be32 *val; np = of_find_node_by_path("/cpus/cpu@0"); if (!np) { CPUFREQ_ERR("No cpu node found\n"); return -ENODEV; } prop = of_find_property(np, "cpufreq_tbl", NULL); if (!prop || !prop->value) { CPUFREQ_ERR("Invalid cpufreq_tbl\n"); return -ENODEV; } cnt = prop->length / sizeof(u32); val = prop->value; for (i = 0; i < cnt; i++) { // sunxi_freq_table_ca7[i].index = i; sunxi_freq_table_ca7[i].frequency = be32_to_cpup(val++); // sunxi_freq_table_ca15[i].index = i; sunxi_freq_table_ca15[i].frequency = sunxi_freq_table_ca7[i].frequency; } // sunxi_freq_table_ca7[i].index = i; // sunxi_freq_table_ca15[i].index = i; sunxi_freq_table_ca15[i].frequency = CPUFREQ_TABLE_END; sunxi_freq_table_ca7[i].frequency = CPUFREQ_TABLE_END; return 0; } #endif /* * init cpu max/min frequency from dt; * return: 0 - init cpu max/min successed, !0 - init cpu max/min failed; */ static int __init_freq_dt(char *tbl_name) { int ret = 0; struct device_node *main_np; struct device_node *sub_np; main_np = of_find_node_by_path("/dvfs_table"); if (!main_np) { CPUFREQ_ERR("No dvfs table node found\n"); ret = -ENODEV; goto fail; } sub_np = of_find_compatible_node(main_np, NULL, tbl_name); if (!sub_np) { CPUFREQ_ERR("No %s node found\n", tbl_name); ret = -ENODEV; goto fail; } if (of_property_read_u32(sub_np, "L_max_freq", &l_freq_max)) { CPUFREQ_ERR("get cpu l_max frequency from dt failed\n"); ret = -EINVAL; goto fail; } if (of_property_read_u32(sub_np, "L_min_freq", &l_freq_min)) { CPUFREQ_ERR("get cpu l_min frequency from dt failed\n"); ret = -EINVAL; goto fail; } if (of_property_read_u32(sub_np, "L_extremity_freq", &l_freq_ext)) l_freq_ext = l_freq_max; if (of_property_read_u32(sub_np, "L_boot_freq", &l_freq_boot)) l_freq_boot = l_freq_max; else sunxi_boot_freq_lock = 1; if (of_property_read_u32(sub_np, "B_max_freq", &b_freq_max)) { CPUFREQ_ERR("get cpu b_max frequency from dt failed\n"); ret = -EINVAL; goto fail; } if (of_property_read_u32(sub_np, "B_min_freq", &b_freq_min)) { CPUFREQ_ERR("get cpu b_min frequency from dt failed\n"); ret = -EINVAL; goto fail; } if (of_property_read_u32(sub_np, "B_extremity_freq", &b_freq_ext)) b_freq_ext = b_freq_max; if (of_property_read_u32(sub_np, "B_boot_freq", &b_freq_boot)) b_freq_boot = b_freq_max; if (l_freq_max > SUNXI_CPUFREQ_L_MAX || l_freq_max < SUNXI_CPUFREQ_L_MIN || l_freq_min < SUNXI_CPUFREQ_L_MIN || l_freq_min > SUNXI_CPUFREQ_L_MAX) { CPUFREQ_ERR("l_cpu max or min frequency from sysconfig is more than range\n"); ret = -EINVAL; goto fail; } if (l_freq_min > l_freq_max) { CPUFREQ_ERR("l_cpu min frequency can not be more than l_cpu max frequency\n"); ret = -EINVAL; goto fail; } if (l_freq_ext < l_freq_max) { CPUFREQ_ERR("l_cpu ext frequency can not be less than l_cpu max frequency\n"); ret = -EINVAL; goto fail; } if (l_freq_boot > l_freq_max || l_freq_boot < l_freq_min) { CPUFREQ_ERR("l_cpu boot frequency invalid\n"); ret = -EINVAL; goto fail; } if (b_freq_max > SUNXI_CPUFREQ_B_MAX || b_freq_max < SUNXI_CPUFREQ_B_MIN || b_freq_min < SUNXI_CPUFREQ_B_MIN || b_freq_min > SUNXI_CPUFREQ_B_MAX) { CPUFREQ_ERR("b_cpu max or min frequency from sysconfig is more than range\n"); ret = -EINVAL; goto fail; } if (b_freq_min > b_freq_max) { CPUFREQ_ERR("b_cpu min frequency can not be more than b_cpu max frequency\n"); ret = -EINVAL; goto fail; } if (b_freq_ext < b_freq_max) { CPUFREQ_ERR("b_cpu ext frequency can not be less than b_cpu max frequency\n"); ret = -EINVAL; goto fail; } if (b_freq_boot > b_freq_max || b_freq_boot < b_freq_min) { CPUFREQ_ERR("b_cpu boot frequency invalid\n"); ret = -EINVAL; goto fail; } l_freq_max /= 1000; l_freq_min /= 1000; l_freq_ext /= 1000; l_freq_boot /= 1000; b_freq_max /= 1000; b_freq_min /= 1000; b_freq_ext /= 1000; b_freq_boot /= 1000; return 0; fail: /* use default cpu max/min frequency */ l_freq_max = SUNXI_CPUFREQ_L_MAX / 1000; l_freq_min = SUNXI_CPUFREQ_L_MIN / 1000; l_freq_ext = l_freq_max; l_freq_boot = l_freq_max; b_freq_max = SUNXI_CPUFREQ_B_MAX / 1000; b_freq_min = SUNXI_CPUFREQ_B_MIN / 1000; b_freq_ext = b_freq_max; b_freq_boot = b_freq_max; return ret; } static int sunxi_get_vf_table(void) { int cluster = 0; int index = 0; struct device_node *dvfs_main_np; struct device_node *dvfs_sub_np; unsigned int table_count; char cluster_name[2] = {'L', 'B'}; int vf_table_type = 0; int vf_table_size[2] = {0, 0}; char vf_table_main_key[64]; char vf_table_sub_key[64]; dvfs_main_np = of_find_node_by_path("/dvfs_table"); if (!dvfs_main_np) { CPUFREQ_ERR("No dvfs table node found\n"); return -ENODEV; } if (of_property_read_u32(dvfs_main_np, "vf_table_count", &table_count)) { CPUFREQ_ERR("get vf_table_count failed\n"); return -EINVAL; } if (table_count == 1) { CPUFREQ_DBG("%s: support only one vf_table\n", __func__); vf_table_type = 0; } else vf_table_type = sunxi_get_soc_bin(); sprintf(vf_table_main_key, "%s%d", "allwinner,vf_table", vf_table_type); CPUFREQ_DBG("%s: vf table type [%d=%s]\n", __func__, vf_table_type, vf_table_main_key); /* parse system config v-f table information */ for (cluster = 0; cluster < 1; cluster++) { sprintf(vf_table_sub_key, "%c_LV_count", cluster_name[cluster]); dvfs_sub_np = of_find_compatible_node(dvfs_main_np, NULL, vf_table_main_key); if (!dvfs_sub_np) { CPUFREQ_ERR("No %s node found\n", vf_table_main_key); return -ENODEV; } if (of_property_read_u32(dvfs_sub_np, vf_table_sub_key, &vf_table_size[cluster])) { CPUFREQ_ERR("get %s failed\n", vf_table_sub_key); return -EINVAL; } for (index = 0; index < vf_table_size[cluster]; index++) { sprintf(vf_table_sub_key, "%c_LV%d_freq", cluster_name[cluster], index + 1); if (of_property_read_u32(dvfs_sub_np, vf_table_sub_key, &arisc_vf_table[cluster][index].freq)) { CPUFREQ_ERR("get %s failed\n", vf_table_sub_key); return -EINVAL; } CPUFREQ_DBG("%s: freq [%s-%d=%d]\n", __func__, vf_table_sub_key, index, arisc_vf_table[cluster][index].freq); sprintf(vf_table_sub_key, "%c_LV%d_volt", cluster_name[cluster], index + 1); if (of_property_read_u32(dvfs_sub_np, vf_table_sub_key, &arisc_vf_table[cluster][index].voltage)) { CPUFREQ_ERR("get %s failed\n", vf_table_sub_key); return -EINVAL; } CPUFREQ_DBG("%s: volt [%s-%d=%d]\n", __func__, vf_table_sub_key, index, arisc_vf_table[cluster][index].voltage); } } return index; } static int sunxi_set_vf_table(u32 num) { void *kvir = NULL; kvir = kmalloc(num * sizeof(struct cpufreq_frequency_table), GFP_KERNEL); if (kvir == NULL) { CPUFREQ_ERR("kmalloc error for transmiting vf table\n"); return -1; } memcpy((void *)kvir, (void *)(&arisc_vf_table[0]), num * sizeof(struct cpufreq_frequency_table)); __dma_flush_area((void *)kvir, num * sizeof(struct cpufreq_frequency_table)); arisc_dvfs_cfg_vf_table(0, num, virt_to_phys(kvir)); kfree(kvir); kvir = NULL; return 0; } static int sunxi_cpufreq_cpu_init(struct cpufreq_policy *policy) { u32 cur_cluster = cpu_to_cluster(policy->cpu); u32 cpu; /* set cpu freq-table */ if (is_bL_switching_enabled()) { /* bL switcher use the merged freq table */ cpufreq_table_validate_and_show(policy, freq_table[MAX_CLUSTERS]); } else { /* HMP use the per-cluster freq table */ cpufreq_table_validate_and_show(policy, freq_table[cur_cluster]); } /* set the target cluster of cpu */ if (cur_cluster < MAX_CLUSTERS) { /* set cpu masks */ #ifdef CONFIG_SCHED_SMP_DCMP cpumask_copy(policy->cpus, cpu_possible_mask); #else cpumask_copy(policy->cpus, topology_core_cpumask(policy->cpu)); #endif /* set core sibling */ for_each_cpu(cpu, topology_core_cpumask(policy->cpu)) { per_cpu(physical_cluster, cpu) = cur_cluster; } } else { /* Assumption: during init, we are always running on A7 */ per_cpu(physical_cluster, policy->cpu) = A7_CLUSTER; } /* initialize current cpu freq */ policy->cur = sunxi_clk_get_cpu_rate(policy->cpu); per_cpu(cpu_last_req_freq, policy->cpu) = policy->cur; if (unlikely(sunxi_dvfs_debug)) CPUFREQ_DBG("cpu:%d, cluster:%d, init freq:%d\n", policy->cpu, cur_cluster, policy->cur); /* set the policy min and max freq */ if (is_bL_switching_enabled()) { /* bL switcher use the merged freq table */ cpufreq_frequency_table_cpuinfo(policy, freq_table[MAX_CLUSTERS]); } else { /* HMP use the per-cluster freq table */ if (cur_cluster == A7_CLUSTER) { policy->min = policy->cpuinfo.min_freq = l_freq_min; policy->cpuinfo.max_freq = l_freq_ext; policy->max = l_freq_max; // policy->cpuinfo.boot_freq = l_freq_boot; } else if (cur_cluster == A15_CLUSTER) { policy->min = policy->cpuinfo.min_freq = b_freq_min; policy->cpuinfo.max_freq = b_freq_ext; policy->max = b_freq_max; // policy->cpuinfo.boot_freq = b_freq_boot; } } /* set the transition latency value */ policy->cpuinfo.transition_latency = SUNXI_FREQTRANS_LATENCY; return 0; } /* Export freq_table to sysfs */ static struct freq_attr *sunxi_cpufreq_attr[] = { &cpufreq_freq_attr_scaling_available_freqs, NULL, }; static struct cpufreq_driver sunxi_cpufreq_driver = { .name = "sunxi-iks", .flags = CPUFREQ_STICKY, .verify = sunxi_cpufreq_verify_policy, .target_index = sunxi_cpufreq_set_target_index, .get = sunxi_clk_get_cpu_rate, .init = sunxi_cpufreq_cpu_init, // .have_governor_per_policy = true, .attr = sunxi_cpufreq_attr, }; static int sunxi_cpufreq_switcher_notifier(struct notifier_block *nfb, unsigned long action, void *_arg) { pr_info("%s: action:%lu\n", __func__, action); switch (action) { case BL_NOTIFY_PRE_ENABLE: case BL_NOTIFY_PRE_DISABLE: cpufreq_unregister_driver(&sunxi_cpufreq_driver); break; case BL_NOTIFY_POST_ENABLE: set_switching_enabled(true); cpufreq_register_driver(&sunxi_cpufreq_driver); break; case BL_NOTIFY_POST_DISABLE: set_switching_enabled(false); cpufreq_register_driver(&sunxi_cpufreq_driver); break; default: return NOTIFY_DONE; } return NOTIFY_OK; } static struct notifier_block sunxi_switcher_notifier = { .notifier_call = sunxi_cpufreq_switcher_notifier, }; static int __init sunxi_cpufreq_init(void) { int ret, i; char vftbl_name[20] = {0}; int vf_table_type = 0; unsigned int vf_table_count = 0; int num; struct device_node *np; ret = bL_switcher_get_enabled(); set_switching_enabled(ret); /* get pll1 pll2 clock handle */ clk_pll1 = clk_get(NULL, PLL1_CLK); if (IS_ERR_OR_NULL(clk_pll1)) pr_err("get clk_pll1 clk handle err or null\n"); clk_pll2 = clk_get(NULL, PLL2_CLK); if (IS_ERR_OR_NULL(clk_pll2)) pr_err("get clk_pll2 clk handle err or null\n"); /* get cluster clock handle */ cluster_clk[A7_CLUSTER] = clk_get(NULL, CLUSTER0_CLK); if (IS_ERR_OR_NULL(cluster_clk[A7_CLUSTER])) pr_err("get cluster_clk[%d] clk handle err or null\n", A7_CLUSTER); cluster_clk[A15_CLUSTER] = clk_get(NULL, CLUSTER1_CLK); if (IS_ERR_OR_NULL(cluster_clk[A15_CLUSTER])) pr_err("get cluster_clk[%d] clk handle err or null\n", A15_CLUSTER); pr_info("cluster_clk[%d] = %lu\n", A7_CLUSTER, clk_get_rate(cluster_clk[A7_CLUSTER])); pr_info("cluster_clk[%d] = %lu\n", A15_CLUSTER, clk_get_rate(cluster_clk[A15_CLUSTER])); #ifdef CONFIG_ARCH_SUN8IW17P1 init_cpufreq_table_dt(); #endif /* initialize cpufreq table and merge ca7/ca15 table */ freq_table[A7_CLUSTER] = sunxi_freq_table_ca7; freq_table[A15_CLUSTER] = sunxi_freq_table_ca15; merge_cluster_tables(); /* initialize ca7 and ca15 cluster pll number */ cluster_pll[A7_CLUSTER] = ARISC_DVFS_PLL1; cluster_pll[A15_CLUSTER] = ARISC_DVFS_PLL2; cpu_vdd[A7_CLUSTER] = regulator_get(NULL, SUNXI_CPUFREQ_C0_CPUVDD); if (IS_ERR(cpu_vdd[A7_CLUSTER])) { CPUFREQ_ERR("%s: could not get Cluster0 cpu vdd\n", __func__); return -ENOENT; } cpu_vdd[A15_CLUSTER] = regulator_get(NULL, SUNXI_CPUFREQ_C1_CPUVDD); if (IS_ERR(cpu_vdd[A15_CLUSTER])) { regulator_put(cpu_vdd[A7_CLUSTER]); CPUFREQ_ERR("%s: could not get Cluster1 cpu vdd\n", __func__); return -ENOENT; } np = of_find_node_by_path("/dvfs_table"); if (!np) { CPUFREQ_ERR("No dvfs table node found\n"); return -ENODEV; } if (of_property_read_u32(np, "vf_table_count", &vf_table_count)) { CPUFREQ_ERR("get vf_table_count failed\n"); return -EINVAL; } if (vf_table_count == 1) { CPUFREQ_DBG("support only one vf_table\n"); vf_table_type = 0; } else vf_table_type = sunxi_get_soc_bin(); sprintf(vftbl_name, "%s%d", "allwinner,vf_table", vf_table_type); /* init cpu extreme frequency from dts */ if (__init_freq_dt(vftbl_name)) { CPUFREQ_ERR("%s, use default cpu max/min frequency, l_max: %uMHz, l_min: %uMHz, b_max: %uMHz, b_min: %uMHz\n", __func__, l_freq_max/1000, l_freq_min/1000, b_freq_max/1000, b_freq_min/1000); } else { CPUFREQ_DBG("%s, get cpu frequency from sysconfig, l_max: %uMHz, l_min: %uMHz, b_max: %uMHz, b_min: %uMHz\n", __func__, l_freq_max/1000, l_freq_min/1000, b_freq_max/1000, b_freq_min/1000); } for (i = 0; i < MAX_CLUSTERS; i++) mutex_init(&cluster_lock[i]); num = sunxi_get_vf_table(); sunxi_set_vf_table(num); ret = cpufreq_register_driver(&sunxi_cpufreq_driver); if (ret) { CPUFREQ_ERR("sunxi register cpufreq driver fail, err: %d\n", ret); } else { CPUFREQ_DBG("sunxi register cpufreq driver succeed\n"); ret = bL_switcher_register_notifier(&sunxi_switcher_notifier); if (ret) { CPUFREQ_ERR("sunxi register bL notifier fail, err: %d\n", ret); cpufreq_unregister_driver(&sunxi_cpufreq_driver); } else { CPUFREQ_DBG("sunxi register bL notifier succeed\n"); } } bL_switcher_put_enabled(); pr_debug("%s: done!\n", __func__); return ret; } module_init(sunxi_cpufreq_init); static void __exit sunxi_cpufreq_exit(void) { int i; for (i = 0; i < MAX_CLUSTERS; i++) mutex_destroy(&cluster_lock[i]); clk_put(clk_pll1); clk_put(clk_pll2); clk_put(cluster_clk[A7_CLUSTER]); clk_put(cluster_clk[A15_CLUSTER]); regulator_put(cpu_vdd[A7_CLUSTER]); regulator_put(cpu_vdd[A15_CLUSTER]); bL_switcher_get_enabled(); bL_switcher_unregister_notifier(&sunxi_switcher_notifier); cpufreq_unregister_driver(&sunxi_cpufreq_driver); bL_switcher_put_enabled(); CPUFREQ_DBG("%s: done!\n", __func__); } module_exit(sunxi_cpufreq_exit); #ifdef CONFIG_DEBUG_FS static struct dentry *debugfs_cpufreq_root; static int get_c0_time_show(struct seq_file *s, void *data) { seq_printf(s, "%llu\n", c0_get_time_usecs); return 0; } static int get_c0_time_open(struct inode *inode, struct file *file) { return single_open(file, get_c0_time_show, inode->i_private); } static const struct file_operations get_c0_time_fops = { .open = get_c0_time_open, .read = seq_read, }; static int set_c0_time_show(struct seq_file *s, void *data) { seq_printf(s, "%llu\n", c0_set_time_usecs); return 0; } static int set_c0_time_open(struct inode *inode, struct file *file) { return single_open(file, set_c0_time_show, inode->i_private); } static const struct file_operations set_c0_time_fops = { .open = set_c0_time_open, .read = seq_read, }; static int get_c1_time_show(struct seq_file *s, void *data) { seq_printf(s, "%llu\n", c1_get_time_usecs); return 0; } static int get_c1_time_open(struct inode *inode, struct file *file) { return single_open(file, get_c1_time_show, inode->i_private); } static const struct file_operations get_c1_time_fops = { .open = get_c1_time_open, .read = seq_read, }; static int set_c1_time_show(struct seq_file *s, void *data) { seq_printf(s, "%llu\n", c1_set_time_usecs); return 0; } static int set_c1_time_open(struct inode *inode, struct file *file) { return single_open(file, set_c1_time_show, inode->i_private); } static const struct file_operations set_c1_time_fops = { .open = set_c1_time_open, .read = seq_read, }; static int __init debug_init(void) { int err = 0; debugfs_cpufreq_root = debugfs_create_dir("cpufreq", 0); if (!debugfs_cpufreq_root) return -ENOMEM; if (!debugfs_create_file("c0_get_time", 0444, debugfs_cpufreq_root, NULL, &get_c0_time_fops)) { err = -ENOMEM; goto out; } if (!debugfs_create_file("c0_set_time", 0444, debugfs_cpufreq_root, NULL, &set_c0_time_fops)) { err = -ENOMEM; goto out; } if (!debugfs_create_file("c1_get_time", 0444, debugfs_cpufreq_root, NULL, &get_c1_time_fops)) { err = -ENOMEM; goto out; } if (!debugfs_create_file("c1_set_time", 0444, debugfs_cpufreq_root, NULL, &set_c1_time_fops)) { err = -ENOMEM; goto out; } return 0; out: debugfs_remove_recursive(debugfs_cpufreq_root); return err; } static void __exit debug_exit(void) { debugfs_remove_recursive(debugfs_cpufreq_root); } late_initcall(debug_init); module_exit(debug_exit); #endif /* CONFIG_DEBUG_FS */ MODULE_AUTHOR("sunny "); MODULE_DESCRIPTION("sunxi iks cpufreq driver"); MODULE_LICENSE("GPL");