Merge: update cpufreq subsystem to v6.3

MR: https://gitlab.com/redhat/centos-stream/src/kernel/centos-stream-9/-/merge_requests/2623

Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=2181272

Does not include most of the qcom specific commits or any of the
amd-pstate commits. The AMD commits are being handled in a
different MR.

Signed-off-by: Mark Langsdorf <mlangsdo@redhat.com>

Approved-by: Dean Nelson <dnelson@redhat.com>
Approved-by: David Arcari <darcari@redhat.com>
Approved-by: Eric Chanudet <echanude@redhat.com>

Signed-off-by: Herton R. Krzesinski <herton@redhat.com>
This commit is contained in:
Herton R. Krzesinski 2023-08-07 23:14:18 +00:00
commit d19b8ca994
26 changed files with 112 additions and 2668 deletions

View File

@ -3,7 +3,6 @@ menu "CPU Frequency scaling"
config CPU_FREQ
bool "CPU Frequency scaling"
select SRCU
help
CPU Frequency scaling allows you to change the clock speed of
CPUs on the fly. This is a nice method to save power, because
@ -37,7 +36,7 @@ config CPU_FREQ_STAT
choice
prompt "Default CPUFreq governor"
default CPU_FREQ_DEFAULT_GOV_USERSPACE if ARM_SA1100_CPUFREQ || ARM_SA1110_CPUFREQ
default CPU_FREQ_DEFAULT_GOV_USERSPACE if ARM_SA1110_CPUFREQ
default CPU_FREQ_DEFAULT_GOV_SCHEDUTIL if ARM64 || ARM
default CPU_FREQ_DEFAULT_GOV_SCHEDUTIL if X86_INTEL_PSTATE && SMP
default CPU_FREQ_DEFAULT_GOV_PERFORMANCE

View File

@ -168,84 +168,6 @@ config ARM_RASPBERRYPI_CPUFREQ
If in doubt, say N.
config ARM_S3C_CPUFREQ
bool
help
Internal configuration node for common cpufreq on Samsung SoC
config ARM_S3C24XX_CPUFREQ
bool "CPUfreq driver for Samsung S3C24XX series CPUs (EXPERIMENTAL)"
depends on ARCH_S3C24XX
select ARM_S3C_CPUFREQ
help
This enables the CPUfreq driver for the Samsung S3C24XX family
of CPUs.
For details, take a look at <file:Documentation/cpu-freq>.
If in doubt, say N.
config ARM_S3C24XX_CPUFREQ_DEBUG
bool "Debug CPUfreq Samsung driver core"
depends on ARM_S3C24XX_CPUFREQ
help
Enable s3c_freq_dbg for the Samsung S3C CPUfreq core
config ARM_S3C24XX_CPUFREQ_IODEBUG
bool "Debug CPUfreq Samsung driver IO timing"
depends on ARM_S3C24XX_CPUFREQ
help
Enable s3c_freq_iodbg for the Samsung S3C CPUfreq core
config ARM_S3C24XX_CPUFREQ_DEBUGFS
bool "Export debugfs for CPUFreq"
depends on ARM_S3C24XX_CPUFREQ && DEBUG_FS
help
Export status information via debugfs.
config ARM_S3C2410_CPUFREQ
bool
depends on ARM_S3C24XX_CPUFREQ && CPU_S3C2410
help
CPU Frequency scaling support for S3C2410
config ARM_S3C2412_CPUFREQ
bool
depends on ARM_S3C24XX_CPUFREQ && CPU_S3C2412
default y
select S3C2412_IOTIMING
help
CPU Frequency scaling support for S3C2412 and S3C2413 SoC CPUs.
config ARM_S3C2416_CPUFREQ
bool "S3C2416 CPU Frequency scaling support"
depends on CPU_S3C2416
help
This adds the CPUFreq driver for the Samsung S3C2416 and
S3C2450 SoC. The S3C2416 supports changing the rate of the
armdiv clock source and also entering a so called dynamic
voltage scaling mode in which it is possible to reduce the
core voltage of the CPU.
If in doubt, say N.
config ARM_S3C2416_CPUFREQ_VCORESCALE
bool "Allow voltage scaling for S3C2416 arm core"
depends on ARM_S3C2416_CPUFREQ && REGULATOR
help
Enable CPU voltage scaling when entering the dvs mode.
It uses information gathered through existing hardware and
tests but not documented in any datasheet.
If in doubt, say N.
config ARM_S3C2440_CPUFREQ
bool "S3C2440/S3C2442 CPU Frequency scaling support"
depends on ARM_S3C24XX_CPUFREQ && (CPU_S3C2440 || CPU_S3C2442)
default y
help
CPU Frequency scaling support for S3C2440 and S3C2442 SoC CPUs.
config ARM_S3C64XX_CPUFREQ
bool "Samsung S3C64XX"
depends on CPU_S3C6410
@ -265,9 +187,6 @@ config ARM_S5PV210_CPUFREQ
If in doubt, say N.
config ARM_SA1100_CPUFREQ
bool
config ARM_SA1110_CPUFREQ
bool

View File

@ -69,15 +69,8 @@ obj-$(CONFIG_PXA3xx) += pxa3xx-cpufreq.o
obj-$(CONFIG_ARM_QCOM_CPUFREQ_HW) += qcom-cpufreq-hw.o
obj-$(CONFIG_ARM_QCOM_CPUFREQ_NVMEM) += qcom-cpufreq-nvmem.o
obj-$(CONFIG_ARM_RASPBERRYPI_CPUFREQ) += raspberrypi-cpufreq.o
obj-$(CONFIG_ARM_S3C2410_CPUFREQ) += s3c2410-cpufreq.o
obj-$(CONFIG_ARM_S3C2412_CPUFREQ) += s3c2412-cpufreq.o
obj-$(CONFIG_ARM_S3C2416_CPUFREQ) += s3c2416-cpufreq.o
obj-$(CONFIG_ARM_S3C2440_CPUFREQ) += s3c2440-cpufreq.o
obj-$(CONFIG_ARM_S3C64XX_CPUFREQ) += s3c64xx-cpufreq.o
obj-$(CONFIG_ARM_S3C24XX_CPUFREQ) += s3c24xx-cpufreq.o
obj-$(CONFIG_ARM_S3C24XX_CPUFREQ_DEBUGFS) += s3c24xx-cpufreq-debugfs.o
obj-$(CONFIG_ARM_S5PV210_CPUFREQ) += s5pv210-cpufreq.o
obj-$(CONFIG_ARM_SA1100_CPUFREQ) += sa1100-cpufreq.o
obj-$(CONFIG_ARM_SA1110_CPUFREQ) += sa1110-cpufreq.o
obj-$(CONFIG_ARM_SCMI_CPUFREQ) += scmi-cpufreq.o
obj-$(CONFIG_ARM_SCPI_CPUFREQ) += scpi-cpufreq.o

View File

@ -19,6 +19,7 @@
#include <linux/compiler.h>
#include <linux/dmi.h>
#include <linux/slab.h>
#include <linux/string_helpers.h>
#include <linux/acpi.h>
#include <linux/io.h>
@ -135,8 +136,8 @@ static int set_boost(struct cpufreq_policy *policy, int val)
{
on_each_cpu_mask(policy->cpus, boost_set_msr_each,
(void *)(long)val, 1);
pr_debug("CPU %*pbl: Core Boosting %sabled.\n",
cpumask_pr_args(policy->cpus), val ? "en" : "dis");
pr_debug("CPU %*pbl: Core Boosting %s.\n",
cpumask_pr_args(policy->cpus), str_enabled_disabled(val));
return 0;
}

View File

@ -751,10 +751,7 @@ static int brcm_avs_cpufreq_probe(struct platform_device *pdev)
static int brcm_avs_cpufreq_remove(struct platform_device *pdev)
{
int ret;
ret = cpufreq_unregister_driver(&brcm_avs_driver);
WARN_ON(ret);
cpufreq_unregister_driver(&brcm_avs_driver);
brcm_avs_prepare_uninit(pdev);

View File

@ -487,7 +487,8 @@ static unsigned int get_perf_level_count(struct cpufreq_policy *policy)
cpu_data = policy->driver_data;
perf_caps = &cpu_data->perf_caps;
max_cap = arch_scale_cpu_capacity(cpu);
min_cap = div_u64(max_cap * perf_caps->lowest_perf, perf_caps->highest_perf);
min_cap = div_u64((u64)max_cap * perf_caps->lowest_perf,
perf_caps->highest_perf);
if ((min_cap == 0) || (max_cap < min_cap))
return 0;
return 1 + max_cap / CPPC_EM_CAP_STEP - min_cap / CPPC_EM_CAP_STEP;
@ -519,10 +520,10 @@ static int cppc_get_cpu_power(struct device *cpu_dev,
cpu_data = policy->driver_data;
perf_caps = &cpu_data->perf_caps;
max_cap = arch_scale_cpu_capacity(cpu_dev->id);
min_cap = div_u64(max_cap * perf_caps->lowest_perf,
perf_caps->highest_perf);
perf_step = CPPC_EM_CAP_STEP * perf_caps->highest_perf / max_cap;
min_cap = div_u64((u64)max_cap * perf_caps->lowest_perf,
perf_caps->highest_perf);
perf_step = div_u64((u64)CPPC_EM_CAP_STEP * perf_caps->highest_perf,
max_cap);
min_step = min_cap / CPPC_EM_CAP_STEP;
max_step = max_cap / CPPC_EM_CAP_STEP;

View File

@ -223,10 +223,8 @@ static int dt_cpufreq_early_init(struct device *dev, int cpu)
if (reg_name[0]) {
priv->opp_token = dev_pm_opp_set_regulators(cpu_dev, reg_name);
if (priv->opp_token < 0) {
ret = priv->opp_token;
if (ret != -EPROBE_DEFER)
dev_err(cpu_dev, "failed to set regulators: %d\n",
ret);
ret = dev_err_probe(cpu_dev, priv->opp_token,
"failed to set regulators\n");
goto free_cpumask;
}
}

View File

@ -1002,7 +1002,7 @@ static const struct sysfs_ops sysfs_ops = {
.store = store,
};
static struct kobj_type ktype_cpufreq = {
static const struct kobj_type ktype_cpufreq = {
.sysfs_ops = &sysfs_ops,
.default_groups = cpufreq_groups,
.release = cpufreq_sysfs_release,
@ -1216,6 +1216,7 @@ static struct cpufreq_policy *cpufreq_policy_alloc(unsigned int cpu)
if (!zalloc_cpumask_var(&policy->real_cpus, GFP_KERNEL))
goto err_free_rcpumask;
init_completion(&policy->kobj_unregister);
ret = kobject_init_and_add(&policy->kobj, &ktype_cpufreq,
cpufreq_global_kobject, "policy%u", cpu);
if (ret) {
@ -1254,7 +1255,6 @@ static struct cpufreq_policy *cpufreq_policy_alloc(unsigned int cpu)
init_rwsem(&policy->rwsem);
spin_lock_init(&policy->transition_lock);
init_waitqueue_head(&policy->transition_wait);
init_completion(&policy->kobj_unregister);
INIT_WORK(&policy->update, handle_update);
policy->cpu = cpu;
@ -2906,12 +2906,12 @@ EXPORT_SYMBOL_GPL(cpufreq_register_driver);
* Returns zero if successful, and -EINVAL if the cpufreq_driver is
* currently not initialised.
*/
int cpufreq_unregister_driver(struct cpufreq_driver *driver)
void cpufreq_unregister_driver(struct cpufreq_driver *driver)
{
unsigned long flags;
if (!cpufreq_driver || (driver != cpufreq_driver))
return -EINVAL;
if (WARN_ON(!cpufreq_driver || (driver != cpufreq_driver)))
return;
pr_debug("unregistering driver %s\n", driver->name);
@ -2928,8 +2928,6 @@ int cpufreq_unregister_driver(struct cpufreq_driver *driver)
write_unlock_irqrestore(&cpufreq_driver_lock, flags);
cpus_read_unlock();
return 0;
}
EXPORT_SYMBOL_GPL(cpufreq_unregister_driver);

View File

@ -128,25 +128,23 @@ static ssize_t show_trans_table(struct cpufreq_policy *policy, char *buf)
ssize_t len = 0;
int i, j, count;
len += scnprintf(buf + len, PAGE_SIZE - len, " From : To\n");
len += scnprintf(buf + len, PAGE_SIZE - len, " : ");
len += sysfs_emit_at(buf, len, " From : To\n");
len += sysfs_emit_at(buf, len, " : ");
for (i = 0; i < stats->state_num; i++) {
if (len >= PAGE_SIZE)
break;
len += scnprintf(buf + len, PAGE_SIZE - len, "%9u ",
stats->freq_table[i]);
len += sysfs_emit_at(buf, len, "%9u ", stats->freq_table[i]);
}
if (len >= PAGE_SIZE)
return PAGE_SIZE;
len += scnprintf(buf + len, PAGE_SIZE - len, "\n");
len += sysfs_emit_at(buf, len, "\n");
for (i = 0; i < stats->state_num; i++) {
if (len >= PAGE_SIZE)
break;
len += scnprintf(buf + len, PAGE_SIZE - len, "%9u: ",
stats->freq_table[i]);
len += sysfs_emit_at(buf, len, "%9u: ", stats->freq_table[i]);
for (j = 0; j < stats->state_num; j++) {
if (len >= PAGE_SIZE)
@ -157,11 +155,11 @@ static ssize_t show_trans_table(struct cpufreq_policy *policy, char *buf)
else
count = stats->trans_table[i * stats->max_state + j];
len += scnprintf(buf + len, PAGE_SIZE - len, "%9u ", count);
len += sysfs_emit_at(buf, len, "%9u ", count);
}
if (len >= PAGE_SIZE)
break;
len += scnprintf(buf + len, PAGE_SIZE - len, "\n");
len += sysfs_emit_at(buf, len, "\n");
}
if (len >= PAGE_SIZE) {

View File

@ -138,7 +138,9 @@ static int __exit davinci_cpufreq_remove(struct platform_device *pdev)
if (cpufreq.asyncclk)
clk_put(cpufreq.asyncclk);
return cpufreq_unregister_driver(&davinci_driver);
cpufreq_unregister_driver(&davinci_driver);
return 0;
}
static struct platform_driver davinci_cpufreq_driver = {

View File

@ -55,7 +55,7 @@ static struct notifier_block hb_cpufreq_clk_nb = {
.notifier_call = hb_cpufreq_clk_notify,
};
static int hb_cpufreq_driver_init(void)
static int __init hb_cpufreq_driver_init(void)
{
struct platform_device_info devinfo = { .name = "cpufreq-dt", };
struct device *cpu_dev;

View File

@ -396,9 +396,7 @@ static int imx6q_cpufreq_probe(struct platform_device *pdev)
ret = imx6q_opp_check_speed_grading(cpu_dev);
}
if (ret) {
if (ret != -EPROBE_DEFER)
dev_err(cpu_dev, "failed to read ocotp: %d\n",
ret);
dev_err_probe(cpu_dev, ret, "failed to read ocotp\n");
goto out_free_opp;
}

View File

@ -184,7 +184,9 @@ static int omap_cpufreq_probe(struct platform_device *pdev)
static int omap_cpufreq_remove(struct platform_device *pdev)
{
return cpufreq_unregister_driver(&omap_driver);
cpufreq_unregister_driver(&omap_driver);
return 0;
}
static struct platform_driver omap_cpufreq_platdrv = {

View File

@ -459,7 +459,9 @@ static int qcom_cpufreq_hw_driver_probe(struct platform_device *pdev)
static int qcom_cpufreq_hw_driver_remove(struct platform_device *pdev)
{
return cpufreq_unregister_driver(&cpufreq_qcom_hw_driver);
cpufreq_unregister_driver(&cpufreq_qcom_hw_driver);
return 0;
}
static struct platform_driver qcom_cpufreq_hw_driver = {

View File

@ -66,7 +66,7 @@ static struct platform_device *cpufreq_dt_pdev, *cpufreq_pdev;
static void get_krait_bin_format_a(struct device *cpu_dev,
int *speed, int *pvs, int *pvs_ver,
struct nvmem_cell *pvs_nvmem, u8 *buf)
u8 *buf)
{
u32 pte_efuse;
@ -97,7 +97,7 @@ static void get_krait_bin_format_a(struct device *cpu_dev,
static void get_krait_bin_format_b(struct device *cpu_dev,
int *speed, int *pvs, int *pvs_ver,
struct nvmem_cell *pvs_nvmem, u8 *buf)
u8 *buf)
{
u32 pte_efuse, redundant_sel;
@ -215,6 +215,7 @@ static int qcom_cpufreq_krait_name_version(struct device *cpu_dev,
int speed = 0, pvs = 0, pvs_ver = 0;
u8 *speedbin;
size_t len;
int ret = 0;
speedbin = nvmem_cell_read(speedbin_nvmem, &len);
@ -224,15 +225,16 @@ static int qcom_cpufreq_krait_name_version(struct device *cpu_dev,
switch (len) {
case 4:
get_krait_bin_format_a(cpu_dev, &speed, &pvs, &pvs_ver,
speedbin_nvmem, speedbin);
speedbin);
break;
case 8:
get_krait_bin_format_b(cpu_dev, &speed, &pvs, &pvs_ver,
speedbin_nvmem, speedbin);
speedbin);
break;
default:
dev_err(cpu_dev, "Unable to read nvmem data. Defaulting to 0!\n");
return -ENODEV;
ret = -ENODEV;
goto len_error;
}
snprintf(*pvs_name, sizeof("speedXX-pvsXX-vXX"), "speed%d-pvs%d-v%d",
@ -240,8 +242,9 @@ static int qcom_cpufreq_krait_name_version(struct device *cpu_dev,
drv->versions = (1 << speed);
len_error:
kfree(speedbin);
return 0;
return ret;
}
static const struct qcom_cpufreq_match_data match_data_kryo = {
@ -264,7 +267,8 @@ static int qcom_cpufreq_probe(struct platform_device *pdev)
struct nvmem_cell *speedbin_nvmem;
struct device_node *np;
struct device *cpu_dev;
char *pvs_name = "speedXX-pvsXX-vXX";
char pvs_name_buffer[] = "speedXX-pvsXX-vXX";
char *pvs_name = pvs_name_buffer;
unsigned cpu;
const struct of_device_id *match;
int ret;
@ -297,11 +301,8 @@ static int qcom_cpufreq_probe(struct platform_device *pdev)
if (drv->data->get_version) {
speedbin_nvmem = of_nvmem_cell_get(np, NULL);
if (IS_ERR(speedbin_nvmem)) {
if (PTR_ERR(speedbin_nvmem) != -EPROBE_DEFER)
dev_err(cpu_dev,
"Could not get nvmem cell: %ld\n",
PTR_ERR(speedbin_nvmem));
ret = PTR_ERR(speedbin_nvmem);
ret = dev_err_probe(cpu_dev, PTR_ERR(speedbin_nvmem),
"Could not get nvmem cell\n");
goto free_drv;
}

View File

@ -1,155 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2006-2008 Simtec Electronics
* http://armlinux.simtec.co.uk/
* Ben Dooks <ben@simtec.co.uk>
*
* S3C2410 CPU Frequency scaling
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/ioport.h>
#include <linux/cpufreq.h>
#include <linux/device.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/soc/samsung/s3c-cpufreq-core.h>
#include <linux/soc/samsung/s3c-pm.h>
#include <asm/mach/arch.h>
#include <asm/mach/map.h>
#define S3C2410_CLKDIVN_PDIVN (1<<0)
#define S3C2410_CLKDIVN_HDIVN (1<<1)
/* Note, 2410A has an extra mode for 1:4:4 ratio, bit 2 of CLKDIV */
static void s3c2410_cpufreq_setdivs(struct s3c_cpufreq_config *cfg)
{
u32 clkdiv = 0;
if (cfg->divs.h_divisor == 2)
clkdiv |= S3C2410_CLKDIVN_HDIVN;
if (cfg->divs.p_divisor != cfg->divs.h_divisor)
clkdiv |= S3C2410_CLKDIVN_PDIVN;
s3c24xx_write_clkdivn(clkdiv);
}
static int s3c2410_cpufreq_calcdivs(struct s3c_cpufreq_config *cfg)
{
unsigned long hclk, fclk, pclk;
unsigned int hdiv, pdiv;
unsigned long hclk_max;
fclk = cfg->freq.fclk;
hclk_max = cfg->max.hclk;
cfg->freq.armclk = fclk;
s3c_freq_dbg("%s: fclk is %lu, max hclk %lu\n",
__func__, fclk, hclk_max);
hdiv = (fclk > cfg->max.hclk) ? 2 : 1;
hclk = fclk / hdiv;
if (hclk > cfg->max.hclk) {
s3c_freq_dbg("%s: hclk too big\n", __func__);
return -EINVAL;
}
pdiv = (hclk > cfg->max.pclk) ? 2 : 1;
pclk = hclk / pdiv;
if (pclk > cfg->max.pclk) {
s3c_freq_dbg("%s: pclk too big\n", __func__);
return -EINVAL;
}
pdiv *= hdiv;
/* record the result */
cfg->divs.p_divisor = pdiv;
cfg->divs.h_divisor = hdiv;
return 0;
}
static struct s3c_cpufreq_info s3c2410_cpufreq_info = {
.max = {
.fclk = 200000000,
.hclk = 100000000,
.pclk = 50000000,
},
/* transition latency is about 5ms worst-case, so
* set 10ms to be sure */
.latency = 10000000,
.locktime_m = 150,
.locktime_u = 150,
.locktime_bits = 12,
.need_pll = 1,
.name = "s3c2410",
.calc_iotiming = s3c2410_iotiming_calc,
.set_iotiming = s3c2410_iotiming_set,
.get_iotiming = s3c2410_iotiming_get,
.set_fvco = s3c2410_set_fvco,
.set_refresh = s3c2410_cpufreq_setrefresh,
.set_divs = s3c2410_cpufreq_setdivs,
.calc_divs = s3c2410_cpufreq_calcdivs,
.debug_io_show = s3c_cpufreq_debugfs_call(s3c2410_iotiming_debugfs),
};
static int s3c2410_cpufreq_add(struct device *dev,
struct subsys_interface *sif)
{
return s3c_cpufreq_register(&s3c2410_cpufreq_info);
}
static struct subsys_interface s3c2410_cpufreq_interface = {
.name = "s3c2410_cpufreq",
.subsys = &s3c2410_subsys,
.add_dev = s3c2410_cpufreq_add,
};
static int __init s3c2410_cpufreq_init(void)
{
return subsys_interface_register(&s3c2410_cpufreq_interface);
}
arch_initcall(s3c2410_cpufreq_init);
static int s3c2410a_cpufreq_add(struct device *dev,
struct subsys_interface *sif)
{
/* alter the maximum freq settings for S3C2410A. If a board knows
* it only has a maximum of 200, then it should register its own
* limits. */
s3c2410_cpufreq_info.max.fclk = 266000000;
s3c2410_cpufreq_info.max.hclk = 133000000;
s3c2410_cpufreq_info.max.pclk = 66500000;
s3c2410_cpufreq_info.name = "s3c2410a";
return s3c2410_cpufreq_add(dev, sif);
}
static struct subsys_interface s3c2410a_cpufreq_interface = {
.name = "s3c2410a_cpufreq",
.subsys = &s3c2410a_subsys,
.add_dev = s3c2410a_cpufreq_add,
};
static int __init s3c2410a_cpufreq_init(void)
{
return subsys_interface_register(&s3c2410a_cpufreq_interface);
}
arch_initcall(s3c2410a_cpufreq_init);

View File

@ -1,240 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2008 Simtec Electronics
* http://armlinux.simtec.co.uk/
* Ben Dooks <ben@simtec.co.uk>
*
* S3C2412 CPU Frequency scalling
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/init.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/ioport.h>
#include <linux/cpufreq.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/soc/samsung/s3c-cpufreq-core.h>
#include <linux/soc/samsung/s3c-pm.h>
#include <asm/mach/arch.h>
#include <asm/mach/map.h>
#define S3C2412_CLKDIVN_PDIVN (1<<2)
#define S3C2412_CLKDIVN_HDIVN_MASK (3<<0)
#define S3C2412_CLKDIVN_ARMDIVN (1<<3)
#define S3C2412_CLKDIVN_DVSEN (1<<4)
#define S3C2412_CLKDIVN_HALFHCLK (1<<5)
#define S3C2412_CLKDIVN_USB48DIV (1<<6)
#define S3C2412_CLKDIVN_UARTDIV_MASK (15<<8)
#define S3C2412_CLKDIVN_UARTDIV_SHIFT (8)
#define S3C2412_CLKDIVN_I2SDIV_MASK (15<<12)
#define S3C2412_CLKDIVN_I2SDIV_SHIFT (12)
#define S3C2412_CLKDIVN_CAMDIV_MASK (15<<16)
#define S3C2412_CLKDIVN_CAMDIV_SHIFT (16)
/* our clock resources. */
static struct clk *xtal;
static struct clk *fclk;
static struct clk *hclk;
static struct clk *armclk;
/* HDIV: 1, 2, 3, 4, 6, 8 */
static int s3c2412_cpufreq_calcdivs(struct s3c_cpufreq_config *cfg)
{
unsigned int hdiv, pdiv, armdiv, dvs;
unsigned long hclk, fclk, armclk, armdiv_clk;
unsigned long hclk_max;
fclk = cfg->freq.fclk;
armclk = cfg->freq.armclk;
hclk_max = cfg->max.hclk;
/* We can't run hclk above armclk as at the best we have to
* have armclk and hclk in dvs mode. */
if (hclk_max > armclk)
hclk_max = armclk;
s3c_freq_dbg("%s: fclk=%lu, armclk=%lu, hclk_max=%lu\n",
__func__, fclk, armclk, hclk_max);
s3c_freq_dbg("%s: want f=%lu, arm=%lu, h=%lu, p=%lu\n",
__func__, cfg->freq.fclk, cfg->freq.armclk,
cfg->freq.hclk, cfg->freq.pclk);
armdiv = fclk / armclk;
if (armdiv < 1)
armdiv = 1;
if (armdiv > 2)
armdiv = 2;
cfg->divs.arm_divisor = armdiv;
armdiv_clk = fclk / armdiv;
hdiv = armdiv_clk / hclk_max;
if (hdiv < 1)
hdiv = 1;
cfg->freq.hclk = hclk = armdiv_clk / hdiv;
/* set dvs depending on whether we reached armclk or not. */
cfg->divs.dvs = dvs = armclk < armdiv_clk;
/* update the actual armclk we achieved. */
cfg->freq.armclk = dvs ? hclk : armdiv_clk;
s3c_freq_dbg("%s: armclk %lu, hclk %lu, armdiv %d, hdiv %d, dvs %d\n",
__func__, armclk, hclk, armdiv, hdiv, cfg->divs.dvs);
if (hdiv > 4)
goto invalid;
pdiv = (hclk > cfg->max.pclk) ? 2 : 1;
if ((hclk / pdiv) > cfg->max.pclk)
pdiv++;
cfg->freq.pclk = hclk / pdiv;
s3c_freq_dbg("%s: pdiv %d\n", __func__, pdiv);
if (pdiv > 2)
goto invalid;
pdiv *= hdiv;
/* store the result, and then return */
cfg->divs.h_divisor = hdiv * armdiv;
cfg->divs.p_divisor = pdiv * armdiv;
return 0;
invalid:
return -EINVAL;
}
static void s3c2412_cpufreq_setdivs(struct s3c_cpufreq_config *cfg)
{
unsigned long clkdiv;
unsigned long olddiv;
olddiv = clkdiv = s3c24xx_read_clkdivn();
/* clear off current clock info */
clkdiv &= ~S3C2412_CLKDIVN_ARMDIVN;
clkdiv &= ~S3C2412_CLKDIVN_HDIVN_MASK;
clkdiv &= ~S3C2412_CLKDIVN_PDIVN;
if (cfg->divs.arm_divisor == 2)
clkdiv |= S3C2412_CLKDIVN_ARMDIVN;
clkdiv |= ((cfg->divs.h_divisor / cfg->divs.arm_divisor) - 1);
if (cfg->divs.p_divisor != cfg->divs.h_divisor)
clkdiv |= S3C2412_CLKDIVN_PDIVN;
s3c_freq_dbg("%s: div %08lx => %08lx\n", __func__, olddiv, clkdiv);
s3c24xx_write_clkdivn(clkdiv);
clk_set_parent(armclk, cfg->divs.dvs ? hclk : fclk);
}
/* set the default cpu frequency information, based on an 200MHz part
* as we have no other way of detecting the speed rating in software.
*/
static struct s3c_cpufreq_info s3c2412_cpufreq_info = {
.max = {
.fclk = 200000000,
.hclk = 100000000,
.pclk = 50000000,
},
.latency = 5000000, /* 5ms */
.locktime_m = 150,
.locktime_u = 150,
.locktime_bits = 16,
.name = "s3c2412",
.set_refresh = s3c2412_cpufreq_setrefresh,
.set_divs = s3c2412_cpufreq_setdivs,
.calc_divs = s3c2412_cpufreq_calcdivs,
.calc_iotiming = s3c2412_iotiming_calc,
.set_iotiming = s3c2412_iotiming_set,
.get_iotiming = s3c2412_iotiming_get,
.debug_io_show = s3c_cpufreq_debugfs_call(s3c2412_iotiming_debugfs),
};
static int s3c2412_cpufreq_add(struct device *dev,
struct subsys_interface *sif)
{
unsigned long fclk_rate;
hclk = clk_get(NULL, "hclk");
if (IS_ERR(hclk)) {
pr_err("cannot find hclk clock\n");
return -ENOENT;
}
fclk = clk_get(NULL, "fclk");
if (IS_ERR(fclk)) {
pr_err("cannot find fclk clock\n");
goto err_fclk;
}
fclk_rate = clk_get_rate(fclk);
if (fclk_rate > 200000000) {
pr_info("fclk %ld MHz, assuming 266MHz capable part\n",
fclk_rate / 1000000);
s3c2412_cpufreq_info.max.fclk = 266000000;
s3c2412_cpufreq_info.max.hclk = 133000000;
s3c2412_cpufreq_info.max.pclk = 66000000;
}
armclk = clk_get(NULL, "armclk");
if (IS_ERR(armclk)) {
pr_err("cannot find arm clock\n");
goto err_armclk;
}
xtal = clk_get(NULL, "xtal");
if (IS_ERR(xtal)) {
pr_err("cannot find xtal clock\n");
goto err_xtal;
}
return s3c_cpufreq_register(&s3c2412_cpufreq_info);
err_xtal:
clk_put(armclk);
err_armclk:
clk_put(fclk);
err_fclk:
clk_put(hclk);
return -ENOENT;
}
static struct subsys_interface s3c2412_cpufreq_interface = {
.name = "s3c2412_cpufreq",
.subsys = &s3c2412_subsys,
.add_dev = s3c2412_cpufreq_add,
};
static int s3c2412_cpufreq_init(void)
{
return subsys_interface_register(&s3c2412_cpufreq_interface);
}
arch_initcall(s3c2412_cpufreq_init);

View File

@ -1,492 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* S3C2416/2450 CPUfreq Support
*
* Copyright 2011 Heiko Stuebner <heiko@sntech.de>
*
* based on s3c64xx_cpufreq.c
*
* Copyright 2009 Wolfson Microelectronics plc
*/
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/init.h>
#include <linux/cpufreq.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/regulator/consumer.h>
#include <linux/reboot.h>
#include <linux/module.h>
static DEFINE_MUTEX(cpufreq_lock);
struct s3c2416_data {
struct clk *armdiv;
struct clk *armclk;
struct clk *hclk;
unsigned long regulator_latency;
#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
struct regulator *vddarm;
#endif
struct cpufreq_frequency_table *freq_table;
bool is_dvs;
bool disable_dvs;
};
static struct s3c2416_data s3c2416_cpufreq;
struct s3c2416_dvfs {
unsigned int vddarm_min;
unsigned int vddarm_max;
};
/* pseudo-frequency for dvs mode */
#define FREQ_DVS 132333
/* frequency to sleep and reboot in
* it's essential to leave dvs, as some boards do not reconfigure the
* regulator on reboot
*/
#define FREQ_SLEEP 133333
/* Sources for the ARMCLK */
#define SOURCE_HCLK 0
#define SOURCE_ARMDIV 1
#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
/* S3C2416 only supports changing the voltage in the dvs-mode.
* Voltages down to 1.0V seem to work, so we take what the regulator
* can get us.
*/
static struct s3c2416_dvfs s3c2416_dvfs_table[] = {
[SOURCE_HCLK] = { 950000, 1250000 },
[SOURCE_ARMDIV] = { 1250000, 1350000 },
};
#endif
static struct cpufreq_frequency_table s3c2416_freq_table[] = {
{ 0, SOURCE_HCLK, FREQ_DVS },
{ 0, SOURCE_ARMDIV, 133333 },
{ 0, SOURCE_ARMDIV, 266666 },
{ 0, SOURCE_ARMDIV, 400000 },
{ 0, 0, CPUFREQ_TABLE_END },
};
static struct cpufreq_frequency_table s3c2450_freq_table[] = {
{ 0, SOURCE_HCLK, FREQ_DVS },
{ 0, SOURCE_ARMDIV, 133500 },
{ 0, SOURCE_ARMDIV, 267000 },
{ 0, SOURCE_ARMDIV, 534000 },
{ 0, 0, CPUFREQ_TABLE_END },
};
static unsigned int s3c2416_cpufreq_get_speed(unsigned int cpu)
{
struct s3c2416_data *s3c_freq = &s3c2416_cpufreq;
if (cpu != 0)
return 0;
/* return our pseudo-frequency when in dvs mode */
if (s3c_freq->is_dvs)
return FREQ_DVS;
return clk_get_rate(s3c_freq->armclk) / 1000;
}
static int s3c2416_cpufreq_set_armdiv(struct s3c2416_data *s3c_freq,
unsigned int freq)
{
int ret;
if (clk_get_rate(s3c_freq->armdiv) / 1000 != freq) {
ret = clk_set_rate(s3c_freq->armdiv, freq * 1000);
if (ret < 0) {
pr_err("cpufreq: Failed to set armdiv rate %dkHz: %d\n",
freq, ret);
return ret;
}
}
return 0;
}
static int s3c2416_cpufreq_enter_dvs(struct s3c2416_data *s3c_freq, int idx)
{
#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
struct s3c2416_dvfs *dvfs;
#endif
int ret;
if (s3c_freq->is_dvs) {
pr_debug("cpufreq: already in dvs mode, nothing to do\n");
return 0;
}
pr_debug("cpufreq: switching armclk to hclk (%lukHz)\n",
clk_get_rate(s3c_freq->hclk) / 1000);
ret = clk_set_parent(s3c_freq->armclk, s3c_freq->hclk);
if (ret < 0) {
pr_err("cpufreq: Failed to switch armclk to hclk: %d\n", ret);
return ret;
}
#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
/* changing the core voltage is only allowed when in dvs mode */
if (s3c_freq->vddarm) {
dvfs = &s3c2416_dvfs_table[idx];
pr_debug("cpufreq: setting regulator to %d-%d\n",
dvfs->vddarm_min, dvfs->vddarm_max);
ret = regulator_set_voltage(s3c_freq->vddarm,
dvfs->vddarm_min,
dvfs->vddarm_max);
/* when lowering the voltage failed, there is nothing to do */
if (ret != 0)
pr_err("cpufreq: Failed to set VDDARM: %d\n", ret);
}
#endif
s3c_freq->is_dvs = 1;
return 0;
}
static int s3c2416_cpufreq_leave_dvs(struct s3c2416_data *s3c_freq, int idx)
{
#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
struct s3c2416_dvfs *dvfs;
#endif
int ret;
if (!s3c_freq->is_dvs) {
pr_debug("cpufreq: not in dvs mode, so can't leave\n");
return 0;
}
#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
if (s3c_freq->vddarm) {
dvfs = &s3c2416_dvfs_table[idx];
pr_debug("cpufreq: setting regulator to %d-%d\n",
dvfs->vddarm_min, dvfs->vddarm_max);
ret = regulator_set_voltage(s3c_freq->vddarm,
dvfs->vddarm_min,
dvfs->vddarm_max);
if (ret != 0) {
pr_err("cpufreq: Failed to set VDDARM: %d\n", ret);
return ret;
}
}
#endif
/* force armdiv to hclk frequency for transition from dvs*/
if (clk_get_rate(s3c_freq->armdiv) > clk_get_rate(s3c_freq->hclk)) {
pr_debug("cpufreq: force armdiv to hclk frequency (%lukHz)\n",
clk_get_rate(s3c_freq->hclk) / 1000);
ret = s3c2416_cpufreq_set_armdiv(s3c_freq,
clk_get_rate(s3c_freq->hclk) / 1000);
if (ret < 0) {
pr_err("cpufreq: Failed to set the armdiv to %lukHz: %d\n",
clk_get_rate(s3c_freq->hclk) / 1000, ret);
return ret;
}
}
pr_debug("cpufreq: switching armclk parent to armdiv (%lukHz)\n",
clk_get_rate(s3c_freq->armdiv) / 1000);
ret = clk_set_parent(s3c_freq->armclk, s3c_freq->armdiv);
if (ret < 0) {
pr_err("cpufreq: Failed to switch armclk clock parent to armdiv: %d\n",
ret);
return ret;
}
s3c_freq->is_dvs = 0;
return 0;
}
static int s3c2416_cpufreq_set_target(struct cpufreq_policy *policy,
unsigned int index)
{
struct s3c2416_data *s3c_freq = &s3c2416_cpufreq;
unsigned int new_freq;
int idx, ret, to_dvs = 0;
mutex_lock(&cpufreq_lock);
idx = s3c_freq->freq_table[index].driver_data;
if (idx == SOURCE_HCLK)
to_dvs = 1;
/* switching to dvs when it's not allowed */
if (to_dvs && s3c_freq->disable_dvs) {
pr_debug("cpufreq: entering dvs mode not allowed\n");
ret = -EINVAL;
goto out;
}
/* When leavin dvs mode, always switch the armdiv to the hclk rate
* The S3C2416 has stability issues when switching directly to
* higher frequencies.
*/
new_freq = (s3c_freq->is_dvs && !to_dvs)
? clk_get_rate(s3c_freq->hclk) / 1000
: s3c_freq->freq_table[index].frequency;
if (to_dvs) {
pr_debug("cpufreq: enter dvs\n");
ret = s3c2416_cpufreq_enter_dvs(s3c_freq, idx);
} else if (s3c_freq->is_dvs) {
pr_debug("cpufreq: leave dvs\n");
ret = s3c2416_cpufreq_leave_dvs(s3c_freq, idx);
} else {
pr_debug("cpufreq: change armdiv to %dkHz\n", new_freq);
ret = s3c2416_cpufreq_set_armdiv(s3c_freq, new_freq);
}
out:
mutex_unlock(&cpufreq_lock);
return ret;
}
#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
static void s3c2416_cpufreq_cfg_regulator(struct s3c2416_data *s3c_freq)
{
int count, v, i, found;
struct cpufreq_frequency_table *pos;
struct s3c2416_dvfs *dvfs;
count = regulator_count_voltages(s3c_freq->vddarm);
if (count < 0) {
pr_err("cpufreq: Unable to check supported voltages\n");
return;
}
if (!count)
goto out;
cpufreq_for_each_valid_entry(pos, s3c_freq->freq_table) {
dvfs = &s3c2416_dvfs_table[pos->driver_data];
found = 0;
/* Check only the min-voltage, more is always ok on S3C2416 */
for (i = 0; i < count; i++) {
v = regulator_list_voltage(s3c_freq->vddarm, i);
if (v >= dvfs->vddarm_min)
found = 1;
}
if (!found) {
pr_debug("cpufreq: %dkHz unsupported by regulator\n",
pos->frequency);
pos->frequency = CPUFREQ_ENTRY_INVALID;
}
}
out:
/* Guessed */
s3c_freq->regulator_latency = 1 * 1000 * 1000;
}
#endif
static int s3c2416_cpufreq_reboot_notifier_evt(struct notifier_block *this,
unsigned long event, void *ptr)
{
struct s3c2416_data *s3c_freq = &s3c2416_cpufreq;
int ret;
struct cpufreq_policy *policy;
mutex_lock(&cpufreq_lock);
/* disable further changes */
s3c_freq->disable_dvs = 1;
mutex_unlock(&cpufreq_lock);
/* some boards don't reconfigure the regulator on reboot, which
* could lead to undervolting the cpu when the clock is reset.
* Therefore we always leave the DVS mode on reboot.
*/
if (s3c_freq->is_dvs) {
pr_debug("cpufreq: leave dvs on reboot\n");
policy = cpufreq_cpu_get(0);
if (!policy) {
pr_debug("cpufreq: get no policy for cpu0\n");
return NOTIFY_BAD;
}
ret = cpufreq_driver_target(policy, FREQ_SLEEP, 0);
cpufreq_cpu_put(policy);
if (ret < 0)
return NOTIFY_BAD;
}
return NOTIFY_DONE;
}
static struct notifier_block s3c2416_cpufreq_reboot_notifier = {
.notifier_call = s3c2416_cpufreq_reboot_notifier_evt,
};
static int s3c2416_cpufreq_driver_init(struct cpufreq_policy *policy)
{
struct s3c2416_data *s3c_freq = &s3c2416_cpufreq;
struct cpufreq_frequency_table *pos;
struct clk *msysclk;
unsigned long rate;
int ret;
if (policy->cpu != 0)
return -EINVAL;
msysclk = clk_get(NULL, "msysclk");
if (IS_ERR(msysclk)) {
ret = PTR_ERR(msysclk);
pr_err("cpufreq: Unable to obtain msysclk: %d\n", ret);
return ret;
}
/*
* S3C2416 and S3C2450 share the same processor-ID and also provide no
* other means to distinguish them other than through the rate of
* msysclk. On S3C2416 msysclk runs at 800MHz and on S3C2450 at 533MHz.
*/
rate = clk_get_rate(msysclk);
if (rate == 800 * 1000 * 1000) {
pr_info("cpufreq: msysclk running at %lukHz, using S3C2416 frequency table\n",
rate / 1000);
s3c_freq->freq_table = s3c2416_freq_table;
policy->cpuinfo.max_freq = 400000;
} else if (rate / 1000 == 534000) {
pr_info("cpufreq: msysclk running at %lukHz, using S3C2450 frequency table\n",
rate / 1000);
s3c_freq->freq_table = s3c2450_freq_table;
policy->cpuinfo.max_freq = 534000;
}
/* not needed anymore */
clk_put(msysclk);
if (s3c_freq->freq_table == NULL) {
pr_err("cpufreq: No frequency information for this CPU, msysclk at %lukHz\n",
rate / 1000);
return -ENODEV;
}
s3c_freq->is_dvs = 0;
s3c_freq->armdiv = clk_get(NULL, "armdiv");
if (IS_ERR(s3c_freq->armdiv)) {
ret = PTR_ERR(s3c_freq->armdiv);
pr_err("cpufreq: Unable to obtain ARMDIV: %d\n", ret);
return ret;
}
s3c_freq->hclk = clk_get(NULL, "hclk");
if (IS_ERR(s3c_freq->hclk)) {
ret = PTR_ERR(s3c_freq->hclk);
pr_err("cpufreq: Unable to obtain HCLK: %d\n", ret);
goto err_hclk;
}
/* chech hclk rate, we only support the common 133MHz for now
* hclk could also run at 66MHz, but this not often used
*/
rate = clk_get_rate(s3c_freq->hclk);
if (rate < 133 * 1000 * 1000) {
pr_err("cpufreq: HCLK not at 133MHz\n");
ret = -EINVAL;
goto err_armclk;
}
s3c_freq->armclk = clk_get(NULL, "armclk");
if (IS_ERR(s3c_freq->armclk)) {
ret = PTR_ERR(s3c_freq->armclk);
pr_err("cpufreq: Unable to obtain ARMCLK: %d\n", ret);
goto err_armclk;
}
#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
s3c_freq->vddarm = regulator_get(NULL, "vddarm");
if (IS_ERR(s3c_freq->vddarm)) {
ret = PTR_ERR(s3c_freq->vddarm);
pr_err("cpufreq: Failed to obtain VDDARM: %d\n", ret);
goto err_vddarm;
}
s3c2416_cpufreq_cfg_regulator(s3c_freq);
#else
s3c_freq->regulator_latency = 0;
#endif
cpufreq_for_each_entry(pos, s3c_freq->freq_table) {
/* special handling for dvs mode */
if (pos->driver_data == 0) {
if (!s3c_freq->hclk) {
pr_debug("cpufreq: %dkHz unsupported as it would need unavailable dvs mode\n",
pos->frequency);
pos->frequency = CPUFREQ_ENTRY_INVALID;
} else {
continue;
}
}
/* Check for frequencies we can generate */
rate = clk_round_rate(s3c_freq->armdiv,
pos->frequency * 1000);
rate /= 1000;
if (rate != pos->frequency) {
pr_debug("cpufreq: %dkHz unsupported by clock (clk_round_rate return %lu)\n",
pos->frequency, rate);
pos->frequency = CPUFREQ_ENTRY_INVALID;
}
}
/* Datasheet says PLL stabalisation time must be at least 300us,
* so but add some fudge. (reference in LOCKCON0 register description)
*/
cpufreq_generic_init(policy, s3c_freq->freq_table,
(500 * 1000) + s3c_freq->regulator_latency);
register_reboot_notifier(&s3c2416_cpufreq_reboot_notifier);
return 0;
#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
err_vddarm:
clk_put(s3c_freq->armclk);
#endif
err_armclk:
clk_put(s3c_freq->hclk);
err_hclk:
clk_put(s3c_freq->armdiv);
return ret;
}
static struct cpufreq_driver s3c2416_cpufreq_driver = {
.flags = CPUFREQ_NEED_INITIAL_FREQ_CHECK,
.verify = cpufreq_generic_frequency_table_verify,
.target_index = s3c2416_cpufreq_set_target,
.get = s3c2416_cpufreq_get_speed,
.init = s3c2416_cpufreq_driver_init,
.name = "s3c2416",
.attr = cpufreq_generic_attr,
};
static int __init s3c2416_cpufreq_init(void)
{
return cpufreq_register_driver(&s3c2416_cpufreq_driver);
}
module_init(s3c2416_cpufreq_init);

View File

@ -1,321 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2006-2009 Simtec Electronics
* http://armlinux.simtec.co.uk/
* Ben Dooks <ben@simtec.co.uk>
* Vincent Sanders <vince@simtec.co.uk>
*
* S3C2440/S3C2442 CPU Frequency scaling
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/init.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/ioport.h>
#include <linux/cpufreq.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/soc/samsung/s3c-cpufreq-core.h>
#include <linux/soc/samsung/s3c-pm.h>
#include <asm/mach/arch.h>
#include <asm/mach/map.h>
#define S3C2440_CLKDIVN_PDIVN (1<<0)
#define S3C2440_CLKDIVN_HDIVN_MASK (3<<1)
#define S3C2440_CLKDIVN_HDIVN_1 (0<<1)
#define S3C2440_CLKDIVN_HDIVN_2 (1<<1)
#define S3C2440_CLKDIVN_HDIVN_4_8 (2<<1)
#define S3C2440_CLKDIVN_HDIVN_3_6 (3<<1)
#define S3C2440_CLKDIVN_UCLK (1<<3)
#define S3C2440_CAMDIVN_CAMCLK_MASK (0xf<<0)
#define S3C2440_CAMDIVN_CAMCLK_SEL (1<<4)
#define S3C2440_CAMDIVN_HCLK3_HALF (1<<8)
#define S3C2440_CAMDIVN_HCLK4_HALF (1<<9)
#define S3C2440_CAMDIVN_DVSEN (1<<12)
#define S3C2442_CAMDIVN_CAMCLK_DIV3 (1<<5)
static struct clk *xtal;
static struct clk *fclk;
static struct clk *hclk;
static struct clk *armclk;
/* HDIV: 1, 2, 3, 4, 6, 8 */
static inline int within_khz(unsigned long a, unsigned long b)
{
long diff = a - b;
return (diff >= -1000 && diff <= 1000);
}
/**
* s3c2440_cpufreq_calcdivs - calculate divider settings
* @cfg: The cpu frequency settings.
*
* Calcualte the divider values for the given frequency settings
* specified in @cfg. The values are stored in @cfg for later use
* by the relevant set routine if the request settings can be reached.
*/
static int s3c2440_cpufreq_calcdivs(struct s3c_cpufreq_config *cfg)
{
unsigned int hdiv, pdiv;
unsigned long hclk, fclk, armclk;
unsigned long hclk_max;
fclk = cfg->freq.fclk;
armclk = cfg->freq.armclk;
hclk_max = cfg->max.hclk;
s3c_freq_dbg("%s: fclk is %lu, armclk %lu, max hclk %lu\n",
__func__, fclk, armclk, hclk_max);
if (armclk > fclk) {
pr_warn("%s: armclk > fclk\n", __func__);
armclk = fclk;
}
/* if we are in DVS, we need HCLK to be <= ARMCLK */
if (armclk < fclk && armclk < hclk_max)
hclk_max = armclk;
for (hdiv = 1; hdiv < 9; hdiv++) {
if (hdiv == 5 || hdiv == 7)
hdiv++;
hclk = (fclk / hdiv);
if (hclk <= hclk_max || within_khz(hclk, hclk_max))
break;
}
s3c_freq_dbg("%s: hclk %lu, div %d\n", __func__, hclk, hdiv);
if (hdiv > 8)
goto invalid;
pdiv = (hclk > cfg->max.pclk) ? 2 : 1;
if ((hclk / pdiv) > cfg->max.pclk)
pdiv++;
s3c_freq_dbg("%s: pdiv %d\n", __func__, pdiv);
if (pdiv > 2)
goto invalid;
pdiv *= hdiv;
/* calculate a valid armclk */
if (armclk < hclk)
armclk = hclk;
/* if we're running armclk lower than fclk, this really means
* that the system should go into dvs mode, which means that
* armclk is connected to hclk. */
if (armclk < fclk) {
cfg->divs.dvs = 1;
armclk = hclk;
} else
cfg->divs.dvs = 0;
cfg->freq.armclk = armclk;
/* store the result, and then return */
cfg->divs.h_divisor = hdiv;
cfg->divs.p_divisor = pdiv;
return 0;
invalid:
return -EINVAL;
}
#define CAMDIVN_HCLK_HALF (S3C2440_CAMDIVN_HCLK3_HALF | \
S3C2440_CAMDIVN_HCLK4_HALF)
/**
* s3c2440_cpufreq_setdivs - set the cpu frequency divider settings
* @cfg: The cpu frequency settings.
*
* Set the divisors from the settings in @cfg, which where generated
* during the calculation phase by s3c2440_cpufreq_calcdivs().
*/
static void s3c2440_cpufreq_setdivs(struct s3c_cpufreq_config *cfg)
{
unsigned long clkdiv, camdiv;
s3c_freq_dbg("%s: divisors: h=%d, p=%d\n", __func__,
cfg->divs.h_divisor, cfg->divs.p_divisor);
clkdiv = s3c24xx_read_clkdivn();
camdiv = s3c2440_read_camdivn();
clkdiv &= ~(S3C2440_CLKDIVN_HDIVN_MASK | S3C2440_CLKDIVN_PDIVN);
camdiv &= ~CAMDIVN_HCLK_HALF;
switch (cfg->divs.h_divisor) {
case 1:
clkdiv |= S3C2440_CLKDIVN_HDIVN_1;
break;
case 2:
clkdiv |= S3C2440_CLKDIVN_HDIVN_2;
break;
case 6:
camdiv |= S3C2440_CAMDIVN_HCLK3_HALF;
fallthrough;
case 3:
clkdiv |= S3C2440_CLKDIVN_HDIVN_3_6;
break;
case 8:
camdiv |= S3C2440_CAMDIVN_HCLK4_HALF;
fallthrough;
case 4:
clkdiv |= S3C2440_CLKDIVN_HDIVN_4_8;
break;
default:
BUG(); /* we don't expect to get here. */
}
if (cfg->divs.p_divisor != cfg->divs.h_divisor)
clkdiv |= S3C2440_CLKDIVN_PDIVN;
/* todo - set pclk. */
/* Write the divisors first with hclk intentionally halved so that
* when we write clkdiv we will under-frequency instead of over. We
* then make a short delay and remove the hclk halving if necessary.
*/
s3c2440_write_camdivn(camdiv | CAMDIVN_HCLK_HALF);
s3c24xx_write_clkdivn(clkdiv);
ndelay(20);
s3c2440_write_camdivn(camdiv);
clk_set_parent(armclk, cfg->divs.dvs ? hclk : fclk);
}
static int run_freq_for(unsigned long max_hclk, unsigned long fclk,
int *divs,
struct cpufreq_frequency_table *table,
size_t table_size)
{
unsigned long freq;
int index = 0;
int div;
for (div = *divs; div > 0; div = *divs++) {
freq = fclk / div;
if (freq > max_hclk && div != 1)
continue;
freq /= 1000; /* table is in kHz */
index = s3c_cpufreq_addfreq(table, index, table_size, freq);
if (index < 0)
break;
}
return index;
}
static int hclk_divs[] = { 1, 2, 3, 4, 6, 8, -1 };
static int s3c2440_cpufreq_calctable(struct s3c_cpufreq_config *cfg,
struct cpufreq_frequency_table *table,
size_t table_size)
{
int ret;
WARN_ON(cfg->info == NULL);
WARN_ON(cfg->board == NULL);
ret = run_freq_for(cfg->info->max.hclk,
cfg->info->max.fclk,
hclk_divs,
table, table_size);
s3c_freq_dbg("%s: returning %d\n", __func__, ret);
return ret;
}
static struct s3c_cpufreq_info s3c2440_cpufreq_info = {
.max = {
.fclk = 400000000,
.hclk = 133333333,
.pclk = 66666666,
},
.locktime_m = 300,
.locktime_u = 300,
.locktime_bits = 16,
.name = "s3c244x",
.calc_iotiming = s3c2410_iotiming_calc,
.set_iotiming = s3c2410_iotiming_set,
.get_iotiming = s3c2410_iotiming_get,
.set_fvco = s3c2410_set_fvco,
.set_refresh = s3c2410_cpufreq_setrefresh,
.set_divs = s3c2440_cpufreq_setdivs,
.calc_divs = s3c2440_cpufreq_calcdivs,
.calc_freqtable = s3c2440_cpufreq_calctable,
.debug_io_show = s3c_cpufreq_debugfs_call(s3c2410_iotiming_debugfs),
};
static int s3c2440_cpufreq_add(struct device *dev,
struct subsys_interface *sif)
{
xtal = s3c_cpufreq_clk_get(NULL, "xtal");
hclk = s3c_cpufreq_clk_get(NULL, "hclk");
fclk = s3c_cpufreq_clk_get(NULL, "fclk");
armclk = s3c_cpufreq_clk_get(NULL, "armclk");
if (IS_ERR(xtal) || IS_ERR(hclk) || IS_ERR(fclk) || IS_ERR(armclk)) {
pr_err("%s: failed to get clocks\n", __func__);
return -ENOENT;
}
return s3c_cpufreq_register(&s3c2440_cpufreq_info);
}
static struct subsys_interface s3c2440_cpufreq_interface = {
.name = "s3c2440_cpufreq",
.subsys = &s3c2440_subsys,
.add_dev = s3c2440_cpufreq_add,
};
static int s3c2440_cpufreq_init(void)
{
return subsys_interface_register(&s3c2440_cpufreq_interface);
}
/* arch_initcall adds the clocks we need, so use subsys_initcall. */
subsys_initcall(s3c2440_cpufreq_init);
static struct subsys_interface s3c2442_cpufreq_interface = {
.name = "s3c2442_cpufreq",
.subsys = &s3c2442_subsys,
.add_dev = s3c2440_cpufreq_add,
};
static int s3c2442_cpufreq_init(void)
{
return subsys_interface_register(&s3c2442_cpufreq_interface);
}
subsys_initcall(s3c2442_cpufreq_init);

View File

@ -1,163 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2009 Simtec Electronics
* http://armlinux.simtec.co.uk/
* Ben Dooks <ben@simtec.co.uk>
*
* S3C24XX CPU Frequency scaling - debugfs status support
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/init.h>
#include <linux/export.h>
#include <linux/interrupt.h>
#include <linux/ioport.h>
#include <linux/cpufreq.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <linux/err.h>
#include <linux/soc/samsung/s3c-cpufreq-core.h>
static struct dentry *dbgfs_root;
static struct dentry *dbgfs_file_io;
static struct dentry *dbgfs_file_info;
static struct dentry *dbgfs_file_board;
#define print_ns(x) ((x) / 10), ((x) % 10)
static void show_max(struct seq_file *seq, struct s3c_freq *f)
{
seq_printf(seq, "MAX: F=%lu, H=%lu, P=%lu, A=%lu\n",
f->fclk, f->hclk, f->pclk, f->armclk);
}
static int board_show(struct seq_file *seq, void *p)
{
struct s3c_cpufreq_config *cfg;
struct s3c_cpufreq_board *brd;
cfg = s3c_cpufreq_getconfig();
if (!cfg) {
seq_printf(seq, "no configuration registered\n");
return 0;
}
brd = cfg->board;
if (!brd) {
seq_printf(seq, "no board definition set?\n");
return 0;
}
seq_printf(seq, "SDRAM refresh %u ns\n", brd->refresh);
seq_printf(seq, "auto_io=%u\n", brd->auto_io);
seq_printf(seq, "need_io=%u\n", brd->need_io);
show_max(seq, &brd->max);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(board);
static int info_show(struct seq_file *seq, void *p)
{
struct s3c_cpufreq_config *cfg;
cfg = s3c_cpufreq_getconfig();
if (!cfg) {
seq_printf(seq, "no configuration registered\n");
return 0;
}
seq_printf(seq, " FCLK %ld Hz\n", cfg->freq.fclk);
seq_printf(seq, " HCLK %ld Hz (%lu.%lu ns)\n",
cfg->freq.hclk, print_ns(cfg->freq.hclk_tns));
seq_printf(seq, " PCLK %ld Hz\n", cfg->freq.hclk);
seq_printf(seq, "ARMCLK %ld Hz\n", cfg->freq.armclk);
seq_printf(seq, "\n");
show_max(seq, &cfg->max);
seq_printf(seq, "Divisors: P=%d, H=%d, A=%d, dvs=%s\n",
cfg->divs.h_divisor, cfg->divs.p_divisor,
cfg->divs.arm_divisor, cfg->divs.dvs ? "on" : "off");
seq_printf(seq, "\n");
seq_printf(seq, "lock_pll=%u\n", cfg->lock_pll);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(info);
static int io_show(struct seq_file *seq, void *p)
{
void (*show_bank)(struct seq_file *, struct s3c_cpufreq_config *, union s3c_iobank *);
struct s3c_cpufreq_config *cfg;
struct s3c_iotimings *iot;
union s3c_iobank *iob;
int bank;
cfg = s3c_cpufreq_getconfig();
if (!cfg) {
seq_printf(seq, "no configuration registered\n");
return 0;
}
show_bank = cfg->info->debug_io_show;
if (!show_bank) {
seq_printf(seq, "no code to show bank timing\n");
return 0;
}
iot = s3c_cpufreq_getiotimings();
if (!iot) {
seq_printf(seq, "no io timings registered\n");
return 0;
}
seq_printf(seq, "hclk period is %lu.%lu ns\n", print_ns(cfg->freq.hclk_tns));
for (bank = 0; bank < MAX_BANKS; bank++) {
iob = &iot->bank[bank];
seq_printf(seq, "bank %d: ", bank);
if (!iob->io_2410) {
seq_printf(seq, "nothing set\n");
continue;
}
show_bank(seq, cfg, iob);
}
return 0;
}
DEFINE_SHOW_ATTRIBUTE(io);
static int __init s3c_freq_debugfs_init(void)
{
dbgfs_root = debugfs_create_dir("s3c-cpufreq", NULL);
if (IS_ERR(dbgfs_root)) {
pr_err("%s: error creating debugfs root\n", __func__);
return PTR_ERR(dbgfs_root);
}
dbgfs_file_io = debugfs_create_file("io-timing", S_IRUGO, dbgfs_root,
NULL, &io_fops);
dbgfs_file_info = debugfs_create_file("info", S_IRUGO, dbgfs_root,
NULL, &info_fops);
dbgfs_file_board = debugfs_create_file("board", S_IRUGO, dbgfs_root,
NULL, &board_fops);
return 0;
}
late_initcall(s3c_freq_debugfs_init);

View File

@ -1,648 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2006-2008 Simtec Electronics
* http://armlinux.simtec.co.uk/
* Ben Dooks <ben@simtec.co.uk>
*
* S3C24XX CPU Frequency scaling
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/init.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/ioport.h>
#include <linux/cpufreq.h>
#include <linux/cpu.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/device.h>
#include <linux/sysfs.h>
#include <linux/slab.h>
#include <linux/soc/samsung/s3c-cpufreq-core.h>
#include <linux/soc/samsung/s3c-pm.h>
#include <asm/mach/arch.h>
#include <asm/mach/map.h>
/* note, cpufreq support deals in kHz, no Hz */
static struct cpufreq_driver s3c24xx_driver;
static struct s3c_cpufreq_config cpu_cur;
static struct s3c_iotimings s3c24xx_iotiming;
static struct cpufreq_frequency_table *pll_reg;
static unsigned int last_target = ~0;
static unsigned int ftab_size;
static struct cpufreq_frequency_table *ftab;
static struct clk *_clk_mpll;
static struct clk *_clk_xtal;
static struct clk *clk_fclk;
static struct clk *clk_hclk;
static struct clk *clk_pclk;
static struct clk *clk_arm;
#ifdef CONFIG_ARM_S3C24XX_CPUFREQ_DEBUGFS
struct s3c_cpufreq_config *s3c_cpufreq_getconfig(void)
{
return &cpu_cur;
}
struct s3c_iotimings *s3c_cpufreq_getiotimings(void)
{
return &s3c24xx_iotiming;
}
#endif /* CONFIG_ARM_S3C24XX_CPUFREQ_DEBUGFS */
static void s3c_cpufreq_getcur(struct s3c_cpufreq_config *cfg)
{
unsigned long fclk, pclk, hclk, armclk;
cfg->freq.fclk = fclk = clk_get_rate(clk_fclk);
cfg->freq.hclk = hclk = clk_get_rate(clk_hclk);
cfg->freq.pclk = pclk = clk_get_rate(clk_pclk);
cfg->freq.armclk = armclk = clk_get_rate(clk_arm);
cfg->pll.driver_data = s3c24xx_read_mpllcon();
cfg->pll.frequency = fclk;
cfg->freq.hclk_tns = 1000000000 / (cfg->freq.hclk / 10);
cfg->divs.h_divisor = fclk / hclk;
cfg->divs.p_divisor = fclk / pclk;
}
static inline void s3c_cpufreq_calc(struct s3c_cpufreq_config *cfg)
{
unsigned long pll = cfg->pll.frequency;
cfg->freq.fclk = pll;
cfg->freq.hclk = pll / cfg->divs.h_divisor;
cfg->freq.pclk = pll / cfg->divs.p_divisor;
/* convert hclk into 10ths of nanoseconds for io calcs */
cfg->freq.hclk_tns = 1000000000 / (cfg->freq.hclk / 10);
}
static inline int closer(unsigned int target, unsigned int n, unsigned int c)
{
int diff_cur = abs(target - c);
int diff_new = abs(target - n);
return (diff_new < diff_cur);
}
static void s3c_cpufreq_show(const char *pfx,
struct s3c_cpufreq_config *cfg)
{
s3c_freq_dbg("%s: Fvco=%u, F=%lu, A=%lu, H=%lu (%u), P=%lu (%u)\n",
pfx, cfg->pll.frequency, cfg->freq.fclk, cfg->freq.armclk,
cfg->freq.hclk, cfg->divs.h_divisor,
cfg->freq.pclk, cfg->divs.p_divisor);
}
/* functions to wrapper the driver info calls to do the cpu specific work */
static void s3c_cpufreq_setio(struct s3c_cpufreq_config *cfg)
{
if (cfg->info->set_iotiming)
(cfg->info->set_iotiming)(cfg, &s3c24xx_iotiming);
}
static int s3c_cpufreq_calcio(struct s3c_cpufreq_config *cfg)
{
if (cfg->info->calc_iotiming)
return (cfg->info->calc_iotiming)(cfg, &s3c24xx_iotiming);
return 0;
}
static void s3c_cpufreq_setrefresh(struct s3c_cpufreq_config *cfg)
{
(cfg->info->set_refresh)(cfg);
}
static void s3c_cpufreq_setdivs(struct s3c_cpufreq_config *cfg)
{
(cfg->info->set_divs)(cfg);
}
static int s3c_cpufreq_calcdivs(struct s3c_cpufreq_config *cfg)
{
return (cfg->info->calc_divs)(cfg);
}
static void s3c_cpufreq_setfvco(struct s3c_cpufreq_config *cfg)
{
cfg->mpll = _clk_mpll;
(cfg->info->set_fvco)(cfg);
}
static inline void s3c_cpufreq_updateclk(struct clk *clk,
unsigned int freq)
{
clk_set_rate(clk, freq);
}
static int s3c_cpufreq_settarget(struct cpufreq_policy *policy,
unsigned int target_freq,
struct cpufreq_frequency_table *pll)
{
struct s3c_cpufreq_freqs freqs;
struct s3c_cpufreq_config cpu_new;
unsigned long flags;
cpu_new = cpu_cur; /* copy new from current */
s3c_cpufreq_show("cur", &cpu_cur);
/* TODO - check for DMA currently outstanding */
cpu_new.pll = pll ? *pll : cpu_cur.pll;
if (pll)
freqs.pll_changing = 1;
/* update our frequencies */
cpu_new.freq.armclk = target_freq;
cpu_new.freq.fclk = cpu_new.pll.frequency;
if (s3c_cpufreq_calcdivs(&cpu_new) < 0) {
pr_err("no divisors for %d\n", target_freq);
goto err_notpossible;
}
s3c_freq_dbg("%s: got divs\n", __func__);
s3c_cpufreq_calc(&cpu_new);
s3c_freq_dbg("%s: calculated frequencies for new\n", __func__);
if (cpu_new.freq.hclk != cpu_cur.freq.hclk) {
if (s3c_cpufreq_calcio(&cpu_new) < 0) {
pr_err("%s: no IO timings\n", __func__);
goto err_notpossible;
}
}
s3c_cpufreq_show("new", &cpu_new);
/* setup our cpufreq parameters */
freqs.old = cpu_cur.freq;
freqs.new = cpu_new.freq;
freqs.freqs.old = cpu_cur.freq.armclk / 1000;
freqs.freqs.new = cpu_new.freq.armclk / 1000;
/* update f/h/p clock settings before we issue the change
* notification, so that drivers do not need to do anything
* special if they want to recalculate on CPUFREQ_PRECHANGE. */
s3c_cpufreq_updateclk(_clk_mpll, cpu_new.pll.frequency);
s3c_cpufreq_updateclk(clk_fclk, cpu_new.freq.fclk);
s3c_cpufreq_updateclk(clk_hclk, cpu_new.freq.hclk);
s3c_cpufreq_updateclk(clk_pclk, cpu_new.freq.pclk);
/* start the frequency change */
cpufreq_freq_transition_begin(policy, &freqs.freqs);
/* If hclk is staying the same, then we do not need to
* re-write the IO or the refresh timings whilst we are changing
* speed. */
local_irq_save(flags);
/* is our memory clock slowing down? */
if (cpu_new.freq.hclk < cpu_cur.freq.hclk) {
s3c_cpufreq_setrefresh(&cpu_new);
s3c_cpufreq_setio(&cpu_new);
}
if (cpu_new.freq.fclk == cpu_cur.freq.fclk) {
/* not changing PLL, just set the divisors */
s3c_cpufreq_setdivs(&cpu_new);
} else {
if (cpu_new.freq.fclk < cpu_cur.freq.fclk) {
/* slow the cpu down, then set divisors */
s3c_cpufreq_setfvco(&cpu_new);
s3c_cpufreq_setdivs(&cpu_new);
} else {
/* set the divisors, then speed up */
s3c_cpufreq_setdivs(&cpu_new);
s3c_cpufreq_setfvco(&cpu_new);
}
}
/* did our memory clock speed up */
if (cpu_new.freq.hclk > cpu_cur.freq.hclk) {
s3c_cpufreq_setrefresh(&cpu_new);
s3c_cpufreq_setio(&cpu_new);
}
/* update our current settings */
cpu_cur = cpu_new;
local_irq_restore(flags);
/* notify everyone we've done this */
cpufreq_freq_transition_end(policy, &freqs.freqs, 0);
s3c_freq_dbg("%s: finished\n", __func__);
return 0;
err_notpossible:
pr_err("no compatible settings for %d\n", target_freq);
return -EINVAL;
}
/* s3c_cpufreq_target
*
* called by the cpufreq core to adjust the frequency that the CPU
* is currently running at.
*/
static int s3c_cpufreq_target(struct cpufreq_policy *policy,
unsigned int target_freq,
unsigned int relation)
{
struct cpufreq_frequency_table *pll;
unsigned int index;
/* avoid repeated calls which cause a needless amout of duplicated
* logging output (and CPU time as the calculation process is
* done) */
if (target_freq == last_target)
return 0;
last_target = target_freq;
s3c_freq_dbg("%s: policy %p, target %u, relation %u\n",
__func__, policy, target_freq, relation);
if (ftab) {
index = cpufreq_frequency_table_target(policy, target_freq,
relation);
s3c_freq_dbg("%s: adjust %d to entry %d (%u)\n", __func__,
target_freq, index, ftab[index].frequency);
target_freq = ftab[index].frequency;
}
target_freq *= 1000; /* convert target to Hz */
/* find the settings for our new frequency */
if (!pll_reg || cpu_cur.lock_pll) {
/* either we've not got any PLL values, or we've locked
* to the current one. */
pll = NULL;
} else {
struct cpufreq_policy tmp_policy;
/* we keep the cpu pll table in Hz, to ensure we get an
* accurate value for the PLL output. */
tmp_policy.min = policy->min * 1000;
tmp_policy.max = policy->max * 1000;
tmp_policy.cpu = policy->cpu;
tmp_policy.freq_table = pll_reg;
/* cpufreq_frequency_table_target returns the index
* of the table entry, not the value of
* the table entry's index field. */
index = cpufreq_frequency_table_target(&tmp_policy, target_freq,
relation);
pll = pll_reg + index;
s3c_freq_dbg("%s: target %u => %u\n",
__func__, target_freq, pll->frequency);
target_freq = pll->frequency;
}
return s3c_cpufreq_settarget(policy, target_freq, pll);
}
struct clk *s3c_cpufreq_clk_get(struct device *dev, const char *name)
{
struct clk *clk;
clk = clk_get(dev, name);
if (IS_ERR(clk))
pr_err("failed to get clock '%s'\n", name);
return clk;
}
static int s3c_cpufreq_init(struct cpufreq_policy *policy)
{
policy->clk = clk_arm;
policy->cpuinfo.transition_latency = cpu_cur.info->latency;
policy->freq_table = ftab;
return 0;
}
static int __init s3c_cpufreq_initclks(void)
{
_clk_mpll = s3c_cpufreq_clk_get(NULL, "mpll");
_clk_xtal = s3c_cpufreq_clk_get(NULL, "xtal");
clk_fclk = s3c_cpufreq_clk_get(NULL, "fclk");
clk_hclk = s3c_cpufreq_clk_get(NULL, "hclk");
clk_pclk = s3c_cpufreq_clk_get(NULL, "pclk");
clk_arm = s3c_cpufreq_clk_get(NULL, "armclk");
if (IS_ERR(clk_fclk) || IS_ERR(clk_hclk) || IS_ERR(clk_pclk) ||
IS_ERR(_clk_mpll) || IS_ERR(clk_arm) || IS_ERR(_clk_xtal)) {
pr_err("%s: could not get clock(s)\n", __func__);
return -ENOENT;
}
pr_info("%s: clocks f=%lu,h=%lu,p=%lu,a=%lu\n",
__func__,
clk_get_rate(clk_fclk) / 1000,
clk_get_rate(clk_hclk) / 1000,
clk_get_rate(clk_pclk) / 1000,
clk_get_rate(clk_arm) / 1000);
return 0;
}
#ifdef CONFIG_PM
static struct cpufreq_frequency_table suspend_pll;
static unsigned int suspend_freq;
static int s3c_cpufreq_suspend(struct cpufreq_policy *policy)
{
suspend_pll.frequency = clk_get_rate(_clk_mpll);
suspend_pll.driver_data = s3c24xx_read_mpllcon();
suspend_freq = clk_get_rate(clk_arm);
return 0;
}
static int s3c_cpufreq_resume(struct cpufreq_policy *policy)
{
int ret;
s3c_freq_dbg("%s: resuming with policy %p\n", __func__, policy);
last_target = ~0; /* invalidate last_target setting */
/* whilst we will be called later on, we try and re-set the
* cpu frequencies as soon as possible so that we do not end
* up resuming devices and then immediately having to re-set
* a number of settings once these devices have restarted.
*
* as a note, it is expected devices are not used until they
* have been un-suspended and at that time they should have
* used the updated clock settings.
*/
ret = s3c_cpufreq_settarget(NULL, suspend_freq, &suspend_pll);
if (ret) {
pr_err("%s: failed to reset pll/freq\n", __func__);
return ret;
}
return 0;
}
#else
#define s3c_cpufreq_resume NULL
#define s3c_cpufreq_suspend NULL
#endif
static struct cpufreq_driver s3c24xx_driver = {
.flags = CPUFREQ_NEED_INITIAL_FREQ_CHECK,
.target = s3c_cpufreq_target,
.get = cpufreq_generic_get,
.init = s3c_cpufreq_init,
.suspend = s3c_cpufreq_suspend,
.resume = s3c_cpufreq_resume,
.name = "s3c24xx",
};
int s3c_cpufreq_register(struct s3c_cpufreq_info *info)
{
if (!info || !info->name) {
pr_err("%s: failed to pass valid information\n", __func__);
return -EINVAL;
}
pr_info("S3C24XX CPU Frequency driver, %s cpu support\n",
info->name);
/* check our driver info has valid data */
BUG_ON(info->set_refresh == NULL);
BUG_ON(info->set_divs == NULL);
BUG_ON(info->calc_divs == NULL);
/* info->set_fvco is optional, depending on whether there
* is a need to set the clock code. */
cpu_cur.info = info;
/* Note, driver registering should probably update locktime */
return 0;
}
int __init s3c_cpufreq_setboard(struct s3c_cpufreq_board *board)
{
struct s3c_cpufreq_board *ours;
if (!board) {
pr_info("%s: no board data\n", __func__);
return -EINVAL;
}
/* Copy the board information so that each board can make this
* initdata. */
ours = kzalloc(sizeof(*ours), GFP_KERNEL);
if (!ours)
return -ENOMEM;
*ours = *board;
cpu_cur.board = ours;
return 0;
}
static int __init s3c_cpufreq_auto_io(void)
{
int ret;
if (!cpu_cur.info->get_iotiming) {
pr_err("%s: get_iotiming undefined\n", __func__);
return -ENOENT;
}
pr_info("%s: working out IO settings\n", __func__);
ret = (cpu_cur.info->get_iotiming)(&cpu_cur, &s3c24xx_iotiming);
if (ret)
pr_err("%s: failed to get timings\n", __func__);
return ret;
}
/* if one or is zero, then return the other, otherwise return the min */
#define do_min(_a, _b) ((_a) == 0 ? (_b) : (_b) == 0 ? (_a) : min(_a, _b))
/**
* s3c_cpufreq_freq_min - find the minimum settings for the given freq.
* @dst: The destination structure
* @a: One argument.
* @b: The other argument.
*
* Create a minimum of each frequency entry in the 'struct s3c_freq',
* unless the entry is zero when it is ignored and the non-zero argument
* used.
*/
static void s3c_cpufreq_freq_min(struct s3c_freq *dst,
struct s3c_freq *a, struct s3c_freq *b)
{
dst->fclk = do_min(a->fclk, b->fclk);
dst->hclk = do_min(a->hclk, b->hclk);
dst->pclk = do_min(a->pclk, b->pclk);
dst->armclk = do_min(a->armclk, b->armclk);
}
static inline u32 calc_locktime(u32 freq, u32 time_us)
{
u32 result;
result = freq * time_us;
result = DIV_ROUND_UP(result, 1000 * 1000);
return result;
}
static void s3c_cpufreq_update_loctkime(void)
{
unsigned int bits = cpu_cur.info->locktime_bits;
u32 rate = (u32)clk_get_rate(_clk_xtal);
u32 val;
if (bits == 0) {
WARN_ON(1);
return;
}
val = calc_locktime(rate, cpu_cur.info->locktime_u) << bits;
val |= calc_locktime(rate, cpu_cur.info->locktime_m);
pr_info("%s: new locktime is 0x%08x\n", __func__, val);
s3c24xx_write_locktime(val);
}
static int s3c_cpufreq_build_freq(void)
{
int size, ret;
kfree(ftab);
size = cpu_cur.info->calc_freqtable(&cpu_cur, NULL, 0);
size++;
ftab = kcalloc(size, sizeof(*ftab), GFP_KERNEL);
if (!ftab)
return -ENOMEM;
ftab_size = size;
ret = cpu_cur.info->calc_freqtable(&cpu_cur, ftab, size);
s3c_cpufreq_addfreq(ftab, ret, size, CPUFREQ_TABLE_END);
return 0;
}
static int __init s3c_cpufreq_initcall(void)
{
int ret = 0;
if (cpu_cur.info && cpu_cur.board) {
ret = s3c_cpufreq_initclks();
if (ret)
goto out;
/* get current settings */
s3c_cpufreq_getcur(&cpu_cur);
s3c_cpufreq_show("cur", &cpu_cur);
if (cpu_cur.board->auto_io) {
ret = s3c_cpufreq_auto_io();
if (ret) {
pr_err("%s: failed to get io timing\n",
__func__);
goto out;
}
}
if (cpu_cur.board->need_io && !cpu_cur.info->set_iotiming) {
pr_err("%s: no IO support registered\n", __func__);
ret = -EINVAL;
goto out;
}
if (!cpu_cur.info->need_pll)
cpu_cur.lock_pll = 1;
s3c_cpufreq_update_loctkime();
s3c_cpufreq_freq_min(&cpu_cur.max, &cpu_cur.board->max,
&cpu_cur.info->max);
if (cpu_cur.info->calc_freqtable)
s3c_cpufreq_build_freq();
ret = cpufreq_register_driver(&s3c24xx_driver);
}
out:
return ret;
}
late_initcall(s3c_cpufreq_initcall);
/**
* s3c_plltab_register - register CPU PLL table.
* @plls: The list of PLL entries.
* @plls_no: The size of the PLL entries @plls.
*
* Register the given set of PLLs with the system.
*/
int s3c_plltab_register(struct cpufreq_frequency_table *plls,
unsigned int plls_no)
{
struct cpufreq_frequency_table *vals;
unsigned int size;
size = sizeof(*vals) * (plls_no + 1);
vals = kzalloc(size, GFP_KERNEL);
if (vals) {
memcpy(vals, plls, size);
pll_reg = vals;
/* write a terminating entry, we don't store it in the
* table that is stored in the kernel */
vals += plls_no;
vals->frequency = CPUFREQ_TABLE_END;
pr_info("%d PLL entries\n", plls_no);
} else
pr_err("no memory for PLL tables\n");
return vals ? 0 : -ENOMEM;
}

View File

@ -1,206 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* cpu-sa1100.c: clock scaling for the SA1100
*
* Copyright (C) 2000 2001, The Delft University of Technology
*
* Authors:
* - Johan Pouwelse (J.A.Pouwelse@its.tudelft.nl): initial version
* - Erik Mouw (J.A.K.Mouw@its.tudelft.nl):
* - major rewrite for linux-2.3.99
* - rewritten for the more generic power management scheme in
* linux-2.4.5-rmk1
*
* This software has been developed while working on the LART
* computing board (http://www.lartmaker.nl/), which is
* sponsored by the Mobile Multi-media Communications
* (http://www.mobimedia.org/) and Ubiquitous Communications
* (http://www.ubicom.tudelft.nl/) projects.
*
* The authors can be reached at:
*
* Erik Mouw
* Information and Communication Theory Group
* Faculty of Information Technology and Systems
* Delft University of Technology
* P.O. Box 5031
* 2600 GA Delft
* The Netherlands
*
* Theory of operations
* ====================
*
* Clock scaling can be used to lower the power consumption of the CPU
* core. This will give you a somewhat longer running time.
*
* The SA-1100 has a single register to change the core clock speed:
*
* PPCR 0x90020014 PLL config
*
* However, the DRAM timings are closely related to the core clock
* speed, so we need to change these, too. The used registers are:
*
* MDCNFG 0xA0000000 DRAM config
* MDCAS0 0xA0000004 Access waveform
* MDCAS1 0xA0000008 Access waveform
* MDCAS2 0xA000000C Access waveform
*
* Care must be taken to change the DRAM parameters the correct way,
* because otherwise the DRAM becomes unusable and the kernel will
* crash.
*
* The simple solution to avoid a kernel crash is to put the actual
* clock change in ROM and jump to that code from the kernel. The main
* disadvantage is that the ROM has to be modified, which is not
* possible on all SA-1100 platforms. Another disadvantage is that
* jumping to ROM makes clock switching unnecessary complicated.
*
* The idea behind this driver is that the memory configuration can be
* changed while running from DRAM (even with interrupts turned on!)
* as long as all re-configuration steps yield a valid DRAM
* configuration. The advantages are clear: it will run on all SA-1100
* platforms, and the code is very simple.
*
* If you really want to understand what is going on in
* sa1100_update_dram_timings(), you'll have to read sections 8.2,
* 9.5.7.3, and 10.2 from the "Intel StrongARM SA-1100 Microprocessor
* Developers Manual" (available for free from Intel).
*/
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/init.h>
#include <linux/cpufreq.h>
#include <linux/io.h>
#include <asm/cputype.h>
#include <mach/generic.h>
#include <mach/hardware.h>
struct sa1100_dram_regs {
int speed;
u32 mdcnfg;
u32 mdcas0;
u32 mdcas1;
u32 mdcas2;
};
static struct cpufreq_driver sa1100_driver;
static struct sa1100_dram_regs sa1100_dram_settings[] = {
/*speed, mdcnfg, mdcas0, mdcas1, mdcas2, clock freq */
{ 59000, 0x00dc88a3, 0xcccccccf, 0xfffffffc, 0xffffffff},/* 59.0 MHz */
{ 73700, 0x011490a3, 0xcccccccf, 0xfffffffc, 0xffffffff},/* 73.7 MHz */
{ 88500, 0x014e90a3, 0xcccccccf, 0xfffffffc, 0xffffffff},/* 88.5 MHz */
{103200, 0x01889923, 0xcccccccf, 0xfffffffc, 0xffffffff},/* 103.2 MHz */
{118000, 0x01c29923, 0x9999998f, 0xfffffff9, 0xffffffff},/* 118.0 MHz */
{132700, 0x01fb2123, 0x9999998f, 0xfffffff9, 0xffffffff},/* 132.7 MHz */
{147500, 0x02352123, 0x3333330f, 0xfffffff3, 0xffffffff},/* 147.5 MHz */
{162200, 0x026b29a3, 0x38e38e1f, 0xfff8e38e, 0xffffffff},/* 162.2 MHz */
{176900, 0x02a329a3, 0x71c71c1f, 0xfff1c71c, 0xffffffff},/* 176.9 MHz */
{191700, 0x02dd31a3, 0xe38e383f, 0xffe38e38, 0xffffffff},/* 191.7 MHz */
{206400, 0x03153223, 0xc71c703f, 0xffc71c71, 0xffffffff},/* 206.4 MHz */
{221200, 0x034fba23, 0xc71c703f, 0xffc71c71, 0xffffffff},/* 221.2 MHz */
{235900, 0x03853a23, 0xe1e1e07f, 0xe1e1e1e1, 0xffffffe1},/* 235.9 MHz */
{250700, 0x03bf3aa3, 0xc3c3c07f, 0xc3c3c3c3, 0xffffffc3},/* 250.7 MHz */
{265400, 0x03f7c2a3, 0xc3c3c07f, 0xc3c3c3c3, 0xffffffc3},/* 265.4 MHz */
{280200, 0x0431c2a3, 0x878780ff, 0x87878787, 0xffffff87},/* 280.2 MHz */
{ 0, 0, 0, 0, 0 } /* last entry */
};
static void sa1100_update_dram_timings(int current_speed, int new_speed)
{
struct sa1100_dram_regs *settings = sa1100_dram_settings;
/* find speed */
while (settings->speed != 0) {
if (new_speed == settings->speed)
break;
settings++;
}
if (settings->speed == 0) {
panic("%s: couldn't find dram setting for speed %d\n",
__func__, new_speed);
}
/* No risk, no fun: run with interrupts on! */
if (new_speed > current_speed) {
/* We're going FASTER, so first relax the memory
* timings before changing the core frequency
*/
/* Half the memory access clock */
MDCNFG |= MDCNFG_CDB2;
/* The order of these statements IS important, keep 8
* pulses!!
*/
MDCAS2 = settings->mdcas2;
MDCAS1 = settings->mdcas1;
MDCAS0 = settings->mdcas0;
MDCNFG = settings->mdcnfg;
} else {
/* We're going SLOWER: first decrease the core
* frequency and then tighten the memory settings.
*/
/* Half the memory access clock */
MDCNFG |= MDCNFG_CDB2;
/* The order of these statements IS important, keep 8
* pulses!!
*/
MDCAS0 = settings->mdcas0;
MDCAS1 = settings->mdcas1;
MDCAS2 = settings->mdcas2;
MDCNFG = settings->mdcnfg;
}
}
static int sa1100_target(struct cpufreq_policy *policy, unsigned int ppcr)
{
unsigned int cur = sa11x0_getspeed(0);
unsigned int new_freq;
new_freq = sa11x0_freq_table[ppcr].frequency;
if (new_freq > cur)
sa1100_update_dram_timings(cur, new_freq);
PPCR = ppcr;
if (new_freq < cur)
sa1100_update_dram_timings(cur, new_freq);
return 0;
}
static int __init sa1100_cpu_init(struct cpufreq_policy *policy)
{
cpufreq_generic_init(policy, sa11x0_freq_table, 0);
return 0;
}
static struct cpufreq_driver sa1100_driver __refdata = {
.flags = CPUFREQ_NEED_INITIAL_FREQ_CHECK |
CPUFREQ_NO_AUTO_DYNAMIC_SWITCHING,
.verify = cpufreq_generic_frequency_table_verify,
.target_index = sa1100_target,
.get = sa11x0_getspeed,
.init = sa1100_cpu_init,
.name = "sa1100",
};
static int __init sa1100_dram_init(void)
{
if (cpu_is_sa1100())
return cpufreq_register_driver(&sa1100_driver);
else
return -ENODEV;
}
arch_initcall(sa1100_dram_init);

View File

@ -259,7 +259,7 @@ static int sti_cpufreq_fetch_syscon_registers(void)
return 0;
}
static int sti_cpufreq_init(void)
static int __init sti_cpufreq_init(void)
{
int ret;

View File

@ -407,7 +407,7 @@ fail_put_node:
return ret;
}
static int ti_cpufreq_init(void)
static int __init ti_cpufreq_init(void)
{
const struct of_device_id *match;

View File

@ -14,6 +14,8 @@
#include <linux/completion.h>
#include <linux/kobject.h>
#include <linux/notifier.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/pm_opp.h>
#include <linux/pm_qos.h>
#include <linux/spinlock.h>
@ -446,7 +448,7 @@ struct cpufreq_driver {
#define CPUFREQ_NO_AUTO_DYNAMIC_SWITCHING BIT(6)
int cpufreq_register_driver(struct cpufreq_driver *driver_data);
int cpufreq_unregister_driver(struct cpufreq_driver *driver_data);
void cpufreq_unregister_driver(struct cpufreq_driver *driver_data);
bool cpufreq_driver_test_flags(u16 flags);
const char *cpufreq_get_current_driver(void);
@ -1107,6 +1109,57 @@ cpufreq_table_set_inefficient(struct cpufreq_policy *policy,
return -EINVAL;
}
static inline int parse_perf_domain(int cpu, const char *list_name,
const char *cell_name,
struct of_phandle_args *args)
{
struct device_node *cpu_np;
int ret;
cpu_np = of_cpu_device_node_get(cpu);
if (!cpu_np)
return -ENODEV;
ret = of_parse_phandle_with_args(cpu_np, list_name, cell_name, 0,
args);
if (ret < 0)
return ret;
of_node_put(cpu_np);
return 0;
}
static inline int of_perf_domain_get_sharing_cpumask(int pcpu, const char *list_name,
const char *cell_name, struct cpumask *cpumask,
struct of_phandle_args *pargs)
{
int cpu, ret;
struct of_phandle_args args;
ret = parse_perf_domain(pcpu, list_name, cell_name, pargs);
if (ret < 0)
return ret;
cpumask_set_cpu(pcpu, cpumask);
for_each_possible_cpu(cpu) {
if (cpu == pcpu)
continue;
ret = parse_perf_domain(cpu, list_name, cell_name, &args);
if (ret < 0)
continue;
if (pargs->np == args.np && pargs->args_count == args.args_count &&
!memcmp(pargs->args, args.args, sizeof(args.args[0]) * args.args_count))
cpumask_set_cpu(cpu, cpumask);
of_node_put(args.np);
}
return 0;
}
#else
static inline int cpufreq_boost_trigger_state(int state)
{
@ -1133,6 +1186,13 @@ cpufreq_table_set_inefficient(struct cpufreq_policy *policy,
{
return -EINVAL;
}
static inline int of_perf_domain_get_sharing_cpumask(int pcpu, const char *list_name,
const char *cell_name, struct cpumask *cpumask,
struct of_phandle_args *pargs)
{
return -EOPNOTSUPP;
}
#endif
#if defined(CONFIG_ENERGY_MODEL) && defined(CONFIG_CPU_FREQ_GOV_SCHEDUTIL)
@ -1154,7 +1214,6 @@ void arch_set_freq_scale(const struct cpumask *cpus,
{
}
#endif
/* the following are really really optional */
extern struct freq_attr cpufreq_freq_attr_scaling_available_freqs;
extern struct freq_attr cpufreq_freq_attr_scaling_boost_freqs;

View File

@ -1,299 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2006-2009 Simtec Electronics
* http://armlinux.simtec.co.uk/
* Ben Dooks <ben@simtec.co.uk>
*
* S3C CPU frequency scaling support - core support
*/
#ifndef __LINUX_SOC_SAMSUNG_S3C_CPUFREQ_CORE_H
#define __LINUX_SOC_SAMSUNG_S3C_CPUFREQ_CORE_H
#include <linux/soc/samsung/s3c-cpu-freq.h>
struct seq_file;
#define MAX_BANKS (8)
#define S3C2412_MAX_IO (8)
/**
* struct s3c2410_iobank_timing - IO bank timings for S3C2410 style timings
* @bankcon: The cached version of settings in this structure.
* @tacp:
* @tacs: Time from address valid to nCS asserted.
* @tcos: Time from nCS asserted to nOE or nWE asserted.
* @tacc: Time that nOE or nWE is asserted.
* @tcoh: Time nCS is held after nOE or nWE are released.
* @tcah: Time address is held for after
* @nwait_en: Whether nWAIT is enabled for this bank.
*
* This structure represents the IO timings for a S3C2410 style IO bank
* used by the CPU frequency support if it needs to change the settings
* of the IO.
*/
struct s3c2410_iobank_timing {
unsigned long bankcon;
unsigned int tacp;
unsigned int tacs;
unsigned int tcos;
unsigned int tacc;
unsigned int tcoh; /* nCS hold after nOE/nWE */
unsigned int tcah; /* Address hold after nCS */
unsigned char nwait_en; /* nWait enabled for bank. */
};
/**
* struct s3c2412_iobank_timing - io timings for PL092 (S3C2412) style IO
* @idcy: The idle cycle time between transactions.
* @wstrd: nCS release to end of read cycle.
* @wstwr: nCS release to end of write cycle.
* @wstoen: nCS assertion to nOE assertion time.
* @wstwen: nCS assertion to nWE assertion time.
* @wstbrd: Burst ready delay.
* @smbidcyr: Register cache for smbidcyr value.
* @smbwstrd: Register cache for smbwstrd value.
* @smbwstwr: Register cache for smbwstwr value.
* @smbwstoen: Register cache for smbwstoen value.
* @smbwstwen: Register cache for smbwstwen value.
* @smbwstbrd: Register cache for smbwstbrd value.
*
* Timing information for a IO bank on an S3C2412 or similar system which
* uses a PL093 block.
*/
struct s3c2412_iobank_timing {
unsigned int idcy;
unsigned int wstrd;
unsigned int wstwr;
unsigned int wstoen;
unsigned int wstwen;
unsigned int wstbrd;
/* register cache */
unsigned char smbidcyr;
unsigned char smbwstrd;
unsigned char smbwstwr;
unsigned char smbwstoen;
unsigned char smbwstwen;
unsigned char smbwstbrd;
};
union s3c_iobank {
struct s3c2410_iobank_timing *io_2410;
struct s3c2412_iobank_timing *io_2412;
};
/**
* struct s3c_iotimings - Chip IO timings holder
* @bank: The timings for each IO bank.
*/
struct s3c_iotimings {
union s3c_iobank bank[MAX_BANKS];
};
/**
* struct s3c_plltab - PLL table information.
* @vals: List of PLL values.
* @size: Size of the PLL table @vals.
*/
struct s3c_plltab {
struct s3c_pllval *vals;
int size;
};
/**
* struct s3c_cpufreq_config - current cpu frequency configuration
* @freq: The current settings for the core clocks.
* @max: Maxium settings, derived from core, board and user settings.
* @pll: The PLL table entry for the current PLL settings.
* @divs: The divisor settings for the core clocks.
* @info: The current core driver information.
* @board: The information for the board we are running on.
* @lock_pll: Set if the PLL settings cannot be changed.
*
* This is for the core drivers that need to know information about
* the current settings and values. It should not be needed by any
* device drivers.
*/
struct s3c_cpufreq_config {
struct s3c_freq freq;
struct s3c_freq max;
struct clk *mpll;
struct cpufreq_frequency_table pll;
struct s3c_clkdivs divs;
struct s3c_cpufreq_info *info; /* for core, not drivers */
struct s3c_cpufreq_board *board;
unsigned int lock_pll:1;
};
/**
* struct s3c_cpufreq_info - Information for the CPU frequency driver.
* @name: The name of this implementation.
* @max: The maximum frequencies for the system.
* @latency: Transition latency to give to cpufreq.
* @locktime_m: The lock-time in uS for the MPLL.
* @locktime_u: The lock-time in uS for the UPLL.
* @locttime_bits: The number of bits each LOCKTIME field.
* @need_pll: Set if this driver needs to change the PLL values to achieve
* any frequency changes. This is really only need by devices like the
* S3C2410 where there is no or limited divider between the PLL and the
* ARMCLK.
* @get_iotiming: Get the current IO timing data, mainly for use at start.
* @set_iotiming: Update the IO timings from the cached copies calculated
* from the @calc_iotiming entry when changing the frequency.
* @calc_iotiming: Calculate and update the cached copies of the IO timings
* from the newly calculated frequencies.
* @calc_freqtable: Calculate (fill in) the given frequency table from the
* current frequency configuration. If the table passed in is NULL,
* then the return is the number of elements to be filled for allocation
* of the table.
* @set_refresh: Set the memory refresh configuration.
* @set_fvco: Set the PLL frequencies.
* @set_divs: Update the clock divisors.
* @calc_divs: Calculate the clock divisors.
*/
struct s3c_cpufreq_info {
const char *name;
struct s3c_freq max;
unsigned int latency;
unsigned int locktime_m;
unsigned int locktime_u;
unsigned char locktime_bits;
unsigned int need_pll:1;
/* driver routines */
int (*get_iotiming)(struct s3c_cpufreq_config *cfg,
struct s3c_iotimings *timings);
void (*set_iotiming)(struct s3c_cpufreq_config *cfg,
struct s3c_iotimings *timings);
int (*calc_iotiming)(struct s3c_cpufreq_config *cfg,
struct s3c_iotimings *timings);
int (*calc_freqtable)(struct s3c_cpufreq_config *cfg,
struct cpufreq_frequency_table *t,
size_t table_size);
void (*debug_io_show)(struct seq_file *seq,
struct s3c_cpufreq_config *cfg,
union s3c_iobank *iob);
void (*set_refresh)(struct s3c_cpufreq_config *cfg);
void (*set_fvco)(struct s3c_cpufreq_config *cfg);
void (*set_divs)(struct s3c_cpufreq_config *cfg);
int (*calc_divs)(struct s3c_cpufreq_config *cfg);
};
extern int s3c_cpufreq_register(struct s3c_cpufreq_info *info);
extern int s3c_plltab_register(struct cpufreq_frequency_table *plls,
unsigned int plls_no);
/* exports and utilities for debugfs */
extern struct s3c_cpufreq_config *s3c_cpufreq_getconfig(void);
extern struct s3c_iotimings *s3c_cpufreq_getiotimings(void);
#ifdef CONFIG_ARM_S3C24XX_CPUFREQ_DEBUGFS
#define s3c_cpufreq_debugfs_call(x) x
#else
#define s3c_cpufreq_debugfs_call(x) NULL
#endif
/* Useful utility functions. */
extern struct clk *s3c_cpufreq_clk_get(struct device *, const char *);
/* S3C2410 and compatible exported functions */
extern void s3c2410_cpufreq_setrefresh(struct s3c_cpufreq_config *cfg);
extern void s3c2410_set_fvco(struct s3c_cpufreq_config *cfg);
#ifdef CONFIG_S3C2410_IOTIMING
extern void s3c2410_iotiming_debugfs(struct seq_file *seq,
struct s3c_cpufreq_config *cfg,
union s3c_iobank *iob);
extern int s3c2410_iotiming_calc(struct s3c_cpufreq_config *cfg,
struct s3c_iotimings *iot);
extern int s3c2410_iotiming_get(struct s3c_cpufreq_config *cfg,
struct s3c_iotimings *timings);
extern void s3c2410_iotiming_set(struct s3c_cpufreq_config *cfg,
struct s3c_iotimings *iot);
#else
#define s3c2410_iotiming_debugfs NULL
#define s3c2410_iotiming_calc NULL
#define s3c2410_iotiming_get NULL
#define s3c2410_iotiming_set NULL
#endif /* CONFIG_S3C2410_IOTIMING */
/* S3C2412 compatible routines */
#ifdef CONFIG_S3C2412_IOTIMING
extern void s3c2412_iotiming_debugfs(struct seq_file *seq,
struct s3c_cpufreq_config *cfg,
union s3c_iobank *iob);
extern int s3c2412_iotiming_get(struct s3c_cpufreq_config *cfg,
struct s3c_iotimings *timings);
extern int s3c2412_iotiming_calc(struct s3c_cpufreq_config *cfg,
struct s3c_iotimings *iot);
extern void s3c2412_iotiming_set(struct s3c_cpufreq_config *cfg,
struct s3c_iotimings *iot);
extern void s3c2412_cpufreq_setrefresh(struct s3c_cpufreq_config *cfg);
#else
#define s3c2412_iotiming_debugfs NULL
#define s3c2412_iotiming_calc NULL
#define s3c2412_iotiming_get NULL
#define s3c2412_iotiming_set NULL
#endif /* CONFIG_S3C2412_IOTIMING */
#ifdef CONFIG_ARM_S3C24XX_CPUFREQ_DEBUG
#define s3c_freq_dbg(x...) printk(KERN_INFO x)
#else
#define s3c_freq_dbg(x...) do { if (0) printk(x); } while (0)
#endif /* CONFIG_ARM_S3C24XX_CPUFREQ_DEBUG */
#ifdef CONFIG_ARM_S3C24XX_CPUFREQ_IODEBUG
#define s3c_freq_iodbg(x...) printk(KERN_INFO x)
#else
#define s3c_freq_iodbg(x...) do { if (0) printk(x); } while (0)
#endif /* CONFIG_ARM_S3C24XX_CPUFREQ_IODEBUG */
static inline int s3c_cpufreq_addfreq(struct cpufreq_frequency_table *table,
int index, size_t table_size,
unsigned int freq)
{
if (index < 0)
return index;
if (table) {
if (index >= table_size)
return -ENOMEM;
s3c_freq_dbg("%s: { %d = %u kHz }\n",
__func__, index, freq);
table[index].driver_data = index;
table[index].frequency = freq;
}
return index + 1;
}
u32 s3c2440_read_camdivn(void);
void s3c2440_write_camdivn(u32 camdiv);
u32 s3c24xx_read_clkdivn(void);
void s3c24xx_write_clkdivn(u32 clkdiv);
u32 s3c24xx_read_mpllcon(void);
void s3c24xx_write_locktime(u32 locktime);
#endif