Merge branch 'next-s3c24xx-cpufreq' into next-s3c

This commit is contained in:
Ben Dooks 2009-08-14 15:23:45 +01:00
commit 215ed3236a
38 changed files with 3605 additions and 26 deletions

View file

@ -0,0 +1,75 @@
S3C24XX CPUfreq support
=======================
Introduction
------------
The S3C24XX series support a number of power saving systems, such as
the ability to change the core, memory and peripheral operating
frequencies. The core control is exported via the CPUFreq driver
which has a number of different manual or automatic controls over the
rate the core is running at.
There are two forms of the driver depending on the specific CPU and
how the clocks are arranged. The first implementation used as single
PLL to feed the ARM, memory and peripherals via a series of dividers
and muxes and this is the implementation that is documented here. A
newer version where there is a seperate PLL and clock divider for the
ARM core is available as a seperate driver.
Layout
------
The code core manages the CPU specific drivers, any data that they
need to register and the interface to the generic drivers/cpufreq
system. Each CPU registers a driver to control the PLL, clock dividers
and anything else associated with it. Any board that wants to use this
framework needs to supply at least basic details of what is required.
The core registers with drivers/cpufreq at init time if all the data
necessary has been supplied.
CPU support
-----------
The support for each CPU depends on the facilities provided by the
SoC and the driver as each device has different PLL and clock chains
associated with it.
Slow Mode
---------
The SLOW mode where the PLL is turned off altogether and the
system is fed by the external crystal input is currently not
supported.
sysfs
-----
The core code exports extra information via sysfs in the directory
devices/system/cpu/cpu0/arch-freq.
Board Support
-------------
Each board that wants to use the cpufreq code must register some basic
information with the core driver to provide information about what the
board requires and any restrictions being placed on it.
The board needs to supply information about whether it needs the IO bank
timings changing, any maximum frequency limits and information about the
SDRAM refresh rate.
Document Author
---------------
Ben Dooks, Copyright 2009 Simtec Electronics
Licensed under GPLv2

View file

@ -126,6 +126,13 @@ config ARCH_HAS_ILOG2_U32
config ARCH_HAS_ILOG2_U64
bool
config ARCH_HAS_CPUFREQ
bool
help
Internal node to signify that the ARCH has CPUFREQ support
and that the relevant menu configurations are displayed for
it.
config GENERIC_HWEIGHT
bool
default y
@ -203,6 +210,7 @@ config ARCH_AAEC2000
config ARCH_INTEGRATOR
bool "ARM Ltd. Integrator family"
select ARM_AMBA
select ARCH_HAS_CPUFREQ
select HAVE_CLK
select COMMON_CLKDEV
select ICST525
@ -509,6 +517,7 @@ config ARCH_PXA
bool "PXA2xx/PXA3xx-based"
depends on MMU
select ARCH_MTD_XIP
select ARCH_HAS_CPUFREQ
select GENERIC_GPIO
select HAVE_CLK
select COMMON_CLKDEV
@ -551,6 +560,7 @@ config ARCH_SA1100
select ISA
select ARCH_SPARSEMEM_ENABLE
select ARCH_MTD_XIP
select ARCH_HAS_CPUFREQ
select GENERIC_GPIO
select GENERIC_TIME
select GENERIC_CLOCKEVENTS
@ -563,6 +573,7 @@ config ARCH_SA1100
config ARCH_S3C2410
bool "Samsung S3C2410, S3C2412, S3C2413, S3C2440, S3C2442, S3C2443"
select GENERIC_GPIO
select ARCH_HAS_CPUFREQ
select HAVE_CLK
help
Samsung S3C2410X CPU based systems, such as the Simtec Electronics
@ -573,6 +584,7 @@ config ARCH_S3C64XX
bool "Samsung S3C64XX"
select GENERIC_GPIO
select HAVE_CLK
select ARCH_HAS_CPUFREQ
help
Samsung S3C64XX series based systems
@ -632,6 +644,7 @@ config ARCH_OMAP
select GENERIC_GPIO
select HAVE_CLK
select ARCH_REQUIRE_GPIOLIB
select ARCH_HAS_CPUFREQ
select GENERIC_TIME
select GENERIC_CLOCKEVENTS
help
@ -1241,7 +1254,7 @@ endmenu
menu "CPU Power Management"
if (ARCH_SA1100 || ARCH_INTEGRATOR || ARCH_OMAP || ARCH_PXA || ARCH_S3C64XX)
if ARCH_HAS_CPUFREQ
source "drivers/cpufreq/Kconfig"
@ -1276,6 +1289,52 @@ config CPU_FREQ_S3C64XX
bool "CPUfreq support for Samsung S3C64XX CPUs"
depends on CPU_FREQ && CPU_S3C6410
config CPU_FREQ_S3C
bool
help
Internal configuration node for common cpufreq on Samsung SoC
config CPU_FREQ_S3C24XX
bool "CPUfreq driver for Samsung S3C24XX series CPUs"
depends on ARCH_S3C2410 && CPU_FREQ && EXPERIMENTAL
select CPU_FREQ_S3C
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 CPU_FREQ_S3C24XX_PLL
bool "Support CPUfreq changing of PLL frequency"
depends on CPU_FREQ_S3C24XX && EXPERIMENTAL
help
Compile in support for changing the PLL frequency from the
S3C24XX series CPUfreq driver. The PLL takes time to settle
after a frequency change, so by default it is not enabled.
This also means that the PLL tables for the selected CPU(s) will
be built which may increase the size of the kernel image.
config CPU_FREQ_S3C24XX_DEBUG
bool "Debug CPUfreq Samsung driver core"
depends on CPU_FREQ_S3C24XX
help
Enable s3c_freq_dbg for the Samsung S3C CPUfreq core
config CPU_FREQ_S3C24XX_IODEBUG
bool "Debug CPUfreq Samsung driver IO timing"
depends on CPU_FREQ_S3C24XX
help
Enable s3c_freq_iodbg for the Samsung S3C CPUfreq core
config CPU_FREQ_S3C24XX_DEBUGFS
bool "Export debugfs for CPUFreq"
depends on CPU_FREQ_S3C24XX && DEBUG_FS
help
Export status information via debugfs.
endif
source "drivers/cpuidle/Kconfig"

View file

@ -12,6 +12,7 @@ config CPU_S3C2410
select S3C2410_GPIO
select CPU_LLSERIAL_S3C2410
select S3C2410_PM if PM
select S3C2410_CPUFREQ if CPU_FREQ_S3C24XX
help
Support for S3C2410 and S3C2410A family from the S3C24XX line
of Samsung Mobile CPUs.
@ -45,6 +46,22 @@ config MACH_BAST_IDE
Internal node for machines with an BAST style IDE
interface
# cpu frequency scaling support
config S3C2410_CPUFREQ
bool
depends on CPU_FREQ_S3C24XX && CPU_S3C2410
select S3C2410_CPUFREQ_UTILS
help
CPU Frequency scaling support for S3C2410
config S3C2410_PLLTABLE
bool
depends on S3C2410_CPUFREQ && CPU_FREQ_S3C24XX_PLL
default y
help
Select the PLL table for the S3C2410
menu "S3C2410 Machines"
config ARCH_SMDK2410
@ -79,6 +96,7 @@ config MACH_N30
config ARCH_BAST
bool "Simtec Electronics BAST (EB2410ITX)"
select CPU_S3C2410
select S3C2410_IOTIMING if S3C2410_CPUFREQ
select PM_SIMTEC if PM
select SIMTEC_NOR
select MACH_BAST_IDE

View file

@ -15,6 +15,8 @@ obj-$(CONFIG_CPU_S3C2410_DMA) += dma.o
obj-$(CONFIG_CPU_S3C2410_DMA) += dma.o
obj-$(CONFIG_S3C2410_PM) += pm.o sleep.o
obj-$(CONFIG_S3C2410_GPIO) += gpio.o
obj-$(CONFIG_S3C2410_CPUFREQ) += cpu-freq.o
obj-$(CONFIG_S3C2410_PLLTABLE) += pll.o
# Machine support

View file

@ -0,0 +1,159 @@
/* linux/arch/arm/mach-s3c2410/cpu-freq.c
*
* Copyright (c) 2006,2008 Simtec Electronics
* http://armlinux.simtec.co.uk/
* Ben Dooks <ben@simtec.co.uk>
*
* S3C2410 CPU Frequency scaling
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/ioport.h>
#include <linux/cpufreq.h>
#include <linux/sysdev.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/io.h>
#include <asm/mach/arch.h>
#include <asm/mach/map.h>
#include <mach/regs-clock.h>
#include <plat/cpu.h>
#include <plat/clock.h>
#include <plat/cpu-freq-core.h>
/* 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;
__raw_writel(clkdiv, S3C2410_CLKDIVN);
}
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,
.resume_clocks = s3c2410_setup_clocks,
.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 sys_device *sysdev)
{
return s3c_cpufreq_register(&s3c2410_cpufreq_info);
}
static struct sysdev_driver s3c2410_cpufreq_driver = {
.add = s3c2410_cpufreq_add,
};
static int __init s3c2410_cpufreq_init(void)
{
return sysdev_driver_register(&s3c2410_sysclass,
&s3c2410_cpufreq_driver);
}
arch_initcall(s3c2410_cpufreq_init);
static int s3c2410a_cpufreq_add(struct sys_device *sysdev)
{
/* 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(sysdev);
}
static struct sysdev_driver s3c2410a_cpufreq_driver = {
.add = s3c2410a_cpufreq_add,
};
static int __init s3c2410a_cpufreq_init(void)
{
return sysdev_driver_register(&s3c2410a_sysclass,
&s3c2410a_cpufreq_driver);
}
arch_initcall(s3c2410a_cpufreq_init);

View file

@ -164,6 +164,17 @@ static int __init s3c2410_dma_drvinit(void)
}
arch_initcall(s3c2410_dma_drvinit);
static struct sysdev_driver s3c2410a_dma_driver = {
.add = s3c2410_dma_add,
};
static int __init s3c2410a_dma_drvinit(void)
{
return sysdev_driver_register(&s3c2410a_sysclass, &s3c2410a_dma_driver);
}
arch_initcall(s3c2410a_dma_drvinit);
#endif
#if defined(CONFIG_CPU_S3C2442)

View file

@ -67,6 +67,13 @@
#define S3C2443_PA_HSMMC (0x4A800000)
#define S3C2443_SZ_HSMMC (256)
/* S3C2412 memory and IO controls */
#define S3C2412_PA_SSMC (0x4F000000)
#define S3C2412_VA_SSMC S3C_ADDR_CPU(0x00000000)
#define S3C2412_PA_EBI (0x48800000)
#define S3C2412_VA_EBI S3C_ADDR_CPU(0x00010000)
/* physical addresses of all the chip-select areas */
#define S3C2410_CS0 (0x00000000)

View file

@ -73,6 +73,16 @@
#define S3C2410_BWSCON_WS7 (1<<30)
#define S3C2410_BWSCON_ST7 (1<<31)
/* accesor functions for getting BANK(n) configuration. (n != 0) */
#define S3C2410_BWSCON_GET(_bwscon, _bank) (((_bwscon) >> ((_bank) * 4)) & 0xf)
#define S3C2410_BWSCON_DW8 (0)
#define S3C2410_BWSCON_DW16 (1)
#define S3C2410_BWSCON_DW32 (2)
#define S3C2410_BWSCON_WS (1 << 2)
#define S3C2410_BWSCON_ST (1 << 3)
/* memory set (rom, ram) */
#define S3C2410_BANKCON0 S3C2410_MEMREG(0x0004)
#define S3C2410_BANKCON1 S3C2410_MEMREG(0x0008)

View file

@ -14,9 +14,11 @@
#ifndef __ASM_ARM_REGS_S3C2412_MEM
#define __ASM_ARM_REGS_S3C2412_MEM
#ifndef S3C2412_MEMREG
#define S3C2412_MEMREG(x) (S3C24XX_VA_MEMCTRL + (x))
#endif
#define S3C2412_EBIREG(x) (S3C2412_VA_EBI + (x))
#define S3C2412_SSMCREG(x) (S3C2412_VA_SSMC + (x))
#define S3C2412_SSMC(x, o) (S3C2412_SSMCREG((x * 0x20) + (o)))
#define S3C2412_BANKCFG S3C2412_MEMREG(0x00)
#define S3C2412_BANKCON1 S3C2412_MEMREG(0x04)
@ -26,4 +28,21 @@
#define S3C2412_REFRESH S3C2412_MEMREG(0x10)
#define S3C2412_TIMEOUT S3C2412_MEMREG(0x14)
/* EBI control registers */
#define S3C2412_EBI_PR S3C2412_EBIREG(0x00)
#define S3C2412_EBI_BANKCFG S3C2412_EBIREG(0x04)
/* SSMC control registers */
#define S3C2412_SSMC_BANK(x) S3C2412_SSMC(x, 0x00)
#define S3C2412_SMIDCYR(x) S3C2412_SSMC(x, 0x00)
#define S3C2412_SMBWSTRD(x) S3C2412_SSMC(x, 0x04)
#define S3C2412_SMBWSTWRR(x) S3C2412_SSMC(x, 0x08)
#define S3C2412_SMBWSTOENR(x) S3C2412_SSMC(x, 0x0C)
#define S3C2412_SMBWSTWENR(x) S3C2412_SSMC(x, 0x10)
#define S3C2412_SMBCR(x) S3C2412_SSMC(x, 0x14)
#define S3C2412_SMBSR(x) S3C2412_SSMC(x, 0x18)
#define S3C2412_SMBWSTBRDR(x) S3C2412_SSMC(x, 0x1C)
#endif /* __ASM_ARM_REGS_S3C2412_MEM */

View file

@ -39,9 +39,22 @@ static struct sysdev_driver s3c2410_irq_driver = {
.resume = s3c24xx_irq_resume,
};
static int s3c2410_irq_init(void)
static int __init s3c2410_irq_init(void)
{
return sysdev_driver_register(&s3c2410_sysclass, &s3c2410_irq_driver);
}
arch_initcall(s3c2410_irq_init);
static struct sysdev_driver s3c2410a_irq_driver = {
.add = s3c2410_irq_add,
.suspend = s3c24xx_irq_suspend,
.resume = s3c24xx_irq_resume,
};
static int __init s3c2410a_irq_init(void)
{
return sysdev_driver_register(&s3c2410a_sysclass, &s3c2410a_irq_driver);
}
arch_initcall(s3c2410a_irq_init);

View file

@ -60,6 +60,7 @@
#include <plat/clock.h>
#include <plat/devs.h>
#include <plat/cpu.h>
#include <plat/cpu-freq.h>
#include "usb-simtec.h"
#include "nor-simtec.h"
@ -601,6 +602,12 @@ static struct clk *bast_clocks[] __initdata = {
&s3c24xx_uclk,
};
static struct s3c_cpufreq_board __initdata bast_cpufreq = {
.refresh = 7800, /* 7.8usec */
.auto_io = 1,
.need_io = 1,
};
static void __init bast_map_io(void)
{
/* initialise the clocks */
@ -640,6 +647,8 @@ static void __init bast_init(void)
usb_simtec_init();
nor_simtec_init();
s3c_cpufreq_setboard(&bast_cpufreq);
}
MACHINE_START(BAST, "Simtec-BAST")

View file

@ -0,0 +1,95 @@
/* arch/arm/mach-s3c2410/pll.c
*
* Copyright (c) 2006,2007 Simtec Electronics
* http://armlinux.simtec.co.uk/
* Ben Dooks <ben@simtec.co.uk>
* Vincent Sanders <vince@arm.linux.org.uk>
*
* S3C2410 CPU PLL tables
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/sysdev.h>
#include <linux/list.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <plat/cpu.h>
#include <plat/cpu-freq-core.h>
static struct cpufreq_frequency_table pll_vals_12MHz[] = {
{ .frequency = 34000000, .index = PLLVAL(82, 2, 3), },
{ .frequency = 45000000, .index = PLLVAL(82, 1, 3), },
{ .frequency = 51000000, .index = PLLVAL(161, 3, 3), },
{ .frequency = 48000000, .index = PLLVAL(120, 2, 3), },
{ .frequency = 56000000, .index = PLLVAL(142, 2, 3), },
{ .frequency = 68000000, .index = PLLVAL(82, 2, 2), },
{ .frequency = 79000000, .index = PLLVAL(71, 1, 2), },
{ .frequency = 85000000, .index = PLLVAL(105, 2, 2), },
{ .frequency = 90000000, .index = PLLVAL(112, 2, 2), },
{ .frequency = 101000000, .index = PLLVAL(127, 2, 2), },
{ .frequency = 113000000, .index = PLLVAL(105, 1, 2), },
{ .frequency = 118000000, .index = PLLVAL(150, 2, 2), },
{ .frequency = 124000000, .index = PLLVAL(116, 1, 2), },
{ .frequency = 135000000, .index = PLLVAL(82, 2, 1), },
{ .frequency = 147000000, .index = PLLVAL(90, 2, 1), },
{ .frequency = 152000000, .index = PLLVAL(68, 1, 1), },
{ .frequency = 158000000, .index = PLLVAL(71, 1, 1), },
{ .frequency = 170000000, .index = PLLVAL(77, 1, 1), },
{ .frequency = 180000000, .index = PLLVAL(82, 1, 1), },
{ .frequency = 186000000, .index = PLLVAL(85, 1, 1), },
{ .frequency = 192000000, .index = PLLVAL(88, 1, 1), },
{ .frequency = 203000000, .index = PLLVAL(161, 3, 1), },
/* 2410A extras */
{ .frequency = 210000000, .index = PLLVAL(132, 2, 1), },
{ .frequency = 226000000, .index = PLLVAL(105, 1, 1), },
{ .frequency = 266000000, .index = PLLVAL(125, 1, 1), },
{ .frequency = 268000000, .index = PLLVAL(126, 1, 1), },
{ .frequency = 270000000, .index = PLLVAL(127, 1, 1), },
};
static int s3c2410_plls_add(struct sys_device *dev)
{
return s3c_plltab_register(pll_vals_12MHz, ARRAY_SIZE(pll_vals_12MHz));
}
static struct sysdev_driver s3c2410_plls_drv = {
.add = s3c2410_plls_add,
};
static int __init s3c2410_pll_init(void)
{
return sysdev_driver_register(&s3c2410_sysclass, &s3c2410_plls_drv);
}
arch_initcall(s3c2410_pll_init);
static struct sysdev_driver s3c2410a_plls_drv = {
.add = s3c2410_plls_add,
};
static int __init s3c2410a_pll_init(void)
{
return sysdev_driver_register(&s3c2410a_sysclass, &s3c2410a_plls_drv);
}
arch_initcall(s3c2410a_pll_init);

View file

@ -119,6 +119,18 @@ static int __init s3c2410_pm_drvinit(void)
}
arch_initcall(s3c2410_pm_drvinit);
static struct sysdev_driver s3c2410a_pm_driver = {
.add = s3c2410_pm_add,
.resume = s3c2410_pm_resume,
};
static int __init s3c2410a_pm_drvinit(void)
{
return sysdev_driver_register(&s3c2410a_sysclass, &s3c2410a_pm_driver);
}
arch_initcall(s3c2410a_pm_drvinit);
#endif
#if defined(CONFIG_CPU_S3C2440)

View file

@ -105,17 +105,33 @@ void __init_or_cpufreq s3c2410_setup_clocks(void)
s3c24xx_setup_clocks(fclk, hclk, pclk);
}
/* fake ARMCLK for use with cpufreq, etc. */
static struct clk s3c2410_armclk = {
.name = "armclk",
.parent = &clk_f,
.id = -1,
};
void __init s3c2410_init_clocks(int xtal)
{
s3c24xx_register_baseclocks(xtal);
s3c2410_setup_clocks();
s3c2410_baseclk_add();
s3c24xx_register_clock(&s3c2410_armclk);
}
struct sysdev_class s3c2410_sysclass = {
.name = "s3c2410-core",
};
/* Note, we would have liked to name this s3c2410-core, but we cannot
* register two sysdev_class with the same name.
*/
struct sysdev_class s3c2410a_sysclass = {
.name = "s3c2410a-core",
};
static struct sys_device s3c2410_sysdev = {
.cls = &s3c2410_sysclass,
};
@ -133,9 +149,22 @@ static int __init s3c2410_core_init(void)
core_initcall(s3c2410_core_init);
static int __init s3c2410a_core_init(void)
{
return sysdev_class_register(&s3c2410a_sysclass);
}
core_initcall(s3c2410a_core_init);
int __init s3c2410_init(void)
{
printk("S3C2410: Initialising architecture\n");
return sysdev_register(&s3c2410_sysdev);
}
int __init s3c2410a_init(void)
{
s3c2410_sysdev.cls = &s3c2410a_sysclass;
return s3c2410_init();
}

View file

@ -32,6 +32,15 @@ config S3C2412_PM
help
Internal config node to apply S3C2412 power management
# Note, the S3C2412 IOtiming support is in plat-s3c24xx
config S3C2412_CPUFREQ
bool
depends on CPU_FREQ_S3C24XX && CPU_S3C2412
select S3C2412_IOTIMING
default y
help
CPU Frequency scaling support for S3C2412 and S3C2413 SoC CPUs.
menu "S3C2412 Machines"

View file

@ -15,6 +15,7 @@ obj-$(CONFIG_CPU_S3C2412) += clock.o
obj-$(CONFIG_CPU_S3C2412) += gpio.o
obj-$(CONFIG_S3C2412_DMA) += dma.o
obj-$(CONFIG_S3C2412_PM) += pm.o sleep.o
obj-$(CONFIG_S3C2412_CPUFREQ) += cpu-freq.o
# Machine support

View file

@ -0,0 +1,257 @@
/* linux/arch/arm/mach-s3c2412/cpu-freq.c
*
* Copyright 2008 Simtec Electronics
* http://armlinux.simtec.co.uk/
* Ben Dooks <ben@simtec.co.uk>
*
* S3C2412 CPU Frequency scalling
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/ioport.h>
#include <linux/cpufreq.h>
#include <linux/sysdev.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/io.h>
#include <asm/mach/arch.h>
#include <asm/mach/map.h>
#include <mach/regs-clock.h>
#include <mach/regs-s3c2412-mem.h>
#include <plat/cpu.h>
#include <plat/clock.h>
#include <plat/cpu-freq-core.h>
/* 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 = __raw_readl(S3C2410_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);
__raw_writel(clkdiv, S3C2410_CLKDIVN);
clk_set_parent(armclk, cfg->divs.dvs ? hclk : fclk);
}
static void s3c2412_cpufreq_setrefresh(struct s3c_cpufreq_config *cfg)
{
struct s3c_cpufreq_board *board = cfg->board;
unsigned long refresh;
s3c_freq_dbg("%s: refresh %u ns, hclk %lu\n", __func__,
board->refresh, cfg->freq.hclk);
/* Reduce both the refresh time (in ns) and the frequency (in MHz)
* by 10 each to ensure that we do not overflow 32 bit numbers. This
* should work for HCLK up to 133MHz and refresh period up to 30usec.
*/
refresh = (board->refresh / 10);
refresh *= (cfg->freq.hclk / 100);
refresh /= (1 * 1000 * 1000); /* 10^6 */
s3c_freq_dbg("%s: setting refresh 0x%08lx\n", __func__, refresh);
__raw_writel(refresh, S3C2412_REFRESH);
}
/* 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,
.resume_clocks = s3c2412_setup_clocks,
.debug_io_show = s3c_cpufreq_debugfs_call(s3c2412_iotiming_debugfs),
};
static int s3c2412_cpufreq_add(struct sys_device *sysdev)
{
unsigned long fclk_rate;
hclk = clk_get(NULL, "hclk");
if (IS_ERR(hclk)) {
printk(KERN_ERR "%s: cannot find hclk clock\n", __func__);
return -ENOENT;
}
fclk = clk_get(NULL, "fclk");
if (IS_ERR(fclk)) {
printk(KERN_ERR "%s: cannot find fclk clock\n", __func__);
goto err_fclk;
}
fclk_rate = clk_get_rate(fclk);
if (fclk_rate > 200000000) {
printk(KERN_INFO
"%s: fclk %ld MHz, assuming 266MHz capable part\n",
__func__, 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)) {
printk(KERN_ERR "%s: cannot find arm clock\n", __func__);
goto err_armclk;
}
xtal = clk_get(NULL, "xtal");
if (IS_ERR(xtal)) {
printk(KERN_ERR "%s: cannot find xtal clock\n", __func__);
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 sysdev_driver s3c2412_cpufreq_driver = {
.add = s3c2412_cpufreq_add,
};
static int s3c2412_cpufreq_init(void)
{
return sysdev_driver_register(&s3c2412_sysclass,
&s3c2412_cpufreq_driver);
}
arch_initcall(s3c2412_cpufreq_init);

View file

@ -69,6 +69,18 @@ static struct map_desc s3c2412_iodesc[] __initdata = {
IODESC_ENT(CLKPWR),
IODESC_ENT(TIMER),
IODESC_ENT(WATCHDOG),
{
.virtual = (unsigned long)S3C2412_VA_SSMC,
.pfn = __phys_to_pfn(S3C2412_PA_SSMC),
.length = SZ_1M,
.type = MT_DEVICE,
},
{
.virtual = (unsigned long)S3C2412_VA_EBI,
.pfn = __phys_to_pfn(S3C2412_PA_EBI),
.length = SZ_1M,
.type = MT_DEVICE,
},
};
/* uart registration process */

View file

@ -33,6 +33,7 @@ config MACH_ANUBIS
select PM_SIMTEC if PM
select HAVE_PATA_PLATFORM
select S3C24XX_GPIO_EXTRA64
select S3C2440_XTAL_12000000
select S3C_DEV_USB_HOST
help
Say Y here if you are using the Simtec Electronics ANUBIS
@ -44,6 +45,8 @@ config MACH_OSIRIS
select S3C24XX_DCLK
select PM_SIMTEC if PM
select S3C24XX_GPIO_EXTRA128
select S3C2440_XTAL_12000000
select S3C2410_IOTIMING if S3C2440_CPUFREQ
select S3C_DEV_USB_HOST
help
Say Y here if you are using the Simtec IM2440D20 module, also
@ -52,6 +55,7 @@ config MACH_OSIRIS
config MACH_RX3715
bool "HP iPAQ rx3715"
select CPU_S3C2440
select S3C2440_XTAL_16934400
select PM_H1940 if PM
help
Say Y here if you are using the HP iPAQ rx3715.
@ -59,6 +63,7 @@ config MACH_RX3715
config ARCH_S3C2440
bool "SMDK2440"
select CPU_S3C2440
select S3C2440_XTAL_16934400
select MACH_SMDK
select S3C_DEV_USB_HOST
help
@ -67,6 +72,7 @@ config ARCH_S3C2440
config MACH_NEXCODER_2440
bool "NexVision NEXCODER 2440 Light Board"
select CPU_S3C2440
select S3C2440_XTAL_12000000
select S3C_DEV_USB_HOST
help
Say Y here if you are using the Nex Vision NEXCODER 2440 Light Board
@ -75,6 +81,7 @@ config SMDK2440_CPU2440
bool "SMDK2440 with S3C2440 CPU module"
depends on ARCH_S3C2440
default y if ARCH_S3C2440
select S3C2440_XTAL_16934400
select CPU_S3C2440
config MACH_AT2440EVB

View file

@ -34,6 +34,7 @@
#include <asm/irq.h>
#include <asm/mach-types.h>
#include <plat/cpu-freq.h>
#include <plat/regs-serial.h>
#include <mach/regs-gpio.h>
#include <mach/regs-mem.h>
@ -351,6 +352,12 @@ static struct clk *osiris_clocks[] __initdata = {
&s3c24xx_uclk,
};
static struct s3c_cpufreq_board __initdata osiris_cpufreq = {
.refresh = 7800, /* refresh period is 7.8usec */
.auto_io = 1,
.need_io = 1,
};
static void __init osiris_map_io(void)
{
unsigned long flags;
@ -402,6 +409,8 @@ static void __init osiris_init(void)
s3c_i2c0_set_platdata(NULL);
s3c_cpufreq_setboard(&osiris_cpufreq);
i2c_register_board_info(0, osiris_i2c_devs,
ARRAY_SIZE(osiris_i2c_devs));

View file

@ -49,7 +49,7 @@
#define S3C64XX_PA_IIC1 (0x7F00F000)
#define S3C64XX_PA_GPIO (0x7F008000)
#define S3C64XX_VA_GPIO S3C_ADDR(0x00500000)
#define S3C64XX_VA_GPIO S3C_ADDR_CPU(0x00000000)
#define S3C64XX_SZ_GPIO SZ_4K
#define S3C64XX_PA_SDRAM (0x50000000)
@ -57,7 +57,7 @@
#define S3C64XX_PA_VIC1 (0x71300000)
#define S3C64XX_PA_MODEM (0x74108000)
#define S3C64XX_VA_MODEM S3C_ADDR(0x00600000)
#define S3C64XX_VA_MODEM S3C_ADDR_CPU(0x00100000)
#define S3C64XX_PA_USBHOST (0x74300000)

View file

@ -17,6 +17,21 @@ struct s3c_cpufreq_info;
struct s3c_cpufreq_board;
struct s3c_iotimings;
/**
* struct s3c_freq - frequency information (mainly for core drivers)
* @fclk: The FCLK frequency in Hz.
* @armclk: The ARMCLK frequency in Hz.
* @hclk_tns: HCLK cycle time in 10ths of nano-seconds.
* @hclk: The HCLK frequency in Hz.
* @pclk: The PCLK frequency in Hz.
*
* This contains the frequency information about the current configuration
* mainly for the core drivers to ensure we do not end up passing about
* a large number of parameters.
*
* The @hclk_tns field is a useful cache for the parts of the drivers that
* need to calculate IO timings and suchlike.
*/
struct s3c_freq {
unsigned long fclk;
unsigned long armclk;
@ -25,48 +40,84 @@ struct s3c_freq {
unsigned long pclk;
};
/* wrapper 'struct cpufreq_freqs' so that any drivers receiving the
/**
* struct s3c_cpufreq_freqs - s3c cpufreq notification information.
* @freqs: The cpufreq setting information.
* @old: The old clock settings.
* @new: The new clock settings.
* @pll_changing: Set if the PLL is changing.
*
* Wrapper 'struct cpufreq_freqs' so that any drivers receiving the
* notification can use this information that is not provided by just
* having the core frequency alone.
*
* The pll_changing flag is used to indicate if the PLL itself is
* being set during this change. This is important as the clocks
* will temporarily be set to the XTAL clock during this time, so
* drivers may want to close down their output during this time.
*
* Note, this is not being used by any current drivers and therefore
* may be removed in the future.
*/
struct s3c_cpufreq_freqs {
struct cpufreq_freqs freqs;
struct s3c_freq old;
struct s3c_freq new;
unsigned int pll_changing:1;
};
#define to_s3c_cpufreq(_cf) container_of(_cf, struct s3c_cpufreq_freqs, freqs)
/**
* struct s3c_clkdivs - clock divisor information
* @p_divisor: Divisor from FCLK to PCLK.
* @h_divisor: Divisor from FCLK to HCLK.
* @arm_divisor: Divisor from FCLK to ARMCLK (not all CPUs).
* @dvs: Non-zero if using DVS mode for ARMCLK.
*
* Divisor settings for the core clocks.
*/
struct s3c_clkdivs {
int p_divisor; /* fclk / pclk */
int h_divisor; /* fclk / hclk */
int arm_divisor; /* not all cpus have this. */
unsigned char dvs; /* using dvs mode to arm. */
int p_divisor;
int h_divisor;
int arm_divisor;
unsigned char dvs;
};
#define PLLVAL(_m, _p, _s) (((_m) << 12) | ((_p) << 4) | (_s))
/**
* struct s3c_pllval - PLL value entry.
* @freq: The frequency for this entry in Hz.
* @pll_reg: The PLL register setting for this PLL value.
*/
struct s3c_pllval {
unsigned long freq;
unsigned long pll_reg;
};
struct s3c_cpufreq_config {
struct s3c_freq freq;
struct s3c_pllval pll;
struct s3c_clkdivs divs;
struct s3c_cpufreq_info *info; /* for core, not drivers */
struct s3c_cpufreq_board *board;
};
/* s3c_cpufreq_board
/**
* struct s3c_cpufreq_board - per-board cpu frequency informatin
* @refresh: The SDRAM refresh period in nanoseconds.
* @auto_io: Set if the IO timing settings should be generated from the
* initialisation time hardware registers.
* @need_io: Set if the board has external IO on any of the chipselect
* lines that will require the hardware timing registers to be
* updated on a clock change.
* @max: The maxium frequency limits for the system. Any field that
* is left at zero will use the CPU's settings.
*
* per-board configuraton information, such as memory refresh and
* how to initialise IO timings.
* This contains the board specific settings that affect how the CPU
* drivers chose settings. These include the memory refresh and IO
* timing information.
*
* Registration depends on the driver being used, the ARMCLK only
* implementation does not currently need this but the older style
* driver requires this to be available.
*/
struct s3c_cpufreq_board {
unsigned int refresh; /* refresh period in ns */
unsigned int refresh;
unsigned int auto_io:1; /* automatically init io timings. */
unsigned int need_io:1; /* set if needs io timing support. */

View file

@ -65,6 +65,7 @@ extern struct sys_timer s3c24xx_timer;
/* system device classes */
extern struct sysdev_class s3c2410_sysclass;
extern struct sysdev_class s3c2410a_sysclass;
extern struct sysdev_class s3c2412_sysclass;
extern struct sysdev_class s3c2440_sysclass;
extern struct sysdev_class s3c2442_sysclass;

View file

@ -32,9 +32,15 @@
#define S3C_VA_IRQ S3C_ADDR(0x00000000) /* irq controller(s) */
#define S3C_VA_SYS S3C_ADDR(0x00100000) /* system control */
#define S3C_VA_MEM S3C_ADDR(0x00200000) /* system control */
#define S3C_VA_MEM S3C_ADDR(0x00200000) /* memory control */
#define S3C_VA_TIMER S3C_ADDR(0x00300000) /* timer block */
#define S3C_VA_WATCHDOG S3C_ADDR(0x00400000) /* watchdog */
#define S3C_VA_UART S3C_ADDR(0x01000000) /* UART */
/* This is used for the CPU specific mappings that may be needed, so that
* they do not need to directly used S3C_ADDR() and thus make it easier to
* modify the space for mapping.
*/
#define S3C_ADDR_CPU(x) S3C_ADDR(0x00500000 + (x))
#endif /* __ASM_PLAT_MAP_H */

View file

@ -34,6 +34,40 @@ config CPU_S3C244X
help
Support for S3C2440 and S3C2442 Samsung Mobile CPU based systems.
config S3C2440_CPUFREQ
bool "S3C2440/S3C2442 CPU Frequency scaling support"
depends on CPU_FREQ_S3C24XX && (CPU_S3C2440 || CPU_S3C2442)
select S3C2410_CPUFREQ_UTILS
default y
help
CPU Frequency scaling support for S3C2440 and S3C2442 SoC CPUs.
config S3C2440_XTAL_12000000
bool
help
Indicate that the build needs to support 12MHz system
crystal.
config S3C2440_XTAL_16934400
bool
help
Indicate that the build needs to support 16.9344MHz system
crystal.
config S3C2440_PLL_12000000
bool
depends on S3C2440_CPUFREQ && S3C2440_XTAL_12000000
default y if CPU_FREQ_S3C24XX_PLL
help
PLL tables for S3C2440 or S3C2442 CPUs with 12MHz crystals.
config S3C2440_PLL_16934400
bool
depends on S3C2440_CPUFREQ && S3C2440_XTAL_16934400
default y if CPU_FREQ_S3C24XX_PLL
help
PLL tables for S3C2440 or S3C2442 CPUs with 16.934MHz crystals.
config S3C24XX_PWM
bool "PWM device support"
select HAVE_PWM
@ -113,6 +147,31 @@ config S3C24XX_SPI_BUS1_GPD8_GPD9_GPD10
# common code for s3c24xx based machines, such as the SMDKs.
# cpu frequency items common between s3c2410 and s3c2440/s3c2442
config S3C2410_IOTIMING
bool
depends on CPU_FREQ_S3C24XX
help
Internal node to select io timing code that is common to the s3c2410
and s3c2440/s3c2442 cpu frequency support.
config S3C2410_CPUFREQ_UTILS
bool
depends on CPU_FREQ_S3C24XX
help
Internal node to select timing code that is common to the s3c2410
and s3c2440/s3c244 cpu frequency support.
# cpu frequency support common to s3c2412, s3c2413 and s3c2442
config S3C2412_IOTIMING
bool
depends on CPU_FREQ_S3C24XX && (CPU_S3C2412 || CPU_S3C2443)
help
Intel node to select io timing code that is common to the s3c2412
and the s3c2443.
config MACH_SMDK
bool
help

View file

@ -20,11 +20,18 @@ obj-y += gpiolib.o
obj-y += clock.o
obj-$(CONFIG_S3C24XX_DCLK) += clock-dclk.o
obj-$(CONFIG_CPU_FREQ_S3C24XX) += cpu-freq.o
obj-$(CONFIG_CPU_FREQ_S3C24XX_DEBUGFS) += cpu-freq-debugfs.o
# Architecture dependant builds
obj-$(CONFIG_CPU_S3C244X) += s3c244x.o
obj-$(CONFIG_CPU_S3C244X) += s3c244x-irq.o
obj-$(CONFIG_CPU_S3C244X) += s3c244x-clock.o
obj-$(CONFIG_S3C2440_CPUFREQ) += s3c2440-cpufreq.o
obj-$(CONFIG_S3C2440_PLL_12000000) += s3c2440-pll-12000000.o
obj-$(CONFIG_S3C2440_PLL_16934400) += s3c2440-pll-16934400.o
obj-$(CONFIG_PM_SIMTEC) += pm-simtec.o
obj-$(CONFIG_PM) += pm.o
obj-$(CONFIG_PM) += irq-pm.o
@ -33,6 +40,9 @@ obj-$(CONFIG_S3C24XX_PWM) += pwm.o
obj-$(CONFIG_S3C2410_CLOCK) += s3c2410-clock.o
obj-$(CONFIG_S3C2410_DMA) += dma.o
obj-$(CONFIG_S3C24XX_ADC) += adc.o
obj-$(CONFIG_S3C2410_IOTIMING) += s3c2410-iotiming.o
obj-$(CONFIG_S3C2412_IOTIMING) += s3c2412-iotiming.o
obj-$(CONFIG_S3C2410_CPUFREQ_UTILS) += s3c2410-cpufreq-utils.o
# device specific setup and/or initialisation
obj-$(CONFIG_ARCH_S3C2410) += setup-i2c.o

View file

@ -0,0 +1,199 @@
/* linux/arch/arm/plat-s3c24xx/cpu-freq-debugfs.c
*
* Copyright (c) 2009 Simtec Electronics
* http://armlinux.simtec.co.uk/
* Ben Dooks <ben@simtec.co.uk>
*
* S3C24XX CPU Frequency scaling - debugfs status support
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/init.h>
#include <linux/module.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 <plat/cpu-freq-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;
}
static int fops_board_open(struct inode *inode, struct file *file)
{
return single_open(file, board_show, NULL);
}
static const struct file_operations fops_board = {
.open = fops_board_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
.owner = THIS_MODULE,
};
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;
}
static int fops_info_open(struct inode *inode, struct file *file)
{
return single_open(file, info_show, NULL);
}
static const struct file_operations fops_info = {
.open = fops_info_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
.owner = THIS_MODULE,
};
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;
}
static int fops_io_open(struct inode *inode, struct file *file)
{
return single_open(file, io_show, NULL);
}
static const struct file_operations fops_io = {
.open = fops_io_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
.owner = THIS_MODULE,
};
static int __init s3c_freq_debugfs_init(void)
{
dbgfs_root = debugfs_create_dir("s3c-cpufreq", NULL);
if (IS_ERR(dbgfs_root)) {
printk(KERN_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, &fops_io);
dbgfs_file_info = debugfs_create_file("info", S_IRUGO, dbgfs_root,
NULL, &fops_info);
dbgfs_file_board = debugfs_create_file("board", S_IRUGO, dbgfs_root,
NULL, &fops_board);
return 0;
}
late_initcall(s3c_freq_debugfs_init);

View file

@ -0,0 +1,716 @@
/* linux/arch/arm/plat-s3c24xx/cpu-freq.c
*
* Copyright (c) 2006,2007,2008 Simtec Electronics
* http://armlinux.simtec.co.uk/
* Ben Dooks <ben@simtec.co.uk>
*
* S3C24XX CPU Frequency scaling
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#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/sysdev.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <asm/mach/arch.h>
#include <asm/mach/map.h>
#include <plat/cpu.h>
#include <plat/clock.h>
#include <plat/cpu-freq-core.h>
#include <mach/regs-clock.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_CPU_FREQ_S3C24XX_DEBUGFS
struct s3c_cpufreq_config *s3c_cpufreq_getconfig(void)
{
return &cpu_cur;
}
struct s3c_iotimings *s3c_cpufreq_getiotimings(void)
{
return &s3c24xx_iotiming;
}
#endif /* CONFIG_CPU_FREQ_S3C24XX_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.index = __raw_readl(S3C2410_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->info->set_fvco)(cfg);
}
static inline void s3c_cpufreq_resume_clocks(void)
{
cpu_cur.info->resume_clocks();
}
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) {
printk(KERN_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) {
printk(KERN_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.cpu = 0;
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 */
if (policy)
cpufreq_notify_transition(&freqs.freqs, CPUFREQ_PRECHANGE);
/* 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 */
if (policy)
cpufreq_notify_transition(&freqs.freqs, CPUFREQ_POSTCHANGE);
s3c_freq_dbg("%s: finished\n", __func__);
return 0;
err_notpossible:
printk(KERN_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) {
if (cpufreq_frequency_table_target(policy, ftab,
target_freq, relation,
&index)) {
s3c_freq_dbg("%s: table failed\n", __func__);
return -EINVAL;
}
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;
int ret;
/* 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;
/* cpufreq_frequency_table_target uses a pointer to 'index'
* which is the number of the table entry, not the value of
* the table entry's index field. */
ret = cpufreq_frequency_table_target(&tmp_policy, pll_reg,
target_freq, relation,
&index);
if (ret < 0) {
printk(KERN_ERR "%s: no PLL available\n", __func__);
goto err_notpossible;
}
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);
err_notpossible:
printk(KERN_ERR "no compatible settings for %d\n", target_freq);
return -EINVAL;
}
static unsigned int s3c_cpufreq_get(unsigned int cpu)
{
return clk_get_rate(clk_arm) / 1000;
}
struct clk *s3c_cpufreq_clk_get(struct device *dev, const char *name)
{
struct clk *clk;
clk = clk_get(dev, name);
if (IS_ERR(clk))
printk(KERN_ERR "cpufreq: failed to get clock '%s'\n", name);
return clk;
}
static int s3c_cpufreq_init(struct cpufreq_policy *policy)
{
printk(KERN_INFO "%s: initialising policy %p\n", __func__, policy);
if (policy->cpu != 0)
return -EINVAL;
policy->cur = s3c_cpufreq_get(0);
policy->min = policy->cpuinfo.min_freq = 0;
policy->max = policy->cpuinfo.max_freq = cpu_cur.info->max.fclk / 1000;
policy->governor = CPUFREQ_DEFAULT_GOVERNOR;
/* feed the latency information from the cpu driver */
policy->cpuinfo.transition_latency = cpu_cur.info->latency;
if (ftab)
cpufreq_frequency_table_cpuinfo(policy, ftab);
return 0;
}
static __init int 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)) {
printk(KERN_ERR "%s: could not get clock(s)\n", __func__);
return -ENOENT;
}
printk(KERN_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;
}
static int s3c_cpufreq_verify(struct cpufreq_policy *policy)
{
if (policy->cpu != 0)
return -EINVAL;
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, pm_message_t pmsg)
{
suspend_pll.frequency = clk_get_rate(_clk_mpll);
suspend_pll.index = __raw_readl(S3C2410_MPLLCON);
suspend_freq = s3c_cpufreq_get(0) * 1000;
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 */
/* first, find out what speed we resumed at. */
s3c_cpufreq_resume_clocks();
/* 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 immediatley 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) {
printk(KERN_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_STICKY,
.verify = s3c_cpufreq_verify,
.target = s3c_cpufreq_target,
.get = s3c_cpufreq_get,
.init = s3c_cpufreq_init,
.suspend = s3c_cpufreq_suspend,
.resume = s3c_cpufreq_resume,
.name = "s3c24xx",
};
int __init s3c_cpufreq_register(struct s3c_cpufreq_info *info)
{
if (!info || !info->name) {
printk(KERN_ERR "%s: failed to pass valid information\n",
__func__);
return -EINVAL;
}
printk(KERN_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) {
printk(KERN_INFO "%s: no board data\n", __func__);
return -EINVAL;
}
/* Copy the board information so that each board can make this
* initdata. */
ours = kzalloc(sizeof(struct s3c_cpufreq_board), GFP_KERNEL);
if (ours == NULL) {
printk(KERN_ERR "%s: no memory\n", __func__);
return -ENOMEM;
}
*ours = *board;
cpu_cur.board = ours;
return 0;
}
int __init s3c_cpufreq_auto_io(void)
{
int ret;
if (!cpu_cur.info->get_iotiming) {
printk(KERN_ERR "%s: get_iotiming undefined\n", __func__);
return -ENOENT;
}
printk(KERN_INFO "%s: working out IO settings\n", __func__);
ret = (cpu_cur.info->get_iotiming)(&cpu_cur, &s3c24xx_iotiming);
if (ret)
printk(KERN_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);
printk(KERN_INFO "%s: new locktime is 0x%08x\n", __func__, val);
__raw_writel(val, S3C2410_LOCKTIME);
}
static int s3c_cpufreq_build_freq(void)
{
int size, ret;
if (!cpu_cur.info->calc_freqtable)
return -EINVAL;
kfree(ftab);
ftab = NULL;
size = cpu_cur.info->calc_freqtable(&cpu_cur, NULL, 0);
size++;
ftab = kmalloc(sizeof(struct cpufreq_frequency_table) * size, GFP_KERNEL);
if (!ftab) {
printk(KERN_ERR "%s: no memory for tables\n", __func__);
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) {
printk(KERN_ERR "%s: failed to get io timing\n",
__func__);
goto out;
}
}
if (cpu_cur.board->need_io && !cpu_cur.info->set_iotiming) {
printk(KERN_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 __init s3c_plltab_register(struct cpufreq_frequency_table *plls,
unsigned int plls_no)
{
struct cpufreq_frequency_table *vals;
unsigned int size;
size = sizeof(struct cpufreq_frequency_table) * (plls_no + 1);
vals = kmalloc(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;
printk(KERN_INFO "cpufreq: %d PLL entries\n", plls_no);
} else
printk(KERN_ERR "cpufreq: no memory for PLL tables\n");
return vals ? 0 : -ENOMEM;
}

View file

@ -81,7 +81,7 @@ static struct cpu_table cpu_ids[] __initdata = {
.map_io = s3c2410_map_io,
.init_clocks = s3c2410_init_clocks,
.init_uarts = s3c2410_init_uarts,
.init = s3c2410_init,
.init = s3c2410a_init,
.name = name_s3c2410a
},
{

View file

@ -0,0 +1,282 @@
/* arch/arm/plat-s3c/include/plat/cpu-freq.h
*
* Copyright (c) 2006,2007,2009 Simtec Electronics
* http://armlinux.simtec.co.uk/
* Ben Dooks <ben@simtec.co.uk>
*
* S3C CPU frequency scaling support - core support
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <plat/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 afrer 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 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 acheive
* 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.
* @resume_clocks: Update the clocks on resume.
* @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 */
void (*resume_clocks)(void);
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);
extern void s3c2410_iotiming_debugfs(struct seq_file *seq,
struct s3c_cpufreq_config *cfg,
union s3c_iobank *iob);
extern void s3c2412_iotiming_debugfs(struct seq_file *seq,
struct s3c_cpufreq_config *cfg,
union s3c_iobank *iob);
#ifdef CONFIG_CPU_FREQ_S3C24XX_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 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);
extern void s3c2410_set_fvco(struct s3c_cpufreq_config *cfg);
/* S3C2412 compatible routines */
extern int s3c2412_iotiming_get(struct s3c_cpufreq_config *cfg,
struct s3c_iotimings *timings);
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);
#ifdef CONFIG_CPU_FREQ_S3C24XX_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_CPU_FREQ_S3C24XX_DEBUG */
#ifdef CONFIG_CPU_FREQ_S3C24XX_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_CPU_FREQ_S3C24XX_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].index = index;
table[index].frequency = freq;
}
return index + 1;
}

View file

@ -14,6 +14,7 @@
#ifdef CONFIG_CPU_S3C2410
extern int s3c2410_init(void);
extern int s3c2410a_init(void);
extern void s3c2410_map_io(void);

View file

@ -0,0 +1,64 @@
/* linux/arch/arm/plat-s3c24xx/s3c2410-cpufreq-utils.c
*
* Copyright (c) 2009 Simtec Electronics
* http://armlinux.simtec.co.uk/
* Ben Dooks <ben@simtec.co.uk>
*
* S3C24XX CPU Frequency scaling - utils for S3C2410/S3C2440/S3C2442
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/cpufreq.h>
#include <linux/io.h>
#include <mach/map.h>
#include <mach/regs-mem.h>
#include <mach/regs-clock.h>
#include <plat/cpu-freq-core.h>
/**
* s3c2410_cpufreq_setrefresh - set SDRAM refresh value
* @cfg: The frequency configuration
*
* Set the SDRAM refresh value appropriately for the configured
* frequency.
*/
void s3c2410_cpufreq_setrefresh(struct s3c_cpufreq_config *cfg)
{
struct s3c_cpufreq_board *board = cfg->board;
unsigned long refresh;
unsigned long refval;
/* Reduce both the refresh time (in ns) and the frequency (in MHz)
* down to ensure that we do not overflow 32 bit numbers.
*
* This should work for HCLK up to 133MHz and refresh period up
* to 30usec.
*/
refresh = (cfg->freq.hclk / 100) * (board->refresh / 10);
refresh = DIV_ROUND_UP(refresh, (1000 * 1000)); /* apply scale */
refresh = (1 << 11) + 1 - refresh;
s3c_freq_dbg("%s: refresh value %lu\n", __func__, refresh);
refval = __raw_readl(S3C2410_REFRESH);
refval &= ~((1 << 12) - 1);
refval |= refresh;
__raw_writel(refval, S3C2410_REFRESH);
}
/**
* s3c2410_set_fvco - set the PLL value
* @cfg: The frequency configuration
*/
void s3c2410_set_fvco(struct s3c_cpufreq_config *cfg)
{
__raw_writel(cfg->pll.index, S3C2410_MPLLCON);
}

View file

@ -0,0 +1,477 @@
/* linux/arch/arm/plat-s3c24xx/s3c2410-iotiming.c
*
* Copyright (c) 2006,2008,2009 Simtec Electronics
* http://armlinux.simtec.co.uk/
* Ben Dooks <ben@simtec.co.uk>
*
* S3C24XX CPU Frequency scaling - IO timing for S3C2410/S3C2440/S3C2442
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/cpufreq.h>
#include <linux/seq_file.h>
#include <linux/io.h>
#include <mach/map.h>
#include <mach/regs-mem.h>
#include <mach/regs-clock.h>
#include <plat/cpu-freq-core.h>
#define print_ns(x) ((x) / 10), ((x) % 10)
/**
* s3c2410_print_timing - print bank timing data for debug purposes
* @pfx: The prefix to put on the output
* @timings: The timing inforamtion to print.
*/
static void s3c2410_print_timing(const char *pfx,
struct s3c_iotimings *timings)
{
struct s3c2410_iobank_timing *bt;
int bank;
for (bank = 0; bank < MAX_BANKS; bank++) {
bt = timings->bank[bank].io_2410;
if (!bt)
continue;
printk(KERN_DEBUG "%s %d: Tacs=%d.%d, Tcos=%d.%d, Tacc=%d.%d, "
"Tcoh=%d.%d, Tcah=%d.%d\n", pfx, bank,
print_ns(bt->tacs),
print_ns(bt->tcos),
print_ns(bt->tacc),
print_ns(bt->tcoh),
print_ns(bt->tcah));
}
}
/**
* bank_reg - convert bank number to pointer to the control register.
* @bank: The IO bank number.
*/
static inline void __iomem *bank_reg(unsigned int bank)
{
return S3C2410_BANKCON0 + (bank << 2);
}
/**
* bank_is_io - test whether bank is used for IO
* @bankcon: The bank control register.
*
* This is a simplistic test to see if any BANKCON[x] is not an IO
* bank. It currently does not take into account whether BWSCON has
* an illegal width-setting in it, or if the pin connected to nCS[x]
* is actually being handled as a chip-select.
*/
static inline int bank_is_io(unsigned long bankcon)
{
return !(bankcon & S3C2410_BANKCON_SDRAM);
}
/**
* to_div - convert cycle time to divisor
* @cyc: The cycle time, in 10ths of nanoseconds.
* @hclk_tns: The cycle time for HCLK, in 10ths of nanoseconds.
*
* Convert the given cycle time into the divisor to use to obtain it from
* HCLK.
*/
static inline unsigned int to_div(unsigned int cyc, unsigned int hclk_tns)
{
if (cyc == 0)
return 0;
return DIV_ROUND_UP(cyc, hclk_tns);
}
/**
* calc_0124 - calculate divisor control for divisors that do /0, /1. /2 and /4
* @cyc: The cycle time, in 10ths of nanoseconds.
* @hclk_tns: The cycle time for HCLK, in 10ths of nanoseconds.
* @v: Pointer to register to alter.
* @shift: The shift to get to the control bits.
*
* Calculate the divisor, and turn it into the correct control bits to
* set in the result, @v.
*/
static unsigned int calc_0124(unsigned int cyc, unsigned long hclk_tns,
unsigned long *v, int shift)
{
unsigned int div = to_div(cyc, hclk_tns);
unsigned long val;
s3c_freq_iodbg("%s: cyc=%d, hclk=%lu, shift=%d => div %d\n",
__func__, cyc, hclk_tns, shift, div);
switch (div) {
case 0:
val = 0;
break;
case 1:
val = 1;
break;
case 2:
val = 2;
break;
case 3:
case 4:
val = 3;
break;
default:
return -1;
}
*v |= val << shift;
return 0;
}
int calc_tacp(unsigned int cyc, unsigned long hclk, unsigned long *v)
{
/* Currently no support for Tacp calculations. */
return 0;
}
/**
* calc_tacc - calculate divisor control for tacc.
* @cyc: The cycle time, in 10ths of nanoseconds.
* @nwait_en: IS nWAIT enabled for this bank.
* @hclk_tns: The cycle time for HCLK, in 10ths of nanoseconds.
* @v: Pointer to register to alter.
*
* Calculate the divisor control for tACC, taking into account whether
* the bank has nWAIT enabled. The result is used to modify the value
* pointed to by @v.
*/
static int calc_tacc(unsigned int cyc, int nwait_en,
unsigned long hclk_tns, unsigned long *v)
{
unsigned int div = to_div(cyc, hclk_tns);
unsigned long val;
s3c_freq_iodbg("%s: cyc=%u, nwait=%d, hclk=%lu => div=%u\n",
__func__, cyc, nwait_en, hclk_tns, div);
/* if nWait enabled on an bank, Tacc must be at-least 4 cycles. */
if (nwait_en && div < 4)
div = 4;
switch (div) {
case 0:
val = 0;
break;
case 1:
case 2:
case 3:
case 4:
val = div - 1;
break;
case 5:
case 6:
val = 4;
break;
case 7:
case 8:
val = 5;
break;
case 9:
case 10:
val = 6;
break;
case 11:
case 12:
case 13:
case 14:
val = 7;
break;
default:
return -1;
}
*v |= val << 8;
return 0;
}
/**
* s3c2410_calc_bank - calculate bank timing infromation
* @cfg: The configuration we need to calculate for.
* @bt: The bank timing information.
*
* Given the cycle timine for a bank @bt, calculate the new BANKCON
* setting for the @cfg timing. This updates the timing information
* ready for the cpu frequency change.
*/
static int s3c2410_calc_bank(struct s3c_cpufreq_config *cfg,
struct s3c2410_iobank_timing *bt)
{
unsigned long hclk = cfg->freq.hclk_tns;
unsigned long res;
int ret;
res = bt->bankcon;
res &= (S3C2410_BANKCON_SDRAM | S3C2410_BANKCON_PMC16);
/* tacp: 2,3,4,5 */
/* tcah: 0,1,2,4 */
/* tcoh: 0,1,2,4 */
/* tacc: 1,2,3,4,6,7,10,14 (>4 for nwait) */
/* tcos: 0,1,2,4 */
/* tacs: 0,1,2,4 */
ret = calc_0124(bt->tacs, hclk, &res, S3C2410_BANKCON_Tacs_SHIFT);
ret |= calc_0124(bt->tcos, hclk, &res, S3C2410_BANKCON_Tcos_SHIFT);
ret |= calc_0124(bt->tcah, hclk, &res, S3C2410_BANKCON_Tcah_SHIFT);
ret |= calc_0124(bt->tcoh, hclk, &res, S3C2410_BANKCON_Tcoh_SHIFT);
if (ret)
return -EINVAL;
ret |= calc_tacp(bt->tacp, hclk, &res);
ret |= calc_tacc(bt->tacc, bt->nwait_en, hclk, &res);
if (ret)
return -EINVAL;
bt->bankcon = res;
return 0;
}
static unsigned int tacc_tab[] = {
[0] = 1,
[1] = 2,
[2] = 3,
[3] = 4,
[4] = 6,
[5] = 9,
[6] = 10,
[7] = 14,
};
/**
* get_tacc - turn tACC value into cycle time
* @hclk_tns: The cycle time for HCLK, in 10ths of nanoseconds.
* @val: The bank timing register value, shifed down.
*/
static unsigned int get_tacc(unsigned long hclk_tns,
unsigned long val)
{
val &= 7;
return hclk_tns * tacc_tab[val];
}
/**
* get_0124 - turn 0/1/2/4 divider into cycle time
* @hclk_tns: The cycle time for HCLK, in 10ths of nanoseconds.
* @val: The bank timing register value, shifed down.
*/
static unsigned int get_0124(unsigned long hclk_tns,
unsigned long val)
{
val &= 3;
return hclk_tns * ((val == 3) ? 4 : val);
}
/**
* s3c2410_iotiming_getbank - turn BANKCON into cycle time information
* @cfg: The frequency configuration
* @bt: The bank timing to fill in (uses cached BANKCON)
*
* Given the BANKCON setting in @bt and the current frequency settings
* in @cfg, update the cycle timing information.
*/
void s3c2410_iotiming_getbank(struct s3c_cpufreq_config *cfg,
struct s3c2410_iobank_timing *bt)
{
unsigned long bankcon = bt->bankcon;
unsigned long hclk = cfg->freq.hclk_tns;
bt->tcah = get_0124(hclk, bankcon >> S3C2410_BANKCON_Tcah_SHIFT);
bt->tcoh = get_0124(hclk, bankcon >> S3C2410_BANKCON_Tcoh_SHIFT);
bt->tcos = get_0124(hclk, bankcon >> S3C2410_BANKCON_Tcos_SHIFT);
bt->tacs = get_0124(hclk, bankcon >> S3C2410_BANKCON_Tacs_SHIFT);
bt->tacc = get_tacc(hclk, bankcon >> S3C2410_BANKCON_Tacc_SHIFT);
}
/**
* s3c2410_iotiming_debugfs - debugfs show io bank timing information
* @seq: The seq_file to write output to using seq_printf().
* @cfg: The current configuration.
* @iob: The IO bank information to decode.
*/
void s3c2410_iotiming_debugfs(struct seq_file *seq,
struct s3c_cpufreq_config *cfg,
union s3c_iobank *iob)
{
struct s3c2410_iobank_timing *bt = iob->io_2410;
unsigned long bankcon = bt->bankcon;
unsigned long hclk = cfg->freq.hclk_tns;
unsigned int tacs;
unsigned int tcos;
unsigned int tacc;
unsigned int tcoh;
unsigned int tcah;
seq_printf(seq, "BANKCON=0x%08lx\n", bankcon);
tcah = get_0124(hclk, bankcon >> S3C2410_BANKCON_Tcah_SHIFT);
tcoh = get_0124(hclk, bankcon >> S3C2410_BANKCON_Tcoh_SHIFT);
tcos = get_0124(hclk, bankcon >> S3C2410_BANKCON_Tcos_SHIFT);
tacs = get_0124(hclk, bankcon >> S3C2410_BANKCON_Tacs_SHIFT);
tacc = get_tacc(hclk, bankcon >> S3C2410_BANKCON_Tacc_SHIFT);
seq_printf(seq,
"\tRead: Tacs=%d.%d, Tcos=%d.%d, Tacc=%d.%d, Tcoh=%d.%d, Tcah=%d.%d\n",
print_ns(bt->tacs),
print_ns(bt->tcos),
print_ns(bt->tacc),
print_ns(bt->tcoh),
print_ns(bt->tcah));
seq_printf(seq,
"\t Set: Tacs=%d.%d, Tcos=%d.%d, Tacc=%d.%d, Tcoh=%d.%d, Tcah=%d.%d\n",
print_ns(tacs),
print_ns(tcos),
print_ns(tacc),
print_ns(tcoh),
print_ns(tcah));
}
/**
* s3c2410_iotiming_calc - Calculate bank timing for frequency change.
* @cfg: The frequency configuration
* @iot: The IO timing information to fill out.
*
* Calculate the new values for the banks in @iot based on the new
* frequency information in @cfg. This is then used by s3c2410_iotiming_set()
* to update the timing when necessary.
*/
int s3c2410_iotiming_calc(struct s3c_cpufreq_config *cfg,
struct s3c_iotimings *iot)
{
struct s3c2410_iobank_timing *bt;
unsigned long bankcon;
int bank;
int ret;
for (bank = 0; bank < MAX_BANKS; bank++) {
bankcon = __raw_readl(bank_reg(bank));
bt = iot->bank[bank].io_2410;
if (!bt)
continue;
bt->bankcon = bankcon;
ret = s3c2410_calc_bank(cfg, bt);
if (ret) {
printk(KERN_ERR "%s: cannot calculate bank %d io\n",
__func__, bank);
goto err;
}
s3c_freq_iodbg("%s: bank %d: con=%08lx\n",
__func__, bank, bt->bankcon);
}
return 0;
err:
return ret;
}
/**
* s3c2410_iotiming_set - set the IO timings from the given setup.
* @cfg: The frequency configuration
* @iot: The IO timing information to use.
*
* Set all the currently used IO bank timing information generated
* by s3c2410_iotiming_calc() once the core has validated that all
* the new values are within permitted bounds.
*/
void s3c2410_iotiming_set(struct s3c_cpufreq_config *cfg,
struct s3c_iotimings *iot)
{
struct s3c2410_iobank_timing *bt;
int bank;
/* set the io timings from the specifier */
for (bank = 0; bank < MAX_BANKS; bank++) {
bt = iot->bank[bank].io_2410;
if (!bt)
continue;
__raw_writel(bt->bankcon, bank_reg(bank));
}
}
/**
* s3c2410_iotiming_get - Get the timing information from current registers.
* @cfg: The frequency configuration
* @timings: The IO timing information to fill out.
*
* Calculate the @timings timing information from the current frequency
* information in @cfg, and the new frequency configur
* through all the IO banks, reading the state and then updating @iot
* as necessary.
*
* This is used at the moment on initialisation to get the current
* configuration so that boards do not have to carry their own setup
* if the timings are correct on initialisation.
*/
int s3c2410_iotiming_get(struct s3c_cpufreq_config *cfg,
struct s3c_iotimings *timings)
{
struct s3c2410_iobank_timing *bt;
unsigned long bankcon;
unsigned long bwscon;
int bank;
bwscon = __raw_readl(S3C2410_BWSCON);
/* look through all banks to see what is currently set. */
for (bank = 0; bank < MAX_BANKS; bank++) {
bankcon = __raw_readl(bank_reg(bank));
if (!bank_is_io(bankcon))
continue;
s3c_freq_iodbg("%s: bank %d: con %08lx\n",
__func__, bank, bankcon);
bt = kzalloc(sizeof(struct s3c2410_iobank_timing), GFP_KERNEL);
if (!bt) {
printk(KERN_ERR "%s: no memory for bank\n", __func__);
return -ENOMEM;
}
/* find out in nWait is enabled for bank. */
if (bank != 0) {
unsigned long tmp = S3C2410_BWSCON_GET(bwscon, bank);
if (tmp & S3C2410_BWSCON_WS)
bt->nwait_en = 1;
}
timings->bank[bank].io_2410 = bt;
bt->bankcon = bankcon;
s3c2410_iotiming_getbank(cfg, bt);
}
s3c2410_print_timing("get", timings);
return 0;
}

View file

@ -0,0 +1,285 @@
/* linux/arch/arm/plat-s3c24xx/s3c2412-iotiming.c
*
* Copyright (c) 2006,2008 Simtec Electronics
* http://armlinux.simtec.co.uk/
* Ben Dooks <ben@simtec.co.uk>
*
* S3C2412/S3C2443 (PL093 based) IO timing support
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/ioport.h>
#include <linux/cpufreq.h>
#include <linux/seq_file.h>
#include <linux/sysdev.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/amba/pl093.h>
#include <asm/mach/arch.h>
#include <asm/mach/map.h>
#include <mach/regs-s3c2412-mem.h>
#include <plat/cpu.h>
#include <plat/cpu-freq-core.h>
#include <plat/clock.h>
#define print_ns(x) ((x) / 10), ((x) % 10)
/**
* s3c2412_print_timing - print timing infromation via printk.
* @pfx: The prefix to print each line with.
* @iot: The IO timing information
*/
static void s3c2412_print_timing(const char *pfx, struct s3c_iotimings *iot)
{
struct s3c2412_iobank_timing *bt;
unsigned int bank;
for (bank = 0; bank < MAX_BANKS; bank++) {
bt = iot->bank[bank].io_2412;
if (!bt)
continue;
printk(KERN_DEBUG "%s: %d: idcy=%d.%d wstrd=%d.%d wstwr=%d,%d"
"wstoen=%d.%d wstwen=%d.%d wstbrd=%d.%d\n", pfx, bank,
print_ns(bt->idcy),
print_ns(bt->wstrd),
print_ns(bt->wstwr),
print_ns(bt->wstoen),
print_ns(bt->wstwen),
print_ns(bt->wstbrd));
}
}
/**
* to_div - turn a cycle length into a divisor setting.
* @cyc_tns: The cycle time in 10ths of nanoseconds.
* @clk_tns: The clock period in 10ths of nanoseconds.
*/
static inline unsigned int to_div(unsigned int cyc_tns, unsigned int clk_tns)
{
return cyc_tns ? DIV_ROUND_UP(cyc_tns, clk_tns) : 0;
}
/**
* calc_timing - calculate timing divisor value and check in range.
* @hwtm: The hardware timing in 10ths of nanoseconds.
* @clk_tns: The clock period in 10ths of nanoseconds.
* @err: Pointer to err variable to update in event of failure.
*/
static unsigned int calc_timing(unsigned int hwtm, unsigned int clk_tns,
unsigned int *err)
{
unsigned int ret = to_div(hwtm, clk_tns);
if (ret > 0xf)
*err = -EINVAL;
return ret;
}
/**
* s3c2412_calc_bank - calculate the bank divisor settings.
* @cfg: The current frequency configuration.
* @bt: The bank timing.
*/
static int s3c2412_calc_bank(struct s3c_cpufreq_config *cfg,
struct s3c2412_iobank_timing *bt)
{
unsigned int hclk = cfg->freq.hclk_tns;
int err = 0;
bt->smbidcyr = calc_timing(bt->idcy, hclk, &err);
bt->smbwstrd = calc_timing(bt->wstrd, hclk, &err);
bt->smbwstwr = calc_timing(bt->wstwr, hclk, &err);
bt->smbwstoen = calc_timing(bt->wstoen, hclk, &err);
bt->smbwstwen = calc_timing(bt->wstwen, hclk, &err);
bt->smbwstbrd = calc_timing(bt->wstbrd, hclk, &err);
return err;
}
/**
* s3c2412_iotiming_debugfs - debugfs show io bank timing information
* @seq: The seq_file to write output to using seq_printf().
* @cfg: The current configuration.
* @iob: The IO bank information to decode.
*/
void s3c2412_iotiming_debugfs(struct seq_file *seq,
struct s3c_cpufreq_config *cfg,
union s3c_iobank *iob)
{
struct s3c2412_iobank_timing *bt = iob->io_2412;
seq_printf(seq,
"\tRead: idcy=%d.%d wstrd=%d.%d wstwr=%d,%d"
"wstoen=%d.%d wstwen=%d.%d wstbrd=%d.%d\n",
print_ns(bt->idcy),
print_ns(bt->wstrd),
print_ns(bt->wstwr),
print_ns(bt->wstoen),
print_ns(bt->wstwen),
print_ns(bt->wstbrd));
}
/**
* s3c2412_iotiming_calc - calculate all the bank divisor settings.
* @cfg: The current frequency configuration.
* @iot: The bank timing information.
*
* Calculate the timing information for all the banks that are
* configured as IO, using s3c2412_calc_bank().
*/
int s3c2412_iotiming_calc(struct s3c_cpufreq_config *cfg,
struct s3c_iotimings *iot)
{
struct s3c2412_iobank_timing *bt;
int bank;
int ret;
for (bank = 0; bank < MAX_BANKS; bank++) {
bt = iot->bank[bank].io_2412;
if (!bt)
continue;
ret = s3c2412_calc_bank(cfg, bt);
if (ret) {
printk(KERN_ERR "%s: cannot calculate bank %d io\n",
__func__, bank);
goto err;
}
}
return 0;
err:
return ret;
}
/**
* s3c2412_iotiming_set - set the timing information
* @cfg: The current frequency configuration.
* @iot: The bank timing information.
*
* Set the IO bank information from the details calculated earlier from
* calling s3c2412_iotiming_calc().
*/
void s3c2412_iotiming_set(struct s3c_cpufreq_config *cfg,
struct s3c_iotimings *iot)
{
struct s3c2412_iobank_timing *bt;
void __iomem *regs;
int bank;
/* set the io timings from the specifier */
for (bank = 0; bank < MAX_BANKS; bank++) {
bt = iot->bank[bank].io_2412;
if (!bt)
continue;
regs = S3C2412_SSMC_BANK(bank);
__raw_writel(bt->smbidcyr, regs + SMBIDCYR);
__raw_writel(bt->smbwstrd, regs + SMBWSTRDR);
__raw_writel(bt->smbwstwr, regs + SMBWSTWRR);
__raw_writel(bt->smbwstoen, regs + SMBWSTOENR);
__raw_writel(bt->smbwstwen, regs + SMBWSTWENR);
__raw_writel(bt->smbwstbrd, regs + SMBWSTBRDR);
}
}
static inline unsigned int s3c2412_decode_timing(unsigned int clock, u32 reg)
{
return (reg & 0xf) * clock;
}
static void s3c2412_iotiming_getbank(struct s3c_cpufreq_config *cfg,
struct s3c2412_iobank_timing *bt,
unsigned int bank)
{
unsigned long clk = cfg->freq.hclk_tns; /* ssmc clock??? */
void __iomem *regs = S3C2412_SSMC_BANK(bank);
bt->idcy = s3c2412_decode_timing(clk, __raw_readl(regs + SMBIDCYR));
bt->wstrd = s3c2412_decode_timing(clk, __raw_readl(regs + SMBWSTRDR));
bt->wstoen = s3c2412_decode_timing(clk, __raw_readl(regs + SMBWSTOENR));
bt->wstwen = s3c2412_decode_timing(clk, __raw_readl(regs + SMBWSTWENR));
bt->wstbrd = s3c2412_decode_timing(clk, __raw_readl(regs + SMBWSTBRDR));
}
/**
* bank_is_io - return true if bank is (possibly) IO.
* @bank: The bank number.
* @bankcfg: The value of S3C2412_EBI_BANKCFG.
*/
static inline bool bank_is_io(unsigned int bank, u32 bankcfg)
{
if (bank < 2)
return true;
return !(bankcfg & (1 << bank));
}
int s3c2412_iotiming_get(struct s3c_cpufreq_config *cfg,
struct s3c_iotimings *timings)
{
struct s3c2412_iobank_timing *bt;
u32 bankcfg = __raw_readl(S3C2412_EBI_BANKCFG);
unsigned int bank;
/* look through all banks to see what is currently set. */
for (bank = 0; bank < MAX_BANKS; bank++) {
if (!bank_is_io(bank, bankcfg))
continue;
bt = kzalloc(sizeof(struct s3c2412_iobank_timing), GFP_KERNEL);
if (!bt) {
printk(KERN_ERR "%s: no memory for bank\n", __func__);
return -ENOMEM;
}
timings->bank[bank].io_2412 = bt;
s3c2412_iotiming_getbank(cfg, bt, bank);
}
s3c2412_print_timing("get", timings);
return 0;
}
/* this is in here as it is so small, it doesn't currently warrant a file
* to itself. We expect that any s3c24xx needing this is going to also
* need the iotiming support.
*/
void s3c2412_cpufreq_setrefresh(struct s3c_cpufreq_config *cfg)
{
struct s3c_cpufreq_board *board = cfg->board;
u32 refresh;
WARN_ON(board == NULL);
/* Reduce both the refresh time (in ns) and the frequency (in MHz)
* down to ensure that we do not overflow 32 bit numbers.
*
* This should work for HCLK up to 133MHz and refresh period up
* to 30usec.
*/
refresh = (cfg->freq.hclk / 100) * (board->refresh / 10);
refresh = DIV_ROUND_UP(refresh, (1000 * 1000)); /* apply scale */
refresh &= ((1 << 16) - 1);
s3c_freq_dbg("%s: refresh value %u\n", __func__, (unsigned int)refresh);
__raw_writel(refresh, S3C2412_REFRESH);
}

View file

@ -0,0 +1,311 @@
/* linux/arch/arm/plat-s3c24xx/s3c2440-cpufreq.c
*
* Copyright (c) 2006,2008,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
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/ioport.h>
#include <linux/cpufreq.h>
#include <linux/sysdev.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/io.h>
#include <mach/hardware.h>
#include <asm/mach/arch.h>
#include <asm/mach/map.h>
#include <mach/regs-clock.h>
#include <plat/cpu.h>
#include <plat/cpu-freq-core.h>
#include <plat/clock.h>
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.
*/
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) {
printk(KERN_WARNING "%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: divsiors: h=%d, p=%d\n", __func__,
cfg->divs.h_divisor, cfg->divs.p_divisor);
clkdiv = __raw_readl(S3C2410_CLKDIVN);
camdiv = __raw_readl(S3C2440_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;
case 3:
clkdiv |= S3C2440_CLKDIVN_HDIVN_3_6;
break;
case 8:
camdiv |= S3C2440_CAMDIVN_HCLK4_HALF;
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.
*/
__raw_writel(camdiv | CAMDIVN_HCLK_HALF, S3C2440_CAMDIVN);
__raw_writel(clkdiv, S3C2410_CLKDIVN);
ndelay(20);
__raw_writel(camdiv, S3C2440_CAMDIVN);
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;
}
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,
.resume_clocks = s3c244x_setup_clocks,
.debug_io_show = s3c_cpufreq_debugfs_call(s3c2410_iotiming_debugfs),
};
static int s3c2440_cpufreq_add(struct sys_device *sysdev)
{
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)) {
printk(KERN_ERR "%s: failed to get clocks\n", __func__);
return -ENOENT;
}
return s3c_cpufreq_register(&s3c2440_cpufreq_info);
}
static struct sysdev_driver s3c2440_cpufreq_driver = {
.add = s3c2440_cpufreq_add,
};
static int s3c2440_cpufreq_init(void)
{
return sysdev_driver_register(&s3c2440_sysclass,
&s3c2440_cpufreq_driver);
}
/* arch_initcall adds the clocks we need, so use subsys_initcall. */
subsys_initcall(s3c2440_cpufreq_init);
static struct sysdev_driver s3c2442_cpufreq_driver = {
.add = s3c2440_cpufreq_add,
};
static int s3c2442_cpufreq_init(void)
{
return sysdev_driver_register(&s3c2442_sysclass,
&s3c2442_cpufreq_driver);
}
subsys_initcall(s3c2442_cpufreq_init);

View file

@ -0,0 +1,97 @@
/* arch/arm/plat-s3c24xx/s3c2440-pll-12000000.c
*
* Copyright (c) 2006,2007 Simtec Electronics
* http://armlinux.simtec.co.uk/
* Ben Dooks <ben@simtec.co.uk>
* Vincent Sanders <vince@arm.linux.org.uk>
*
* S3C2440/S3C2442 CPU PLL tables (12MHz Crystal)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/sysdev.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <plat/cpu.h>
#include <plat/cpu-freq-core.h>
static struct cpufreq_frequency_table s3c2440_plls_12[] __initdata = {
{ .frequency = 75000000, .index = PLLVAL(0x75, 3, 3), }, /* FVco 600.000000 */
{ .frequency = 80000000, .index = PLLVAL(0x98, 4, 3), }, /* FVco 640.000000 */
{ .frequency = 90000000, .index = PLLVAL(0x70, 2, 3), }, /* FVco 720.000000 */
{ .frequency = 100000000, .index = PLLVAL(0x5c, 1, 3), }, /* FVco 800.000000 */
{ .frequency = 110000000, .index = PLLVAL(0x66, 1, 3), }, /* FVco 880.000000 */
{ .frequency = 120000000, .index = PLLVAL(0x70, 1, 3), }, /* FVco 960.000000 */
{ .frequency = 150000000, .index = PLLVAL(0x75, 3, 2), }, /* FVco 600.000000 */
{ .frequency = 160000000, .index = PLLVAL(0x98, 4, 2), }, /* FVco 640.000000 */
{ .frequency = 170000000, .index = PLLVAL(0x4d, 1, 2), }, /* FVco 680.000000 */
{ .frequency = 180000000, .index = PLLVAL(0x70, 2, 2), }, /* FVco 720.000000 */
{ .frequency = 190000000, .index = PLLVAL(0x57, 1, 2), }, /* FVco 760.000000 */
{ .frequency = 200000000, .index = PLLVAL(0x5c, 1, 2), }, /* FVco 800.000000 */
{ .frequency = 210000000, .index = PLLVAL(0x84, 2, 2), }, /* FVco 840.000000 */
{ .frequency = 220000000, .index = PLLVAL(0x66, 1, 2), }, /* FVco 880.000000 */
{ .frequency = 230000000, .index = PLLVAL(0x6b, 1, 2), }, /* FVco 920.000000 */
{ .frequency = 240000000, .index = PLLVAL(0x70, 1, 2), }, /* FVco 960.000000 */
{ .frequency = 300000000, .index = PLLVAL(0x75, 3, 1), }, /* FVco 600.000000 */
{ .frequency = 310000000, .index = PLLVAL(0x93, 4, 1), }, /* FVco 620.000000 */
{ .frequency = 320000000, .index = PLLVAL(0x98, 4, 1), }, /* FVco 640.000000 */
{ .frequency = 330000000, .index = PLLVAL(0x66, 2, 1), }, /* FVco 660.000000 */
{ .frequency = 340000000, .index = PLLVAL(0x4d, 1, 1), }, /* FVco 680.000000 */
{ .frequency = 350000000, .index = PLLVAL(0xa7, 4, 1), }, /* FVco 700.000000 */
{ .frequency = 360000000, .index = PLLVAL(0x70, 2, 1), }, /* FVco 720.000000 */
{ .frequency = 370000000, .index = PLLVAL(0xb1, 4, 1), }, /* FVco 740.000000 */
{ .frequency = 380000000, .index = PLLVAL(0x57, 1, 1), }, /* FVco 760.000000 */
{ .frequency = 390000000, .index = PLLVAL(0x7a, 2, 1), }, /* FVco 780.000000 */
{ .frequency = 400000000, .index = PLLVAL(0x5c, 1, 1), }, /* FVco 800.000000 */
};
static int s3c2440_plls12_add(struct sys_device *dev)
{
struct clk *xtal_clk;
unsigned long xtal;
xtal_clk = clk_get(NULL, "xtal");
if (IS_ERR(xtal_clk))
return PTR_ERR(xtal_clk);
xtal = clk_get_rate(xtal_clk);
clk_put(xtal_clk);
if (xtal == 12000000) {
printk(KERN_INFO "Using PLL table for 12MHz crystal\n");
return s3c_plltab_register(s3c2440_plls_12,
ARRAY_SIZE(s3c2440_plls_12));
}
return 0;
}
static struct sysdev_driver s3c2440_plls12_drv = {
.add = s3c2440_plls12_add,
};
static int __init s3c2440_pll_12mhz(void)
{
return sysdev_driver_register(&s3c2440_sysclass, &s3c2440_plls12_drv);
}
arch_initcall(s3c2440_pll_12mhz);
static struct sysdev_driver s3c2442_plls12_drv = {
.add = s3c2440_plls12_add,
};
static int __init s3c2442_pll_12mhz(void)
{
return sysdev_driver_register(&s3c2442_sysclass, &s3c2442_plls12_drv);
}
arch_initcall(s3c2442_pll_12mhz);

View file

@ -0,0 +1,127 @@
/* arch/arm/plat-s3c24xx/s3c2440-pll-16934400.c
*
* Copyright (c) 2006-2008 Simtec Electronics
* http://armlinux.simtec.co.uk/
* Ben Dooks <ben@simtec.co.uk>
* Vincent Sanders <vince@arm.linux.org.uk>
*
* S3C2440/S3C2442 CPU PLL tables (16.93444MHz Crystal)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/sysdev.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <plat/cpu.h>
#include <plat/cpu-freq-core.h>
static struct cpufreq_frequency_table s3c2440_plls_169344[] __initdata = {
{ .frequency = 78019200, .index = PLLVAL(121, 5, 3), }, /* FVco 624.153600 */
{ .frequency = 84067200, .index = PLLVAL(131, 5, 3), }, /* FVco 672.537600 */
{ .frequency = 90115200, .index = PLLVAL(141, 5, 3), }, /* FVco 720.921600 */
{ .frequency = 96163200, .index = PLLVAL(151, 5, 3), }, /* FVco 769.305600 */
{ .frequency = 102135600, .index = PLLVAL(185, 6, 3), }, /* FVco 817.084800 */
{ .frequency = 108259200, .index = PLLVAL(171, 5, 3), }, /* FVco 866.073600 */
{ .frequency = 114307200, .index = PLLVAL(127, 3, 3), }, /* FVco 914.457600 */
{ .frequency = 120234240, .index = PLLVAL(134, 3, 3), }, /* FVco 961.873920 */
{ .frequency = 126161280, .index = PLLVAL(141, 3, 3), }, /* FVco 1009.290240 */
{ .frequency = 132088320, .index = PLLVAL(148, 3, 3), }, /* FVco 1056.706560 */
{ .frequency = 138015360, .index = PLLVAL(155, 3, 3), }, /* FVco 1104.122880 */
{ .frequency = 144789120, .index = PLLVAL(163, 3, 3), }, /* FVco 1158.312960 */
{ .frequency = 150100363, .index = PLLVAL(187, 9, 2), }, /* FVco 600.401454 */
{ .frequency = 156038400, .index = PLLVAL(121, 5, 2), }, /* FVco 624.153600 */
{ .frequency = 162086400, .index = PLLVAL(126, 5, 2), }, /* FVco 648.345600 */
{ .frequency = 168134400, .index = PLLVAL(131, 5, 2), }, /* FVco 672.537600 */
{ .frequency = 174048000, .index = PLLVAL(177, 7, 2), }, /* FVco 696.192000 */
{ .frequency = 180230400, .index = PLLVAL(141, 5, 2), }, /* FVco 720.921600 */
{ .frequency = 186278400, .index = PLLVAL(124, 4, 2), }, /* FVco 745.113600 */
{ .frequency = 192326400, .index = PLLVAL(151, 5, 2), }, /* FVco 769.305600 */
{ .frequency = 198132480, .index = PLLVAL(109, 3, 2), }, /* FVco 792.529920 */
{ .frequency = 204271200, .index = PLLVAL(185, 6, 2), }, /* FVco 817.084800 */
{ .frequency = 210268800, .index = PLLVAL(141, 4, 2), }, /* FVco 841.075200 */
{ .frequency = 216518400, .index = PLLVAL(171, 5, 2), }, /* FVco 866.073600 */
{ .frequency = 222264000, .index = PLLVAL(97, 2, 2), }, /* FVco 889.056000 */
{ .frequency = 228614400, .index = PLLVAL(127, 3, 2), }, /* FVco 914.457600 */
{ .frequency = 234259200, .index = PLLVAL(158, 4, 2), }, /* FVco 937.036800 */
{ .frequency = 240468480, .index = PLLVAL(134, 3, 2), }, /* FVco 961.873920 */
{ .frequency = 246960000, .index = PLLVAL(167, 4, 2), }, /* FVco 987.840000 */
{ .frequency = 252322560, .index = PLLVAL(141, 3, 2), }, /* FVco 1009.290240 */
{ .frequency = 258249600, .index = PLLVAL(114, 2, 2), }, /* FVco 1032.998400 */
{ .frequency = 264176640, .index = PLLVAL(148, 3, 2), }, /* FVco 1056.706560 */
{ .frequency = 270950400, .index = PLLVAL(120, 2, 2), }, /* FVco 1083.801600 */
{ .frequency = 276030720, .index = PLLVAL(155, 3, 2), }, /* FVco 1104.122880 */
{ .frequency = 282240000, .index = PLLVAL(92, 1, 2), }, /* FVco 1128.960000 */
{ .frequency = 289578240, .index = PLLVAL(163, 3, 2), }, /* FVco 1158.312960 */
{ .frequency = 294235200, .index = PLLVAL(131, 2, 2), }, /* FVco 1176.940800 */
{ .frequency = 300200727, .index = PLLVAL(187, 9, 1), }, /* FVco 600.401454 */
{ .frequency = 306358690, .index = PLLVAL(191, 9, 1), }, /* FVco 612.717380 */
{ .frequency = 312076800, .index = PLLVAL(121, 5, 1), }, /* FVco 624.153600 */
{ .frequency = 318366720, .index = PLLVAL(86, 3, 1), }, /* FVco 636.733440 */
{ .frequency = 324172800, .index = PLLVAL(126, 5, 1), }, /* FVco 648.345600 */
{ .frequency = 330220800, .index = PLLVAL(109, 4, 1), }, /* FVco 660.441600 */
{ .frequency = 336268800, .index = PLLVAL(131, 5, 1), }, /* FVco 672.537600 */
{ .frequency = 342074880, .index = PLLVAL(93, 3, 1), }, /* FVco 684.149760 */
{ .frequency = 348096000, .index = PLLVAL(177, 7, 1), }, /* FVco 696.192000 */
{ .frequency = 355622400, .index = PLLVAL(118, 4, 1), }, /* FVco 711.244800 */
{ .frequency = 360460800, .index = PLLVAL(141, 5, 1), }, /* FVco 720.921600 */
{ .frequency = 366206400, .index = PLLVAL(165, 6, 1), }, /* FVco 732.412800 */
{ .frequency = 372556800, .index = PLLVAL(124, 4, 1), }, /* FVco 745.113600 */
{ .frequency = 378201600, .index = PLLVAL(126, 4, 1), }, /* FVco 756.403200 */
{ .frequency = 384652800, .index = PLLVAL(151, 5, 1), }, /* FVco 769.305600 */
{ .frequency = 391608000, .index = PLLVAL(177, 6, 1), }, /* FVco 783.216000 */
{ .frequency = 396264960, .index = PLLVAL(109, 3, 1), }, /* FVco 792.529920 */
{ .frequency = 402192000, .index = PLLVAL(87, 2, 1), }, /* FVco 804.384000 */
};
static int s3c2440_plls169344_add(struct sys_device *dev)
{
struct clk *xtal_clk;
unsigned long xtal;
xtal_clk = clk_get(NULL, "xtal");
if (IS_ERR(xtal_clk))
return PTR_ERR(xtal_clk);
xtal = clk_get_rate(xtal_clk);
clk_put(xtal_clk);
if (xtal == 169344000) {
printk(KERN_INFO "Using PLL table for 16.9344MHz crystal\n");
return s3c_plltab_register(s3c2440_plls_169344,
ARRAY_SIZE(s3c2440_plls_169344));
}
return 0;
}
static struct sysdev_driver s3c2440_plls169344_drv = {
.add = s3c2440_plls169344_add,
};
static int __init s3c2440_pll_16934400(void)
{
return sysdev_driver_register(&s3c2440_sysclass,
&s3c2440_plls169344_drv);
}
arch_initcall(s3c2440_pll_16934400);
static struct sysdev_driver s3c2442_plls169344_drv = {
.add = s3c2440_plls169344_add,
};
static int __init s3c2442_pll_16934400(void)
{
return sysdev_driver_register(&s3c2442_sysclass,
&s3c2442_plls169344_drv);
}
arch_initcall(s3c2442_pll_16934400);

View file

@ -0,0 +1,80 @@
/* linux/amba/pl093.h
*
* Copyright (c) 2008 Simtec Electronics
* http://armlinux.simtec.co.uk/
* Ben Dooks <ben@simtec.co.uk>
*
* AMBA PL093 SSMC (synchronous static memory controller)
* See DDI0236.pdf (r0p4) for more details
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#define SMB_BANK(x) ((x) * 0x20) /* each bank control set is 0x20 apart */
/* Offsets for SMBxxxxRy registers */
#define SMBIDCYR (0x00)
#define SMBWSTRDR (0x04)
#define SMBWSTWRR (0x08)
#define SMBWSTOENR (0x0C)
#define SMBWSTWENR (0x10)
#define SMBCR (0x14)
#define SMBSR (0x18)
#define SMBWSTBRDR (0x1C)
/* Masks for SMB registers */
#define IDCY_MASK (0xf)
#define WSTRD_MASK (0xf)
#define WSTWR_MASK (0xf)
#define WSTOEN_MASK (0xf)
#define WSTWEN_MASK (0xf)
/* Notes from datasheet:
* WSTOEN <= WSTRD
* WSTWEN <= WSTWR
*
* WSTOEN is not used with nWAIT
*/
/* SMBCR bit definitions */
#define SMBCR_BIWRITEEN (1 << 21)
#define SMBCR_ADDRVALIDWRITEEN (1 << 20)
#define SMBCR_SYNCWRITE (1 << 17)
#define SMBCR_BMWRITE (1 << 16)
#define SMBCR_WRAPREAD (1 << 14)
#define SMBCR_BIREADEN (1 << 13)
#define SMBCR_ADDRVALIDREADEN (1 << 12)
#define SMBCR_SYNCREAD (1 << 9)
#define SMBCR_BMREAD (1 << 8)
#define SMBCR_SMBLSPOL (1 << 6)
#define SMBCR_WP (1 << 3)
#define SMBCR_WAITEN (1 << 2)
#define SMBCR_WAITPOL (1 << 1)
#define SMBCR_RBLE (1 << 0)
#define SMBCR_BURSTLENWRITE_MASK (3 << 18)
#define SMBCR_BURSTLENWRITE_4 (0 << 18)
#define SMBCR_BURSTLENWRITE_8 (1 << 18)
#define SMBCR_BURSTLENWRITE_RESERVED (2 << 18)
#define SMBCR_BURSTLENWRITE_CONTINUOUS (3 << 18)
#define SMBCR_BURSTLENREAD_MASK (3 << 10)
#define SMBCR_BURSTLENREAD_4 (0 << 10)
#define SMBCR_BURSTLENREAD_8 (1 << 10)
#define SMBCR_BURSTLENREAD_16 (2 << 10)
#define SMBCR_BURSTLENREAD_CONTINUOUS (3 << 10)
#define SMBCR_MW_MASK (3 << 4)
#define SMBCR_MW_8BIT (0 << 4)
#define SMBCR_MW_16BIT (1 << 4)
#define SMBCR_MW_M32BIT (2 << 4)
/* SSMC status registers */
#define SSMCCSR (0x200)
#define SSMCCR (0x204)
#define SSMCITCR (0x208)
#define SSMCITIP (0x20C)
#define SSMCITIOP (0x210)