mirror of
https://github.com/hardkernel/linux.git
synced 2026-06-09 12:17:12 +09:00
cpufreq: add cpufreq drivers for m8baby
PD#141217: porting cpufreq drivers for m8baby 1. add driver source code files of cpufreq; 2. update dts and Make files; Change-Id: I905f62ec7f89ae3f1ea06de988d23d4ef009a477 Signed-off-by: Tao Zeng <tao.zeng@amlogic.com>
This commit is contained in:
@@ -13469,6 +13469,10 @@ AMLOGIC driver for thermal
|
||||
M: Tao Zeng <tao.zeng@amlogic.com>
|
||||
F: drivers/amlogic/thermal/*
|
||||
|
||||
AMLOGIC driver for cpufreq
|
||||
M: Tao Zeng <tao.zeng@amlogic.com>
|
||||
F: drivers/amlogic/cpufreq/*
|
||||
|
||||
HDMITX OUTPUT DRIVER
|
||||
M: Zongdong Jiao <zongdong.jiao@amlogic.com>
|
||||
S: Maintained
|
||||
|
||||
@@ -183,6 +183,29 @@
|
||||
*/
|
||||
};
|
||||
};
|
||||
|
||||
cpufreq-meson{
|
||||
compatible = "amlogic, cpufreq-meson";
|
||||
status = "okay";
|
||||
fixpll_target = <1536000>;
|
||||
clocks = <&clkc CLKID_CPUCLK>,
|
||||
<&clkc CLKID_PLL_SYS>;
|
||||
clock-names = "cpu_clk", "sys_clk";
|
||||
opp_table = <
|
||||
/* frequent(Khz) uV */
|
||||
96000 860000
|
||||
312000 860000
|
||||
504000 860000
|
||||
600000 860000
|
||||
720000 860000
|
||||
816000 900000
|
||||
1008000 1140000
|
||||
1200000 1140000
|
||||
1320000 1140000
|
||||
1488000 1140000
|
||||
1536000 1140000
|
||||
>;
|
||||
};
|
||||
};
|
||||
|
||||
&uart_AO {
|
||||
|
||||
@@ -33,6 +33,7 @@ CONFIG_FORCE_MAX_ZONEORDER=12
|
||||
CONFIG_ARM_APPENDED_DTB=y
|
||||
CONFIG_ARM_ATAG_DTB_COMPAT=y
|
||||
CONFIG_KEXEC=y
|
||||
CONFIG_CPU_FREQ=y
|
||||
CONFIG_CPU_IDLE=y
|
||||
CONFIG_ARM_CPUIDLE=y
|
||||
CONFIG_VFP=y
|
||||
@@ -40,6 +41,7 @@ CONFIG_NEON=y
|
||||
CONFIG_KERNEL_MODE_NEON=y
|
||||
CONFIG_NET=y
|
||||
CONFIG_AMLOGIC_DRIVER=y
|
||||
CONFIG_AMLOGIC_CPUFREQ=y
|
||||
CONFIG_AMLOGIC_UART=y
|
||||
CONFIG_AMLOGIC_SERIAL_MESON_CONSOLE=y
|
||||
CONFIG_AMLOGIC_IOMAP=y
|
||||
|
||||
@@ -12,6 +12,8 @@ config AMLOGIC_DRIVER
|
||||
if AMLOGIC_DRIVER
|
||||
menu "Amlogic Device Drivers"
|
||||
|
||||
source "drivers/amlogic/cpufreq/Kconfig"
|
||||
|
||||
source "drivers/amlogic/uart/Kconfig"
|
||||
|
||||
source "drivers/amlogic/iomap/Kconfig"
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
## Do not change.
|
||||
##########################################
|
||||
|
||||
obj-$(CONFIG_CPU_FREQ) += cpufreq/
|
||||
|
||||
obj-$(CONFIG_AMLOGIC_UART) += uart/
|
||||
|
||||
|
||||
23
drivers/amlogic/cpufreq/Kconfig
Normal file
23
drivers/amlogic/cpufreq/Kconfig
Normal file
@@ -0,0 +1,23 @@
|
||||
menuconfig AMLOGIC_CPUFREQ
|
||||
bool "AMLOGIC CPU frequency driver support"
|
||||
depends on AMLOGIC_DRIVER
|
||||
depends on CPU_FREQ
|
||||
select PM_OPP
|
||||
default n
|
||||
help
|
||||
CPU DVFS driver support for Amlogic SOC chips;
|
||||
You can use this driver to change cpu frequency.
|
||||
|
||||
choice
|
||||
prompt "Meson CPU Freq driver select"
|
||||
depends on AMLOGIC_CPUFREQ
|
||||
default AMLOGIC_M8B_CPUFREQ
|
||||
|
||||
config AMLOGIC_M8B_CPUFREQ
|
||||
bool "Meson CPU Frequency scaling support for m8/m8b"
|
||||
depends on MACH_MESON8B && AMLOGIC_CPUFREQ
|
||||
help
|
||||
cpufreq driver support for m8/m8b, select
|
||||
it if your chip belongs this group
|
||||
|
||||
endchoice #AMLOGIC_CPUFREQ
|
||||
1
drivers/amlogic/cpufreq/Makefile
Normal file
1
drivers/amlogic/cpufreq/Makefile
Normal file
@@ -0,0 +1 @@
|
||||
obj-$(CONFIG_AMLOGIC_M8B_CPUFREQ) += m8b_cpufreq.o
|
||||
390
drivers/amlogic/cpufreq/m8b_cpufreq.c
Normal file
390
drivers/amlogic/cpufreq/m8b_cpufreq.c
Normal file
@@ -0,0 +1,390 @@
|
||||
/*
|
||||
* drivers/amlogic/cpufreq/m8b_cpufreq.c
|
||||
*
|
||||
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
|
||||
*
|
||||
* 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/types.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/cpufreq.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/cpu.h>
|
||||
|
||||
#include <linux/pm_opp.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
|
||||
#define FIX_PLL_TABLE_CNT 8
|
||||
|
||||
struct meson_cpufreq {
|
||||
int fixpll_target;
|
||||
int opp_size;
|
||||
int *opp_table;
|
||||
struct cpufreq_frequency_table *table;
|
||||
struct clk *armclk;
|
||||
struct clk *sysclk;
|
||||
};
|
||||
|
||||
static struct meson_cpufreq *mfreq;
|
||||
|
||||
static void build_fixpll_freqtable(void *data, int target,
|
||||
int *opp, int size,
|
||||
struct device *dev)
|
||||
{
|
||||
int i = 0, j, v, ret;
|
||||
int ratio[FIX_PLL_TABLE_CNT] = {1, 2, 4, 8, 10, 12, 14, 16};
|
||||
struct cpufreq_frequency_table *table;
|
||||
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
table = data;
|
||||
for (i = 0; i < FIX_PLL_TABLE_CNT; i++) {
|
||||
table[i].driver_data = i;
|
||||
table[i].frequency = target * ratio[i] / 16;
|
||||
v = 0;
|
||||
for (j = 0; j < size / 2; j++) {
|
||||
if (opp[j * 2] >= table[i].frequency) {
|
||||
v = opp[j * 2 + 1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (v) {
|
||||
ret = dev_pm_opp_add(dev, table[i].frequency * 1000, v);
|
||||
pr_debug("cpu freq:%d, voltage:%d, ret:%d\n",
|
||||
table[i].frequency, v, ret);
|
||||
} else
|
||||
pr_err("no opp for cpu freq:%d\n", table[i].frequency);
|
||||
}
|
||||
table[i].frequency = CPUFREQ_TABLE_END;
|
||||
}
|
||||
|
||||
static DEFINE_MUTEX(meson_cpufreq_mutex);
|
||||
|
||||
static int meson_cpufreq_verify(struct cpufreq_policy *policy)
|
||||
{
|
||||
struct cpufreq_frequency_table *freq_table;
|
||||
|
||||
freq_table = policy->freq_table;
|
||||
if (freq_table)
|
||||
return cpufreq_frequency_table_verify(policy, freq_table);
|
||||
|
||||
if (policy->cpu)
|
||||
return -EINVAL;
|
||||
|
||||
cpufreq_verify_within_limits(policy, policy->cpuinfo.min_freq,
|
||||
policy->cpuinfo.max_freq);
|
||||
|
||||
policy->min = clk_round_rate(mfreq->armclk, policy->min * 1000) / 1000;
|
||||
policy->max = clk_round_rate(mfreq->armclk, policy->max * 1000) / 1000;
|
||||
cpufreq_verify_within_limits(policy, policy->cpuinfo.min_freq,
|
||||
policy->cpuinfo.max_freq);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int meson_cpufreq_target_locked(struct cpufreq_policy *policy,
|
||||
unsigned int target_freq,
|
||||
unsigned int relation)
|
||||
{
|
||||
struct cpufreq_freqs freqs;
|
||||
int cpu = policy ? policy->cpu : 0;
|
||||
int ret = -EINVAL;
|
||||
struct cpufreq_frequency_table *freq_table;
|
||||
|
||||
if (cpu > (num_possible_cpus() - 1)) {
|
||||
pr_err("cpu %d set target freq error\n", cpu);
|
||||
return ret;
|
||||
}
|
||||
|
||||
freq_table = policy->freq_table;
|
||||
if (IS_ERR_OR_NULL(freq_table)) {
|
||||
pr_err("can't get freq_table\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Ensure desired rate is within allowed range. Some govenors
|
||||
* (ondemand) will just pass target_freq=0 to get the minimum.
|
||||
*/
|
||||
if (policy) {
|
||||
if (target_freq < policy->min)
|
||||
target_freq = policy->min;
|
||||
if (target_freq > policy->max)
|
||||
target_freq = policy->max;
|
||||
}
|
||||
|
||||
/* sys pll is not locked to target */
|
||||
if (!((unsigned long)(mfreq->sysclk) & 0x03)) {
|
||||
ret = clk_set_rate(mfreq->sysclk, mfreq->fixpll_target * 1000);
|
||||
if (ret) {
|
||||
pr_err("lock sys pll to target failed\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
mfreq->sysclk = (void *)((unsigned long)(mfreq->sysclk) | 0x03);
|
||||
}
|
||||
|
||||
ret = cpufreq_frequency_table_target(policy, target_freq,
|
||||
CPUFREQ_RELATION_H);
|
||||
if (ret >= 0)
|
||||
target_freq = freq_table[ret].frequency;
|
||||
else {
|
||||
pr_err("can't find %d in freq table:%d\n", target_freq, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
freqs.old = clk_get_rate(mfreq->armclk) / 1000;
|
||||
freqs.new = clk_round_rate(mfreq->armclk, target_freq * 1000) / 1000;
|
||||
freqs.cpu = cpu;
|
||||
|
||||
if (freqs.old == freqs.new) {
|
||||
pr_info("freq equal, new:%d\n", freqs.new);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
pr_debug("cpufreq-meson: CPU%d transition: %u --> %u\n",
|
||||
freqs.cpu, freqs.old, freqs.new);
|
||||
cpufreq_freq_transition_begin(policy, &freqs);
|
||||
ret = clk_set_rate(mfreq->armclk, freqs.new * 1000);
|
||||
cpufreq_freq_transition_end(policy, &freqs, ret);
|
||||
pr_debug("cpufreq-meson: end\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int meson_cpufreq_target(struct cpufreq_policy *policy,
|
||||
unsigned int target_freq,
|
||||
unsigned int relation)
|
||||
{
|
||||
int ret;
|
||||
/* TODO: set max freq for sys pll */
|
||||
|
||||
mutex_lock(&meson_cpufreq_mutex);
|
||||
ret = meson_cpufreq_target_locked(policy, target_freq, relation);
|
||||
mutex_unlock(&meson_cpufreq_mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static unsigned int meson_cpufreq_get(unsigned int cpu)
|
||||
{
|
||||
unsigned long rate;
|
||||
|
||||
if (cpu > (num_possible_cpus() - 1)) {
|
||||
pr_err("cpu %d on current thread error\n", cpu);
|
||||
return 0;
|
||||
}
|
||||
rate = clk_get_rate(mfreq->armclk) / 1000;
|
||||
return rate;
|
||||
}
|
||||
|
||||
static ssize_t opp_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
int size = 0;
|
||||
struct dev_pm_opp *opp;
|
||||
unsigned long freq = 0, voltage;
|
||||
|
||||
while (1) {
|
||||
opp = dev_pm_opp_find_freq_ceil(dev, &freq);
|
||||
if (IS_ERR_OR_NULL(opp)) {
|
||||
pr_err("can't found opp for freq:%ld\n", freq);
|
||||
break;
|
||||
}
|
||||
freq = dev_pm_opp_get_freq(opp);
|
||||
voltage = dev_pm_opp_get_voltage(opp);
|
||||
size += sprintf(buf + size, "%10ld %7ld\n", freq, voltage);
|
||||
freq += 1; /* increase for next freq */
|
||||
}
|
||||
return size;
|
||||
}
|
||||
static DEVICE_ATTR(opp, 0444, opp_show, NULL);
|
||||
|
||||
static int meson_cpufreq_init(struct cpufreq_policy *policy)
|
||||
{
|
||||
struct device *cpu_dev;
|
||||
int ret;
|
||||
|
||||
cpu_dev = get_cpu_device(policy->cpu);
|
||||
if (policy->cpu != 0)
|
||||
return -EINVAL;
|
||||
|
||||
if (policy->cpu > (num_possible_cpus() - 1)) {
|
||||
pr_err("cpu %d on current thread error\n", policy->cpu);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* build and add opp table */
|
||||
build_fixpll_freqtable(mfreq->table,
|
||||
mfreq->fixpll_target, mfreq->opp_table,
|
||||
mfreq->opp_size, cpu_dev);
|
||||
|
||||
if (cpufreq_table_validate_and_show(policy, mfreq->table)) {
|
||||
pr_err("invalid freq table\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = device_create_file(cpu_dev, &dev_attr_opp);
|
||||
if (ret < 0)
|
||||
dev_err(cpu_dev, "create opp sysfs failed\n");
|
||||
|
||||
kfree(mfreq->opp_table);
|
||||
mfreq->opp_table = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct freq_attr *meson_cpufreq_attr[] = {
|
||||
&cpufreq_freq_attr_scaling_available_freqs,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static int meson_cpufreq_suspend(struct cpufreq_policy *policy)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int meson_cpufreq_resume(struct cpufreq_policy *policy)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct cpufreq_driver meson_cpufreq_driver = {
|
||||
.flags = CPUFREQ_STICKY,
|
||||
.verify = meson_cpufreq_verify,
|
||||
.target = meson_cpufreq_target,
|
||||
.get = meson_cpufreq_get,
|
||||
.init = meson_cpufreq_init,
|
||||
.name = "meson_cpufreq",
|
||||
.attr = meson_cpufreq_attr,
|
||||
.suspend = meson_cpufreq_suspend,
|
||||
.resume = meson_cpufreq_resume
|
||||
};
|
||||
|
||||
static int __init meson_cpufreq_probe(struct platform_device *pdev)
|
||||
{
|
||||
int err = 0;
|
||||
int target, size = 0;
|
||||
struct device_node *node;
|
||||
struct property *prop;
|
||||
struct cpufreq_frequency_table *table = NULL;
|
||||
unsigned int *opp = NULL;
|
||||
|
||||
node = pdev->dev.of_node;
|
||||
if (!node)
|
||||
return -EINVAL;
|
||||
|
||||
mfreq = kzalloc(sizeof(struct meson_cpufreq), GFP_KERNEL);
|
||||
if (!mfreq)
|
||||
return -ENOMEM;
|
||||
|
||||
err = of_property_read_u32(node, "fixpll_target", &target);
|
||||
if (err)
|
||||
target = 1536000;
|
||||
else
|
||||
pr_info("%s:SYSPLL fix to target:%u\n", __func__, target);
|
||||
|
||||
/* find fix pll target */
|
||||
mfreq->fixpll_target = target;
|
||||
table = kzalloc(sizeof(*table) * FIX_PLL_TABLE_CNT, GFP_KERNEL);
|
||||
if (!table) {
|
||||
pr_err("%s, alloc freq table failed\n", __func__);
|
||||
return -ENOMEM;
|
||||
}
|
||||
mfreq->table = table;
|
||||
|
||||
/* find opp table */
|
||||
prop = of_find_property(node, "opp_table", &size);
|
||||
if (IS_ERR_OR_NULL(prop)) {
|
||||
pr_err("%s, can't find opp table\n", __func__);
|
||||
goto out;
|
||||
}
|
||||
opp = kzalloc(size, GFP_KERNEL);
|
||||
if (!opp)
|
||||
goto out;
|
||||
|
||||
size /= sizeof(int);
|
||||
err = of_property_read_u32_array(node, "opp_table", opp, size);
|
||||
if (err < 0) {
|
||||
pr_err("%s, read opp_table failed, size:%d\n", __func__, size);
|
||||
goto out;
|
||||
}
|
||||
mfreq->opp_size = size;
|
||||
mfreq->opp_table = opp;
|
||||
|
||||
mfreq->armclk = devm_clk_get(&pdev->dev, "cpu_clk");
|
||||
if (IS_ERR_OR_NULL(mfreq->armclk)) {
|
||||
pr_err("Unable to get ARM clock\n");
|
||||
goto out;
|
||||
}
|
||||
mfreq->sysclk = devm_clk_get(&pdev->dev, "sys_clk");
|
||||
if (IS_ERR_OR_NULL(mfreq->sysclk)) {
|
||||
pr_err("Unable to get sys clock\n");
|
||||
goto out;
|
||||
}
|
||||
err = clk_prepare_enable(mfreq->sysclk);
|
||||
if (err) {
|
||||
pr_err("enable sysclk failed\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
return cpufreq_register_driver(&meson_cpufreq_driver);
|
||||
|
||||
out:
|
||||
kfree(table);
|
||||
kfree(opp);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
|
||||
static int __exit meson_cpufreq_remove(struct platform_device *pdev)
|
||||
{
|
||||
kfree(mfreq->table);
|
||||
return cpufreq_unregister_driver(&meson_cpufreq_driver);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id amlogic_cpufreq_meson_dt_match[] = {
|
||||
{
|
||||
.compatible = "amlogic, cpufreq-meson",
|
||||
},
|
||||
{},
|
||||
};
|
||||
#else
|
||||
#define amlogic_cpufreq_meson_dt_match NULL
|
||||
#endif
|
||||
|
||||
|
||||
static struct platform_driver meson_cpufreq_parent_driver = {
|
||||
.driver = {
|
||||
.name = "cpufreq-meson",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = amlogic_cpufreq_meson_dt_match,
|
||||
},
|
||||
.probe = meson_cpufreq_probe,
|
||||
.remove = meson_cpufreq_remove,
|
||||
};
|
||||
|
||||
static int __init meson_cpufreq_parent_init(void)
|
||||
{
|
||||
return platform_driver_register(&meson_cpufreq_parent_driver);
|
||||
}
|
||||
late_initcall(meson_cpufreq_parent_init);
|
||||
|
||||
Reference in New Issue
Block a user