SmartAudio/lichee/linux-4.9/drivers/cpufreq/sunxi-iks-cpufreq.c

1259 lines
34 KiB
C
Executable File

/*
* linux/drivers/cpufreq/sunxi-iks-cpufreq.c
*
* Copyright(c) 2013-2015 Allwinnertech Co., Ltd.
* http://www.allwinnertech.com
*
* Author: sunny <sunny@allwinnertech.com>
*
* 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 <linux/clk.h>
#include <linux/cpu.h>
#include <linux/cpufreq.h>
#include <linux/cpumask.h>
#include <linux/export.h>
#include <linux/mutex.h>
//#include <linux/opp.h>
#include <linux/slab.h>
#include <linux/topology.h>
#include <linux/types.h>
#include <linux/module.h>
#include <linux/of.h>
#include <asm/bL_switcher.h>
#include <linux/arisc/arisc.h>
#include <linux/regulator/consumer.h>
#include <linux/sunxi-sid.h>
#include <linux/delay.h>
#include <asm/cacheflush.h>
#ifdef CONFIG_DEBUG_FS
#include <linux/debugfs.h>
#include <linux/hrtimer.h>
#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 <sunny@allwinnertech.com>");
MODULE_DESCRIPTION("sunxi iks cpufreq driver");
MODULE_LICENSE("GPL");