672 lines
16 KiB
C
Executable File
672 lines
16 KiB
C
Executable File
/*
|
|
* drivers/cpufreq/sunxi-cpufreq.c
|
|
*
|
|
* Copyright (c) 2014 softwinner.
|
|
*
|
|
* 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.
|
|
*
|
|
* 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/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/cpufreq.h>
|
|
#include <linux/cpu.h>
|
|
#include <linux/io.h>
|
|
#include <linux/of.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/err.h>
|
|
#include <linux/regulator/consumer.h>
|
|
#include <linux/pm_opp.h>
|
|
#include <linux/arisc/arisc.h>
|
|
#include <linux/sunxi-sid.h>
|
|
#include <asm/cacheflush.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/sunxi-cpufreq.h>
|
|
#include <linux/delay.h>
|
|
#ifdef CONFIG_SUNXI_ARISC
|
|
#include <linux/arisc/arisc.h>
|
|
#endif
|
|
|
|
#define CPUFREQ_DBG(format, args...) \
|
|
pr_debug("[cpu_freq] DBG: "format, ##args)
|
|
#define CPUFREQ_ERR(format, args...) \
|
|
pr_err("[cpu_freq] ERR: "format, ##args)
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
/* sunxi CPUFreq driver data structure */
|
|
static struct {
|
|
s64 cpufreq_set_us;
|
|
s64 cpufreq_get_us;
|
|
} sunxi_cpufreq;
|
|
#endif
|
|
|
|
/* cpufreq_dvfs_table is global default dvfs table */
|
|
static struct cpufreq_dvfs_table cpufreq_dvfs_table[DVFS_VF_TABLE_MAX] = {
|
|
/*
|
|
* cluster0
|
|
* 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}
|
|
};
|
|
|
|
#ifdef CONFIG_ARM_SUNXI_PSENSOR_BIN
|
|
struct psensor_range {
|
|
unsigned int min;
|
|
unsigned int max;
|
|
};
|
|
|
|
static struct psensor_range psensor_range[8];
|
|
static int psensor_range_count;
|
|
#define PSENSOR_REG 0x0300621c
|
|
#endif
|
|
|
|
#ifdef CONFIG_SCHED_SMP_DCMP
|
|
#ifdef CONFIG_ARCH_SUN8IW17P1
|
|
#define CLUSTER_MAX_CORES 3
|
|
struct clk *cluster1_clk;
|
|
#endif
|
|
#endif
|
|
|
|
extern unsigned long dev_pm_opp_axi_bus_divide_ratio(struct dev_pm_opp *opp);
|
|
extern int dev_pm_opp_of_get_sharing_cpus_by_soc_bin(struct device *cpu_dev,
|
|
cpumask_var_t cpumask, int soc_bin);
|
|
extern int dev_pm_opp_of_cpumask_add_table_by_soc_bin(cpumask_var_t cpumask,
|
|
int soc_bin);
|
|
|
|
|
|
static unsigned int sunxi_cpufreq_get(unsigned int cpu)
|
|
{
|
|
unsigned int current_freq = 0;
|
|
#ifdef CONFIG_DEBUG_FS
|
|
ktime_t calltime = ktime_get();
|
|
#endif
|
|
|
|
current_freq = cpufreq_generic_get(cpu);
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
sunxi_cpufreq.cpufreq_get_us =
|
|
ktime_to_us(ktime_sub(ktime_get(), calltime));
|
|
#endif
|
|
|
|
return current_freq;
|
|
}
|
|
|
|
#ifdef CONFIG_SUNXI_ARISC
|
|
static int sunxi_set_cpufreq_and_voltage(struct cpufreq_policy *policy,
|
|
unsigned long freq)
|
|
{
|
|
int ret = 0;
|
|
|
|
#ifdef CONFIG_SCHED_SMP_DCMP
|
|
int cpu;
|
|
#endif
|
|
|
|
#ifdef CONFIG_SUNXI_CPUFREQ_ASYN
|
|
unsigned long timeout;
|
|
|
|
arisc_dvfs_set_cpufreq(freq, ARISC_DVFS_PLL1, ARISC_DVFS_ASYN,
|
|
NULL, NULL);
|
|
/* CPUS max latency for cpu freq*/
|
|
timeout = 15;
|
|
|
|
#ifdef CONFIG_SCHED_SMP_DCMP
|
|
for_each_online_cpu(cpu) {
|
|
if (cpu >= CLUSTER_MAX_CORES) {
|
|
// CPUFREQ_DBG("Set other cluster freq: %d\n", freq);
|
|
arisc_dvfs_set_cpufreq(freq, ARISC_DVFS_PLL2,
|
|
ARISC_DVFS_ASYN, NULL, NULL);
|
|
break;
|
|
}
|
|
}
|
|
while (timeout-- && (clk_get_rate(policy->clk) != freq*1000) &&
|
|
(clk_get_rate(policy->clk) != freq*1000))
|
|
usleep_range(900, 1000);
|
|
if ((clk_get_rate(policy->clk) != freq*1000) || (clk_get_rate(policy->clk) != freq*1000))
|
|
ret = -1;
|
|
#else
|
|
while (timeout-- && (clk_get_rate(policy->clk) != freq*1000))
|
|
msleep(1);
|
|
if (clk_get_rate(policy->clk) != freq*1000)
|
|
ret = -1;
|
|
#endif
|
|
|
|
#else
|
|
|
|
ret = arisc_dvfs_set_cpufreq(freq, ARISC_DVFS_PLL1, ARISC_DVFS_SYN,
|
|
NULL, NULL);
|
|
#ifdef CONFIG_SCHED_SMP_DCMP
|
|
if (ret)
|
|
return ret;
|
|
for_each_online_cpu(cpu) {
|
|
if (cpu >= CLUSTER_MAX_CORES) {
|
|
// CPUFREQ_DBG("Set other cluster freq: %d\n", freq);
|
|
ret = arisc_dvfs_set_cpufreq(freq, ARISC_DVFS_PLL2, ARISC_DVFS_SYN,
|
|
NULL, NULL);
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#endif
|
|
return ret;
|
|
}
|
|
#else
|
|
static int sunxi_set_cpufreq_and_voltage(struct cpufreq_policy *policy,
|
|
unsigned long freq)
|
|
{
|
|
struct device *cpu_dev;
|
|
|
|
cpu_dev = get_cpu_device(policy->cpu);
|
|
|
|
return dev_pm_opp_set_rate(cpu_dev, freq * 1000);
|
|
}
|
|
#endif
|
|
|
|
static int sunxi_cpufreq_target_index(struct cpufreq_policy *policy,
|
|
unsigned int index)
|
|
{
|
|
int ret = 0;
|
|
unsigned long freq;
|
|
#ifdef CONFIG_DEBUG_FS
|
|
ktime_t calltime;
|
|
#endif
|
|
|
|
freq = policy->freq_table[index].frequency;
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
calltime = ktime_get();
|
|
#endif
|
|
/* try to set cpu frequency */
|
|
ret = sunxi_set_cpufreq_and_voltage(policy, freq);
|
|
if (ret)
|
|
CPUFREQ_ERR("Set cpu frequency to %luKHz failed!\n", freq);
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
sunxi_cpufreq.cpufreq_set_us = ktime_to_us(ktime_sub(ktime_get(),
|
|
calltime));
|
|
#endif
|
|
|
|
return ret;
|
|
}
|
|
static int sunxi_cpufreq_set_vf(struct cpufreq_frequency_table *table,
|
|
unsigned int cpu)
|
|
{
|
|
struct cpufreq_frequency_table *pos;
|
|
struct dev_pm_opp *opp;
|
|
struct device *dev;
|
|
unsigned long freq;
|
|
int ret, num = 0;
|
|
int max_opp_num = 0;
|
|
void *kvir = NULL;
|
|
|
|
dev = get_cpu_device(cpu);
|
|
max_opp_num = dev_pm_opp_get_opp_count(dev);
|
|
|
|
#ifndef CONFIG_ARCH_SUN8IW12P1
|
|
for (pos = table; max_opp_num > 0; --max_opp_num) {
|
|
freq = pos[max_opp_num - 1].frequency * 1000;
|
|
#else
|
|
cpufreq_for_each_valid_entry(pos, table) {
|
|
freq = pos->frequency * 1000;
|
|
#endif
|
|
CPUFREQ_DBG("freq: %lu\n", freq);
|
|
rcu_read_lock();
|
|
opp = dev_pm_opp_find_freq_ceil(dev, &freq);
|
|
if (IS_ERR(opp)) {
|
|
ret = PTR_ERR(opp);
|
|
dev_err(dev,
|
|
"%s: failed to find OPP for freq %lu (%d)\n",
|
|
__func__, freq, ret);
|
|
rcu_read_unlock();
|
|
return ret;
|
|
}
|
|
|
|
cpufreq_dvfs_table[num].voltage =
|
|
dev_pm_opp_get_voltage(opp) / 1000;
|
|
cpufreq_dvfs_table[num].freq = dev_pm_opp_get_freq(opp);
|
|
cpufreq_dvfs_table[num].axi_div =
|
|
dev_pm_opp_axi_bus_divide_ratio(opp);
|
|
rcu_read_unlock();
|
|
CPUFREQ_DBG("num:%d, volatge:%d, freq:%d, axi_div:%d ,%s\n",
|
|
num, cpufreq_dvfs_table[num].voltage,
|
|
cpufreq_dvfs_table[num].freq,
|
|
cpufreq_dvfs_table[num].axi_div, __func__);
|
|
num++;
|
|
}
|
|
|
|
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 *)cpufreq_dvfs_table,
|
|
num * sizeof(struct cpufreq_frequency_table));
|
|
__dma_flush_area((void *)kvir, num * sizeof(struct cpufreq_frequency_table));
|
|
|
|
#ifdef CONFIG_SUNXI_ARISC
|
|
arisc_dvfs_cfg_vf_table(0, num, virt_to_phys(kvir));
|
|
#endif
|
|
|
|
kfree(kvir);
|
|
kvir = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_ARM_SUNXI_PSENSOR_BIN
|
|
static int get_psensor_range(void)
|
|
{
|
|
int i = 0;
|
|
char name[20];
|
|
int psensor_count = 0;
|
|
struct device_node *psensor_node = NULL;
|
|
|
|
psensor_node = of_find_node_by_path("/psensor_table");
|
|
if (psensor_node == NULL) {
|
|
pr_err("%s(%d) has no pensor node\n", __func__, __LINE__);
|
|
return -1;
|
|
}
|
|
|
|
if (of_property_read_u32(psensor_node, "psensor_count",
|
|
&psensor_count)) {
|
|
pr_err("%s(%d) get psensor count error\n", __func__, __LINE__);
|
|
return -1;
|
|
}
|
|
psensor_range_count = psensor_count;
|
|
for (i = 0; i < psensor_count; i++) {
|
|
sprintf(name, "prange_min_%d", i);
|
|
if (of_property_read_u32(psensor_node, name,
|
|
&psensor_range[i].min)) {
|
|
pr_err("get prange_min_%d failed\n", i);
|
|
return -1;
|
|
}
|
|
|
|
sprintf(name, "prange_max_%d", i);
|
|
if (of_property_read_u32(psensor_node, name,
|
|
&psensor_range[i].max)) {
|
|
pr_err("get prange_max_%d failed\n", i);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < psensor_count; ++i) {
|
|
pr_debug("psensor_range_%d.min=%d; psensor_range_%d.max=%d\n",
|
|
i, psensor_range[i].min, i, psensor_range[i].max);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int get_psensor_bin(void)
|
|
{
|
|
unsigned int soc_bin;
|
|
void __iomem *bin_reg = NULL;
|
|
int i;
|
|
|
|
bin_reg = ioremap(PSENSOR_REG, 16);
|
|
|
|
soc_bin = readl(bin_reg);
|
|
|
|
/*use the high 16 bit*/
|
|
soc_bin >>= 16;
|
|
|
|
iounmap(bin_reg);
|
|
|
|
for (i = 0; i < psensor_range_count; ++i) {
|
|
if (soc_bin >= psensor_range[i].min && soc_bin <=
|
|
psensor_range[i].max)
|
|
break;
|
|
}
|
|
|
|
if (i >= psensor_range_count)
|
|
return -1;
|
|
|
|
return i;
|
|
}
|
|
#endif
|
|
|
|
static int sunxi_cpufreq_init(struct cpufreq_policy *policy)
|
|
{
|
|
struct device *cpu_dev;
|
|
struct cpufreq_frequency_table *freq_table;
|
|
struct dev_pm_opp *suspend_opp;
|
|
struct clk *cpu_clk;
|
|
const char *regulator_name;
|
|
struct opp_table *table;
|
|
unsigned int transition_latency;
|
|
int ret, soc_bin;
|
|
unsigned int table_count;
|
|
struct device_node *dvfs_main_np;
|
|
#ifdef CONFIG_SCHED_SMP_DCMP
|
|
struct device *cluster1_cpu_dev;
|
|
#endif
|
|
|
|
dvfs_main_np = of_find_node_by_path("/opp_dvfs_table");
|
|
if (!dvfs_main_np) {
|
|
CPUFREQ_ERR("No opp dvfs table node found\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
#ifdef CONFIG_ARM_SUNXI_PSENSOR_BIN
|
|
ret = get_psensor_range();
|
|
if (ret < 0)
|
|
return -EINVAL;
|
|
#endif
|
|
if (of_property_read_u32(dvfs_main_np, "opp_table_count",
|
|
&table_count)) {
|
|
CPUFREQ_ERR("get vf_table_count failed\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (table_count == 1) {
|
|
pr_info("%s: only one opp_table\n", __func__);
|
|
soc_bin = 0;
|
|
} else {
|
|
#ifdef CONFIG_ARM_SUNXI_PSENSOR_BIN
|
|
soc_bin = get_psensor_bin();
|
|
#else
|
|
soc_bin = sunxi_get_soc_bin();
|
|
if (soc_bin == 0)
|
|
soc_bin = 2;
|
|
|
|
if (soc_bin == 1 || soc_bin == 2 || soc_bin == 3)
|
|
soc_bin--;
|
|
#endif
|
|
if (soc_bin < 0) {
|
|
pr_err("%s: get the wrong soc bin!\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
pr_info("%s: support more opp_table and soc bin is %d\n",
|
|
__func__, soc_bin);
|
|
}
|
|
|
|
cpu_dev = get_cpu_device(policy->cpu);
|
|
CPUFREQ_ERR("DEBUG: get cpu %d device\n", policy->cpu);
|
|
if (!cpu_dev) {
|
|
CPUFREQ_ERR("Failed to get cpu%d device\n", policy->cpu);
|
|
return -ENODEV;
|
|
}
|
|
|
|
cpu_clk = clk_get(cpu_dev, NULL);
|
|
if (IS_ERR_OR_NULL(cpu_clk)) {
|
|
ret = PTR_ERR(cpu_clk);
|
|
CPUFREQ_ERR("Unable to get PLL CPU clock\n");
|
|
return ret;
|
|
}
|
|
policy->clk = cpu_clk;
|
|
|
|
#ifdef CONFIG_SCHED_SMP_DCMP
|
|
cluster1_cpu_dev = get_cpu_device(CLUSTER_MAX_CORES);
|
|
if (!cluster1_cpu_dev) {
|
|
CPUFREQ_ERR("Failed to get other cluster cpu%d device\n", policy->cpu);
|
|
return -ENODEV;
|
|
}
|
|
cluster1_clk = clk_get(cluster1_cpu_dev, NULL);
|
|
if (IS_ERR_OR_NULL(cluster1_clk)) {
|
|
ret = PTR_ERR(cluster1_clk);
|
|
CPUFREQ_ERR("Unable to get OTHER cluster PLL CPU clock\n");
|
|
return ret;
|
|
}
|
|
#endif
|
|
regulator_name = of_get_property(cpu_dev->of_node, "regulators", NULL);
|
|
if (!regulator_name) {
|
|
CPUFREQ_ERR("Unable to get regulator\n");
|
|
goto lable_1;
|
|
}
|
|
|
|
table = dev_pm_opp_set_regulator(cpu_dev, regulator_name);
|
|
if (!table) {
|
|
CPUFREQ_ERR("Failed to set regulator for cpu\n");
|
|
goto out_err_clk_pll;
|
|
}
|
|
|
|
lable_1:
|
|
/* set policy->cpus according to operating-points-v2 */
|
|
ret = dev_pm_opp_of_get_sharing_cpus_by_soc_bin(cpu_dev,
|
|
policy->cpus, soc_bin);
|
|
if (ret) {
|
|
CPUFREQ_ERR("OPP-v2 opp-shared Error\n");
|
|
goto out_err_clk_pll;
|
|
}
|
|
|
|
ret = dev_pm_opp_of_cpumask_add_table_by_soc_bin(policy->cpus,
|
|
soc_bin);
|
|
if (ret) {
|
|
CPUFREQ_ERR("Failed to add opp table\n");
|
|
goto out_err_clk_pll;
|
|
}
|
|
|
|
ret = dev_pm_opp_init_cpufreq_table(cpu_dev, &freq_table);
|
|
if (ret) {
|
|
CPUFREQ_ERR("Failed to init cpufreq table: %d\n", ret);
|
|
goto out_err_free_opp;
|
|
}
|
|
|
|
ret = cpufreq_table_validate_and_show(policy, freq_table);
|
|
if (ret) {
|
|
CPUFREQ_ERR("Invalid frequency table: %d\n", ret);
|
|
goto out_err_free_opp;
|
|
}
|
|
|
|
transition_latency = dev_pm_opp_get_max_transition_latency(cpu_dev);
|
|
if (!transition_latency)
|
|
transition_latency = CPUFREQ_ETERNAL;
|
|
policy->cpuinfo.transition_latency = transition_latency;
|
|
|
|
rcu_read_lock();
|
|
suspend_opp = dev_pm_opp_get_suspend_opp(cpu_dev);
|
|
if (suspend_opp)
|
|
policy->suspend_freq = dev_pm_opp_get_freq(suspend_opp) / 1000;
|
|
rcu_read_unlock();
|
|
|
|
ret = sunxi_cpufreq_set_vf(freq_table, policy->cpu);
|
|
if (ret) {
|
|
CPUFREQ_ERR("sunxi_cpufreq_set_vf failed: %d\n", ret);
|
|
goto out_err_free_opp;
|
|
}
|
|
|
|
return 0;
|
|
|
|
out_err_free_opp:
|
|
dev_pm_opp_of_cpumask_remove_table(policy->cpus);
|
|
out_err_clk_pll:
|
|
clk_put(cpu_clk);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int sunxi_cpufreq_exit(struct cpufreq_policy *policy)
|
|
{
|
|
struct device *cpu_dev;
|
|
|
|
cpu_dev = get_cpu_device(policy->cpu);
|
|
|
|
dev_pm_opp_free_cpufreq_table(cpu_dev, &policy->freq_table);
|
|
|
|
dev_pm_opp_of_cpumask_remove_table(policy->related_cpus);
|
|
|
|
//dev_pm_opp_put_regulator(cpu_dev);
|
|
|
|
clk_put(policy->clk);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct freq_attr *sunxi_cpufreq_attr[] = {
|
|
&cpufreq_freq_attr_scaling_available_freqs,
|
|
NULL,
|
|
};
|
|
|
|
static struct cpufreq_driver sunxi_cpufreq_driver = {
|
|
.name = "cpufreq-sunxi",
|
|
.flags = CPUFREQ_STICKY | CPUFREQ_NEED_INITIAL_FREQ_CHECK,
|
|
.attr = sunxi_cpufreq_attr,
|
|
.init = sunxi_cpufreq_init,
|
|
.get = sunxi_cpufreq_get,
|
|
.target_index = sunxi_cpufreq_target_index,
|
|
.exit = sunxi_cpufreq_exit,
|
|
.verify = cpufreq_generic_frequency_table_verify,
|
|
.suspend = cpufreq_generic_suspend,
|
|
};
|
|
|
|
#ifdef CONFIG_ARCH_SUN8IW7P1
|
|
static int set_pll_cpu_lock_time(void)
|
|
{
|
|
unsigned int value;
|
|
void __iomem *lock_time_vbase = NULL;
|
|
#define PLL_CPU_LOCK_TIME_REG (0x01c20000 + 0x204)
|
|
|
|
lock_time_vbase = ioremap(PLL_CPU_LOCK_TIME_REG, 4);
|
|
if (lock_time_vbase == NULL) {
|
|
pr_err("ioremap pll cpu lock time error\n");
|
|
return -1;
|
|
}
|
|
|
|
value = readl(lock_time_vbase);
|
|
value &= ~(0xffff);
|
|
value |= 0x400;
|
|
writel(value, lock_time_vbase);
|
|
|
|
iounmap(lock_time_vbase);
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static int __init sunxi_cpufreq_initcall(void)
|
|
{
|
|
int ret;
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
sunxi_cpufreq.cpufreq_set_us = 0;
|
|
sunxi_cpufreq.cpufreq_get_us = 0;
|
|
#endif
|
|
|
|
#ifdef CONFIG_ARCH_SUN8IW7P1
|
|
if (set_pll_cpu_lock_time())
|
|
return -1;
|
|
#endif
|
|
ret = cpufreq_register_driver(&sunxi_cpufreq_driver);
|
|
if (ret)
|
|
CPUFREQ_ERR("Failed register driver\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void __exit sunxi_cpufreq_exitcall(void)
|
|
{
|
|
cpufreq_unregister_driver(&sunxi_cpufreq_driver);
|
|
}
|
|
|
|
module_init(sunxi_cpufreq_initcall);
|
|
module_exit(sunxi_cpufreq_exitcall);
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
#include <linux/debugfs.h>
|
|
|
|
static struct dentry *debugfs_cpufreq_root;
|
|
|
|
static int cpufreq_debugfs_gettime_show(struct seq_file *s, void *data)
|
|
{
|
|
seq_printf(s, "%lld\n", sunxi_cpufreq.cpufreq_get_us);
|
|
return 0;
|
|
}
|
|
|
|
static int cpufreq_debugfs_gettime_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, cpufreq_debugfs_gettime_show,
|
|
inode->i_private);
|
|
}
|
|
|
|
static const struct file_operations cpufreq_debugfs_gettime_fops = {
|
|
.open = cpufreq_debugfs_gettime_open,
|
|
.read = seq_read,
|
|
};
|
|
|
|
static int cpufreq_debugfs_settime_show(struct seq_file *s, void *data)
|
|
{
|
|
seq_printf(s, "%lld\n", sunxi_cpufreq.cpufreq_set_us);
|
|
return 0;
|
|
}
|
|
|
|
static int cpufreq_debugfs_settime_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, cpufreq_debugfs_settime_show,
|
|
inode->i_private);
|
|
}
|
|
|
|
static const struct file_operations cpufreq_debugfs_settime_fops = {
|
|
.open = cpufreq_debugfs_settime_open,
|
|
.read = seq_read,
|
|
};
|
|
|
|
static int __init cpufreq_debugfs_init(void)
|
|
{
|
|
int err = 0;
|
|
|
|
debugfs_cpufreq_root = debugfs_create_dir("cpufreq", 0);
|
|
if (!debugfs_cpufreq_root)
|
|
return -ENOMEM;
|
|
|
|
if (!debugfs_create_file("get_time", 0444, debugfs_cpufreq_root, NULL,
|
|
&cpufreq_debugfs_gettime_fops)) {
|
|
err = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
if (!debugfs_create_file("set_time", 0444, debugfs_cpufreq_root, NULL,
|
|
&cpufreq_debugfs_settime_fops)) {
|
|
err = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
return 0;
|
|
|
|
out:
|
|
debugfs_remove_recursive(debugfs_cpufreq_root);
|
|
return err;
|
|
}
|
|
|
|
static void __exit cpufreq_debugfs_exit(void)
|
|
{
|
|
debugfs_remove_recursive(debugfs_cpufreq_root);
|
|
}
|
|
|
|
late_initcall(cpufreq_debugfs_init);
|
|
module_exit(cpufreq_debugfs_exit);
|
|
|
|
#endif /* CONFIG_DEBUG_FS */
|
|
|
|
MODULE_DESCRIPTION("cpufreq driver for sunxi SOCs");
|
|
MODULE_LICENSE("GPL");
|