cpufreq: remove s3c24xx drivers

All s3c24xx platforms were removed, so these five drivers are all
obsolete now.

Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
Acked-by: Viresh Kumar <viresh.kumar@linaro.org>
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
This commit is contained in:
Arnd Bergmann 2022-09-30 13:41:15 +02:00
parent a7ddf74b78
commit 014e79d7ec
9 changed files with 0 additions and 2402 deletions

View file

@ -189,84 +189,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

View file

@ -71,13 +71,7 @@ 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_SA1110_CPUFREQ) += sa1110-cpufreq.o
obj-$(CONFIG_ARM_SCMI_CPUFREQ) += scmi-cpufreq.o

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,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