aboutsummaryrefslogtreecommitdiff
path: root/arch/arm/mach-s3c24xx
diff options
context:
space:
mode:
authorKukjin Kim <kgene.kim@samsung.com>2013-01-31 16:54:38 -0800
committerKukjin Kim <kgene.kim@samsung.com>2013-02-03 16:17:10 -0800
commit09ec1d7ea67f6e23b6ef2178fa2ec48fd65477dc (patch)
tree38318b5c98f48754521a288f40e3c77a1fe8a623 /arch/arm/mach-s3c24xx
parentf44ddba3635e35317057e976888d4a12dcb0f842 (diff)
ARM: S3C24XX: Remove plat-s3c24xx directory in arch/arm/
This patch is for just moving plat-s3c24xx/*.c into mach-s3c24xx/, so that we could remove plat-s3c24xx directory. But since the PLAT_S3C24XX is used in drivers, the statement is not deleted and it will be sorted out next time. Cc: Ben Dooks <ben-linux@fluff.org> Cc: Russell King <rmk+kernel@arm.linux.org.uk> Signed-off-by: Kukjin Kim <kgene.kim@samsung.com>
Diffstat (limited to 'arch/arm/mach-s3c24xx')
-rw-r--r--arch/arm/mach-s3c24xx/Kconfig104
-rw-r--r--arch/arm/mach-s3c24xx/Makefile14
-rw-r--r--arch/arm/mach-s3c24xx/clock-dclk.c195
-rw-r--r--arch/arm/mach-s3c24xx/clock-s3c2410.c252
-rw-r--r--arch/arm/mach-s3c24xx/cpufreq-debugfs.c198
-rw-r--r--arch/arm/mach-s3c24xx/cpufreq-utils.c63
-rw-r--r--arch/arm/mach-s3c24xx/cpufreq.c715
-rw-r--r--arch/arm/mach-s3c24xx/dma.c1468
-rw-r--r--arch/arm/mach-s3c24xx/iotiming-s3c2410.c477
-rw-r--r--arch/arm/mach-s3c24xx/iotiming-s3c2412.c285
-rw-r--r--arch/arm/mach-s3c24xx/irq.c822
11 files changed, 4592 insertions, 1 deletions
diff --git a/arch/arm/mach-s3c24xx/Kconfig b/arch/arm/mach-s3c24xx/Kconfig
index 67df58bdc096..31eacad5b3e9 100644
--- a/arch/arm/mach-s3c24xx/Kconfig
+++ b/arch/arm/mach-s3c24xx/Kconfig
@@ -9,6 +9,15 @@
if ARCH_S3C24XX
+config PLAT_S3C24XX
+ def_bool y
+ select ARCH_REQUIRE_GPIOLIB
+ select NO_IOPORT
+ select S3C_DEV_NAND
+ select IRQ_DOMAIN
+ help
+ Base platform code for any Samsung S3C24XX device
+
menu "SAMSUNG S3C24XX SoCs Support"
comment "S3C24XX SoCs"
@@ -83,6 +92,17 @@ config CPU_S3C2443
# common code
+config S3C2410_CLOCK
+ bool
+ help
+ Clock code for the S3C2410, and similar processors which
+ is currently includes the S3C2410, S3C2440, S3C2442.
+
+config S3C24XX_DCLK
+ bool
+ help
+ Clock code for supporting DCLK/CLKOUT on S3C24XX architectures
+
config S3C24XX_SMDK
bool
help
@@ -111,6 +131,22 @@ config S3C24XX_SETUP_TS
help
Compile in platform device definition for Samsung TouchScreen.
+config S3C24XX_DMA
+ bool "S3C2410 DMA support"
+ depends on ARCH_S3C24XX
+ select S3C_DMA
+ help
+ S3C2410 DMA support. This is needed for drivers like sound which
+ use the S3C2410's DMA system to move data to and from the
+ peripheral blocks.
+
+config S3C2410_DMA_DEBUG
+ bool "S3C2410 DMA support debug"
+ depends on ARCH_S3C24XX && S3C2410_DMA
+ help
+ Enable debugging output for the DMA code. This option sends info
+ to the kernel log, at priority KERN_DEBUG.
+
config S3C2410_DMA
bool
depends on S3C24XX_DMA && (CPU_S3C2410 || CPU_S3C2442)
@@ -123,6 +159,74 @@ config S3C2410_PM
help
Power Management code common to S3C2410 and better
+# low-level serial option nodes
+
+config CPU_LLSERIAL_S3C2410_ONLY
+ bool
+ default y if CPU_LLSERIAL_S3C2410 && !CPU_LLSERIAL_S3C2440
+
+config CPU_LLSERIAL_S3C2440_ONLY
+ bool
+ default y if CPU_LLSERIAL_S3C2440 && !CPU_LLSERIAL_S3C2410
+
+config CPU_LLSERIAL_S3C2410
+ bool
+ help
+ Selected if there is an S3C2410 (or register compatible) serial
+ low-level implementation needed
+
+config CPU_LLSERIAL_S3C2440
+ bool
+ help
+ Selected if there is an S3C2440 (or register compatible) serial
+ low-level implementation needed
+
+# gpio configurations
+
+config S3C24XX_GPIO_EXTRA
+ int
+ default 128 if S3C24XX_GPIO_EXTRA128
+ default 64 if S3C24XX_GPIO_EXTRA64
+ default 16 if ARCH_H1940
+ default 0
+
+config S3C24XX_GPIO_EXTRA64
+ bool
+ help
+ Add an extra 64 gpio numbers to the available GPIO pool. This is
+ available for boards that need extra gpios for external devices.
+
+config S3C24XX_GPIO_EXTRA128
+ bool
+ help
+ Add an extra 128 gpio numbers to the available GPIO pool. This is
+ available for boards that need extra gpios for external devices.
+
+# 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.
+
# cpu-specific sections
if CPU_S3C2410
diff --git a/arch/arm/mach-s3c24xx/Makefile b/arch/arm/mach-s3c24xx/Makefile
index 1d67582da41a..af53d27d5c36 100644
--- a/arch/arm/mach-s3c24xx/Makefile
+++ b/arch/arm/mach-s3c24xx/Makefile
@@ -14,7 +14,7 @@ obj- :=
# core
-obj-y += common.o
+obj-y += common.o irq.o
obj-$(CONFIG_CPU_S3C2410) += s3c2410.o
obj-$(CONFIG_S3C2410_CPUFREQ) += cpufreq-s3c2410.o
@@ -47,9 +47,21 @@ obj-$(CONFIG_PM) += pm.o irq-pm.o sleep.o
# common code
+obj-$(CONFIG_S3C24XX_DCLK) += clock-dclk.o
+obj-$(CONFIG_S3C24XX_DMA) += dma.o
+
+obj-$(CONFIG_S3C2410_CLOCK) += clock-s3c2410.o
+obj-$(CONFIG_S3C2410_CPUFREQ_UTILS) += cpufreq-utils.o
+
+obj-$(CONFIG_S3C2410_IOTIMING) += iotiming-s3c2410.o
+obj-$(CONFIG_S3C2412_IOTIMING) += iotiming-s3c2412.o
+
obj-$(CONFIG_S3C2443_COMMON) += common-s3c2443.o
obj-$(CONFIG_S3C2443_DMA) += dma-s3c2443.o
+obj-$(CONFIG_CPU_FREQ_S3C24XX) += cpufreq.o
+obj-$(CONFIG_CPU_FREQ_S3C24XX_DEBUGFS) += cpufreq-debugfs.o
+
#
# machine support
# following is ordered alphabetically by option text.
diff --git a/arch/arm/mach-s3c24xx/clock-dclk.c b/arch/arm/mach-s3c24xx/clock-dclk.c
new file mode 100644
index 000000000000..1edd9b2369c5
--- /dev/null
+++ b/arch/arm/mach-s3c24xx/clock-dclk.c
@@ -0,0 +1,195 @@
+/*
+ * Copyright (c) 2004-2008 Simtec Electronics
+ * Ben Dooks <ben@simtec.co.uk>
+ * http://armlinux.simtec.co.uk/
+ *
+ * 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.
+ *
+ * S3C24XX - definitions for DCLK and CLKOUT registers
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+
+#include <mach/regs-clock.h>
+#include <mach/regs-gpio.h>
+
+#include <plat/clock.h>
+#include <plat/cpu.h>
+
+/* clocks that could be registered by external code */
+
+static int s3c24xx_dclk_enable(struct clk *clk, int enable)
+{
+ unsigned long dclkcon = __raw_readl(S3C24XX_DCLKCON);
+
+ if (enable)
+ dclkcon |= clk->ctrlbit;
+ else
+ dclkcon &= ~clk->ctrlbit;
+
+ __raw_writel(dclkcon, S3C24XX_DCLKCON);
+
+ return 0;
+}
+
+static int s3c24xx_dclk_setparent(struct clk *clk, struct clk *parent)
+{
+ unsigned long dclkcon;
+ unsigned int uclk;
+
+ if (parent == &clk_upll)
+ uclk = 1;
+ else if (parent == &clk_p)
+ uclk = 0;
+ else
+ return -EINVAL;
+
+ clk->parent = parent;
+
+ dclkcon = __raw_readl(S3C24XX_DCLKCON);
+
+ if (clk->ctrlbit == S3C2410_DCLKCON_DCLK0EN) {
+ if (uclk)
+ dclkcon |= S3C2410_DCLKCON_DCLK0_UCLK;
+ else
+ dclkcon &= ~S3C2410_DCLKCON_DCLK0_UCLK;
+ } else {
+ if (uclk)
+ dclkcon |= S3C2410_DCLKCON_DCLK1_UCLK;
+ else
+ dclkcon &= ~S3C2410_DCLKCON_DCLK1_UCLK;
+ }
+
+ __raw_writel(dclkcon, S3C24XX_DCLKCON);
+
+ return 0;
+}
+static unsigned long s3c24xx_calc_div(struct clk *clk, unsigned long rate)
+{
+ unsigned long div;
+
+ if ((rate == 0) || !clk->parent)
+ return 0;
+
+ div = clk_get_rate(clk->parent) / rate;
+ if (div < 2)
+ div = 2;
+ else if (div > 16)
+ div = 16;
+
+ return div;
+}
+
+static unsigned long s3c24xx_round_dclk_rate(struct clk *clk,
+ unsigned long rate)
+{
+ unsigned long div = s3c24xx_calc_div(clk, rate);
+
+ if (div == 0)
+ return 0;
+
+ return clk_get_rate(clk->parent) / div;
+}
+
+static int s3c24xx_set_dclk_rate(struct clk *clk, unsigned long rate)
+{
+ unsigned long mask, data, div = s3c24xx_calc_div(clk, rate);
+
+ if (div == 0)
+ return -EINVAL;
+
+ if (clk == &s3c24xx_dclk0) {
+ mask = S3C2410_DCLKCON_DCLK0_DIV_MASK |
+ S3C2410_DCLKCON_DCLK0_CMP_MASK;
+ data = S3C2410_DCLKCON_DCLK0_DIV(div) |
+ S3C2410_DCLKCON_DCLK0_CMP((div + 1) / 2);
+ } else if (clk == &s3c24xx_dclk1) {
+ mask = S3C2410_DCLKCON_DCLK1_DIV_MASK |
+ S3C2410_DCLKCON_DCLK1_CMP_MASK;
+ data = S3C2410_DCLKCON_DCLK1_DIV(div) |
+ S3C2410_DCLKCON_DCLK1_CMP((div + 1) / 2);
+ } else
+ return -EINVAL;
+
+ clk->rate = clk_get_rate(clk->parent) / div;
+ __raw_writel(((__raw_readl(S3C24XX_DCLKCON) & ~mask) | data),
+ S3C24XX_DCLKCON);
+ return clk->rate;
+}
+static int s3c24xx_clkout_setparent(struct clk *clk, struct clk *parent)
+{
+ unsigned long mask;
+ unsigned long source;
+
+ /* calculate the MISCCR setting for the clock */
+
+ if (parent == &clk_mpll)
+ source = S3C2410_MISCCR_CLK0_MPLL;
+ else if (parent == &clk_upll)
+ source = S3C2410_MISCCR_CLK0_UPLL;
+ else if (parent == &clk_f)
+ source = S3C2410_MISCCR_CLK0_FCLK;
+ else if (parent == &clk_h)
+ source = S3C2410_MISCCR_CLK0_HCLK;
+ else if (parent == &clk_p)
+ source = S3C2410_MISCCR_CLK0_PCLK;
+ else if (clk == &s3c24xx_clkout0 && parent == &s3c24xx_dclk0)
+ source = S3C2410_MISCCR_CLK0_DCLK0;
+ else if (clk == &s3c24xx_clkout1 && parent == &s3c24xx_dclk1)
+ source = S3C2410_MISCCR_CLK0_DCLK0;
+ else
+ return -EINVAL;
+
+ clk->parent = parent;
+
+ if (clk == &s3c24xx_clkout0)
+ mask = S3C2410_MISCCR_CLK0_MASK;
+ else {
+ source <<= 4;
+ mask = S3C2410_MISCCR_CLK1_MASK;
+ }
+
+ s3c2410_modify_misccr(mask, source);
+ return 0;
+}
+
+/* external clock definitions */
+
+static struct clk_ops dclk_ops = {
+ .set_parent = s3c24xx_dclk_setparent,
+ .set_rate = s3c24xx_set_dclk_rate,
+ .round_rate = s3c24xx_round_dclk_rate,
+};
+
+struct clk s3c24xx_dclk0 = {
+ .name = "dclk0",
+ .ctrlbit = S3C2410_DCLKCON_DCLK0EN,
+ .enable = s3c24xx_dclk_enable,
+ .ops = &dclk_ops,
+};
+
+struct clk s3c24xx_dclk1 = {
+ .name = "dclk1",
+ .ctrlbit = S3C2410_DCLKCON_DCLK1EN,
+ .enable = s3c24xx_dclk_enable,
+ .ops = &dclk_ops,
+};
+
+static struct clk_ops clkout_ops = {
+ .set_parent = s3c24xx_clkout_setparent,
+};
+
+struct clk s3c24xx_clkout0 = {
+ .name = "clkout0",
+ .ops = &clkout_ops,
+};
+
+struct clk s3c24xx_clkout1 = {
+ .name = "clkout1",
+ .ops = &clkout_ops,
+};
diff --git a/arch/arm/mach-s3c24xx/clock-s3c2410.c b/arch/arm/mach-s3c24xx/clock-s3c2410.c
new file mode 100644
index 000000000000..641266f3d152
--- /dev/null
+++ b/arch/arm/mach-s3c24xx/clock-s3c2410.c
@@ -0,0 +1,252 @@
+/*
+ * Copyright (c) 2006 Simtec Electronics
+ * Ben Dooks <ben@simtec.co.uk>
+ *
+ * S3C2410,S3C2440,S3C2442 Clock control support
+ *
+ * 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/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/errno.h>
+#include <linux/err.h>
+#include <linux/device.h>
+#include <linux/clk.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/serial_core.h>
+#include <linux/io.h>
+
+#include <asm/mach/map.h>
+
+#include <mach/hardware.h>
+
+#include <plat/regs-serial.h>
+#include <mach/regs-clock.h>
+#include <mach/regs-gpio.h>
+
+#include <plat/s3c2410.h>
+#include <plat/clock.h>
+#include <plat/cpu.h>
+
+int s3c2410_clkcon_enable(struct clk *clk, int enable)
+{
+ unsigned int clocks = clk->ctrlbit;
+ unsigned long clkcon;
+
+ clkcon = __raw_readl(S3C2410_CLKCON);
+
+ if (enable)
+ clkcon |= clocks;
+ else
+ clkcon &= ~clocks;
+
+ /* ensure none of the special function bits set */
+ clkcon &= ~(S3C2410_CLKCON_IDLE|S3C2410_CLKCON_POWER);
+
+ __raw_writel(clkcon, S3C2410_CLKCON);
+
+ return 0;
+}
+
+static int s3c2410_upll_enable(struct clk *clk, int enable)
+{
+ unsigned long clkslow = __raw_readl(S3C2410_CLKSLOW);
+ unsigned long orig = clkslow;
+
+ if (enable)
+ clkslow &= ~S3C2410_CLKSLOW_UCLK_OFF;
+ else
+ clkslow |= S3C2410_CLKSLOW_UCLK_OFF;
+
+ __raw_writel(clkslow, S3C2410_CLKSLOW);
+
+ /* if we started the UPLL, then allow to settle */
+
+ if (enable && (orig & S3C2410_CLKSLOW_UCLK_OFF))
+ udelay(200);
+
+ return 0;
+}
+
+/* standard clock definitions */
+
+static struct clk init_clocks_off[] = {
+ {
+ .name = "nand",
+ .parent = &clk_h,
+ .enable = s3c2410_clkcon_enable,
+ .ctrlbit = S3C2410_CLKCON_NAND,
+ }, {
+ .name = "sdi",
+ .parent = &clk_p,
+ .enable = s3c2410_clkcon_enable,
+ .ctrlbit = S3C2410_CLKCON_SDI,
+ }, {
+ .name = "adc",
+ .parent = &clk_p,
+ .enable = s3c2410_clkcon_enable,
+ .ctrlbit = S3C2410_CLKCON_ADC,
+ }, {
+ .name = "i2c",
+ .parent = &clk_p,
+ .enable = s3c2410_clkcon_enable,
+ .ctrlbit = S3C2410_CLKCON_IIC,
+ }, {
+ .name = "iis",
+ .parent = &clk_p,
+ .enable = s3c2410_clkcon_enable,
+ .ctrlbit = S3C2410_CLKCON_IIS,
+ }, {
+ .name = "spi",
+ .parent = &clk_p,
+ .enable = s3c2410_clkcon_enable,
+ .ctrlbit = S3C2410_CLKCON_SPI,
+ }
+};
+
+static struct clk init_clocks[] = {
+ {
+ .name = "lcd",
+ .parent = &clk_h,
+ .enable = s3c2410_clkcon_enable,
+ .ctrlbit = S3C2410_CLKCON_LCDC,
+ }, {
+ .name = "gpio",
+ .parent = &clk_p,
+ .enable = s3c2410_clkcon_enable,
+ .ctrlbit = S3C2410_CLKCON_GPIO,
+ }, {
+ .name = "usb-host",
+ .parent = &clk_h,
+ .enable = s3c2410_clkcon_enable,
+ .ctrlbit = S3C2410_CLKCON_USBH,
+ }, {
+ .name = "usb-device",
+ .parent = &clk_h,
+ .enable = s3c2410_clkcon_enable,
+ .ctrlbit = S3C2410_CLKCON_USBD,
+ }, {
+ .name = "timers",
+ .parent = &clk_p,
+ .enable = s3c2410_clkcon_enable,
+ .ctrlbit = S3C2410_CLKCON_PWMT,
+ }, {
+ .name = "uart",
+ .devname = "s3c2410-uart.0",
+ .parent = &clk_p,
+ .enable = s3c2410_clkcon_enable,
+ .ctrlbit = S3C2410_CLKCON_UART0,
+ }, {
+ .name = "uart",
+ .devname = "s3c2410-uart.1",
+ .parent = &clk_p,
+ .enable = s3c2410_clkcon_enable,
+ .ctrlbit = S3C2410_CLKCON_UART1,
+ }, {
+ .name = "uart",
+ .devname = "s3c2410-uart.2",
+ .parent = &clk_p,
+ .enable = s3c2410_clkcon_enable,
+ .ctrlbit = S3C2410_CLKCON_UART2,
+ }, {
+ .name = "rtc",
+ .parent = &clk_p,
+ .enable = s3c2410_clkcon_enable,
+ .ctrlbit = S3C2410_CLKCON_RTC,
+ }, {
+ .name = "watchdog",
+ .parent = &clk_p,
+ .ctrlbit = 0,
+ }, {
+ .name = "usb-bus-host",
+ .parent = &clk_usb_bus,
+ }, {
+ .name = "usb-bus-gadget",
+ .parent = &clk_usb_bus,
+ },
+};
+
+/* s3c2410_baseclk_add()
+ *
+ * Add all the clocks used by the s3c2410 or compatible CPUs
+ * such as the S3C2440 and S3C2442.
+ *
+ * We cannot use a system device as we are needed before any
+ * of the init-calls that initialise the devices are actually
+ * done.
+*/
+
+int __init s3c2410_baseclk_add(void)
+{
+ unsigned long clkslow = __raw_readl(S3C2410_CLKSLOW);
+ unsigned long clkcon = __raw_readl(S3C2410_CLKCON);
+ struct clk *clkp;
+ struct clk *xtal;
+ int ret;
+ int ptr;
+
+ clk_upll.enable = s3c2410_upll_enable;
+
+ if (s3c24xx_register_clock(&clk_usb_bus) < 0)
+ printk(KERN_ERR "failed to register usb bus clock\n");
+
+ /* register clocks from clock array */
+
+ clkp = init_clocks;
+ for (ptr = 0; ptr < ARRAY_SIZE(init_clocks); ptr++, clkp++) {
+ /* ensure that we note the clock state */
+
+ clkp->usage = clkcon & clkp->ctrlbit ? 1 : 0;
+
+ ret = s3c24xx_register_clock(clkp);
+ if (ret < 0) {
+ printk(KERN_ERR "Failed to register clock %s (%d)\n",
+ clkp->name, ret);
+ }
+ }
+
+ /* We must be careful disabling the clocks we are not intending to
+ * be using at boot time, as subsystems such as the LCD which do
+ * their own DMA requests to the bus can cause the system to lockup
+ * if they where in the middle of requesting bus access.
+ *
+ * Disabling the LCD clock if the LCD is active is very dangerous,
+ * and therefore the bootloader should be careful to not enable
+ * the LCD clock if it is not needed.
+ */
+
+ /* install (and disable) the clocks we do not need immediately */
+
+ s3c_register_clocks(init_clocks_off, ARRAY_SIZE(init_clocks_off));
+ s3c_disable_clocks(init_clocks_off, ARRAY_SIZE(init_clocks_off));
+
+ /* show the clock-slow value */
+
+ xtal = clk_get(NULL, "xtal");
+
+ printk("CLOCK: Slow mode (%ld.%ld MHz), %s, MPLL %s, UPLL %s\n",
+ print_mhz(clk_get_rate(xtal) /
+ ( 2 * S3C2410_CLKSLOW_GET_SLOWVAL(clkslow))),
+ (clkslow & S3C2410_CLKSLOW_SLOW) ? "slow" : "fast",
+ (clkslow & S3C2410_CLKSLOW_MPLL_OFF) ? "off" : "on",
+ (clkslow & S3C2410_CLKSLOW_UCLK_OFF) ? "off" : "on");
+
+ s3c_pwmclk_init();
+ return 0;
+}
diff --git a/arch/arm/mach-s3c24xx/cpufreq-debugfs.c b/arch/arm/mach-s3c24xx/cpufreq-debugfs.c
new file mode 100644
index 000000000000..9b7b4289d66c
--- /dev/null
+++ b/arch/arm/mach-s3c24xx/cpufreq-debugfs.c
@@ -0,0 +1,198 @@
+/*
+ * 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/export.h>
+#include <linux/interrupt.h>
+#include <linux/ioport.h>
+#include <linux/cpufreq.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/err.h>
+
+#include <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);
+
diff --git a/arch/arm/mach-s3c24xx/cpufreq-utils.c b/arch/arm/mach-s3c24xx/cpufreq-utils.c
new file mode 100644
index 000000000000..89e4e2b7a82e
--- /dev/null
+++ b/arch/arm/mach-s3c24xx/cpufreq-utils.c
@@ -0,0 +1,63 @@
+/*
+ * 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);
+}
diff --git a/arch/arm/mach-s3c24xx/cpufreq.c b/arch/arm/mach-s3c24xx/cpufreq.c
new file mode 100644
index 000000000000..5f181e733eee
--- /dev/null
+++ b/arch/arm/mach-s3c24xx/cpufreq.c
@@ -0,0 +1,715 @@
+/*
+ * Copyright (c) 2006-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/device.h>
+#include <linux/sysfs.h>
+#include <linux/slab.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)
+{
+ 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 immediately having to re-set
+ * a number of settings once these devices have restarted.
+ *
+ * as a note, it is expected devices are not used until they
+ * have been un-suspended and at that time they should have
+ * used the updated clock settings.
+ */
+
+ ret = s3c_cpufreq_settarget(NULL, suspend_freq, &suspend_pll);
+ if (ret) {
+ 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;
+}
diff --git a/arch/arm/mach-s3c24xx/dma.c b/arch/arm/mach-s3c24xx/dma.c
new file mode 100644
index 000000000000..aab64909e9a3
--- /dev/null
+++ b/arch/arm/mach-s3c24xx/dma.c
@@ -0,0 +1,1468 @@
+/*
+ * Copyright 2003-2006 Simtec Electronics
+ * Ben Dooks <ben@simtec.co.uk>
+ *
+ * S3C2410 DMA core
+ *
+ * http://armlinux.simtec.co.uk/
+ *
+ * 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.
+*/
+
+
+#ifdef CONFIG_S3C2410_DMA_DEBUG
+#define DEBUG
+#endif
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#include <linux/syscore_ops.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/io.h>
+
+#include <asm/irq.h>
+#include <mach/hardware.h>
+#include <mach/dma.h>
+#include <mach/map.h>
+
+#include <plat/dma-s3c24xx.h>
+#include <plat/regs-dma.h>
+
+/* io map for dma */
+static void __iomem *dma_base;
+static struct kmem_cache *dma_kmem;
+
+static int dma_channels;
+
+static struct s3c24xx_dma_selection dma_sel;
+
+
+/* debugging functions */
+
+#define BUF_MAGIC (0xcafebabe)
+
+#define dmawarn(fmt...) printk(KERN_DEBUG fmt)
+
+#define dma_regaddr(chan, reg) ((chan)->regs + (reg))
+
+#if 1
+#define dma_wrreg(chan, reg, val) writel((val), (chan)->regs + (reg))
+#else
+static inline void
+dma_wrreg(struct s3c2410_dma_chan *chan, int reg, unsigned long val)
+{
+ pr_debug("writing %08x to register %08x\n",(unsigned int)val,reg);
+ writel(val, dma_regaddr(chan, reg));
+}
+#endif
+
+#define dma_rdreg(chan, reg) readl((chan)->regs + (reg))
+
+/* captured register state for debug */
+
+struct s3c2410_dma_regstate {
+ unsigned long dcsrc;
+ unsigned long disrc;
+ unsigned long dstat;
+ unsigned long dcon;
+ unsigned long dmsktrig;
+};
+
+#ifdef CONFIG_S3C2410_DMA_DEBUG
+
+/* dmadbg_showregs
+ *
+ * simple debug routine to print the current state of the dma registers
+*/
+
+static void
+dmadbg_capture(struct s3c2410_dma_chan *chan, struct s3c2410_dma_regstate *regs)
+{
+ regs->dcsrc = dma_rdreg(chan, S3C2410_DMA_DCSRC);
+ regs->disrc = dma_rdreg(chan, S3C2410_DMA_DISRC);
+ regs->dstat = dma_rdreg(chan, S3C2410_DMA_DSTAT);
+ regs->dcon = dma_rdreg(chan, S3C2410_DMA_DCON);
+ regs->dmsktrig = dma_rdreg(chan, S3C2410_DMA_DMASKTRIG);
+}
+
+static void
+dmadbg_dumpregs(const char *fname, int line, struct s3c2410_dma_chan *chan,
+ struct s3c2410_dma_regstate *regs)
+{
+ printk(KERN_DEBUG "dma%d: %s:%d: DCSRC=%08lx, DISRC=%08lx, DSTAT=%08lx DMT=%02lx, DCON=%08lx\n",
+ chan->number, fname, line,
+ regs->dcsrc, regs->disrc, regs->dstat, regs->dmsktrig,
+ regs->dcon);
+}
+
+static void
+dmadbg_showchan(const char *fname, int line, struct s3c2410_dma_chan *chan)
+{
+ struct s3c2410_dma_regstate state;
+
+ dmadbg_capture(chan, &state);
+
+ printk(KERN_DEBUG "dma%d: %s:%d: ls=%d, cur=%p, %p %p\n",
+ chan->number, fname, line, chan->load_state,
+ chan->curr, chan->next, chan->end);
+
+ dmadbg_dumpregs(fname, line, chan, &state);
+}
+
+static void
+dmadbg_showregs(const char *fname, int line, struct s3c2410_dma_chan *chan)
+{
+ struct s3c2410_dma_regstate state;
+
+ dmadbg_capture(chan, &state);
+ dmadbg_dumpregs(fname, line, chan, &state);
+}
+
+#define dbg_showregs(chan) dmadbg_showregs(__func__, __LINE__, (chan))
+#define dbg_showchan(chan) dmadbg_showchan(__func__, __LINE__, (chan))
+#else
+#define dbg_showregs(chan) do { } while(0)
+#define dbg_showchan(chan) do { } while(0)
+#endif /* CONFIG_S3C2410_DMA_DEBUG */
+
+/* s3c2410_dma_stats_timeout
+ *
+ * Update DMA stats from timeout info
+*/
+
+static void
+s3c2410_dma_stats_timeout(struct s3c2410_dma_stats *stats, int val)
+{
+ if (stats == NULL)
+ return;
+
+ if (val > stats->timeout_longest)
+ stats->timeout_longest = val;
+ if (val < stats->timeout_shortest)
+ stats->timeout_shortest = val;
+
+ stats->timeout_avg += val;
+}
+
+/* s3c2410_dma_waitforload
+ *
+ * wait for the DMA engine to load a buffer, and update the state accordingly
+*/
+
+static int
+s3c2410_dma_waitforload(struct s3c2410_dma_chan *chan, int line)
+{
+ int timeout = chan->load_timeout;
+ int took;
+
+ if (chan->load_state != S3C2410_DMALOAD_1LOADED) {
+ printk(KERN_ERR "dma%d: s3c2410_dma_waitforload() called in loadstate %d from line %d\n", chan->number, chan->load_state, line);
+ return 0;
+ }
+
+ if (chan->stats != NULL)
+ chan->stats->loads++;
+
+ while (--timeout > 0) {
+ if ((dma_rdreg(chan, S3C2410_DMA_DSTAT) << (32-20)) != 0) {
+ took = chan->load_timeout - timeout;
+
+ s3c2410_dma_stats_timeout(chan->stats, took);
+
+ switch (chan->load_state) {
+ case S3C2410_DMALOAD_1LOADED:
+ chan->load_state = S3C2410_DMALOAD_1RUNNING;
+ break;
+
+ default:
+ printk(KERN_ERR "dma%d: unknown load_state in s3c2410_dma_waitforload() %d\n", chan->number, chan->load_state);
+ }
+
+ return 1;
+ }
+ }
+
+ if (chan->stats != NULL) {
+ chan->stats->timeout_failed++;
+ }
+
+ return 0;
+}
+
+/* s3c2410_dma_loadbuffer
+ *
+ * load a buffer, and update the channel state
+*/
+
+static inline int
+s3c2410_dma_loadbuffer(struct s3c2410_dma_chan *chan,
+ struct s3c2410_dma_buf *buf)
+{
+ unsigned long reload;
+
+ if (buf == NULL) {
+ dmawarn("buffer is NULL\n");
+ return -EINVAL;
+ }
+
+ pr_debug("s3c2410_chan_loadbuffer: loading buff %p (0x%08lx,0x%06x)\n",
+ buf, (unsigned long)buf->data, buf->size);
+
+ /* check the state of the channel before we do anything */
+
+ if (chan->load_state == S3C2410_DMALOAD_1LOADED) {
+ dmawarn("load_state is S3C2410_DMALOAD_1LOADED\n");
+ }
+
+ if (chan->load_state == S3C2410_DMALOAD_1LOADED_1RUNNING) {
+ dmawarn("state is S3C2410_DMALOAD_1LOADED_1RUNNING\n");
+ }
+
+ /* it would seem sensible if we are the last buffer to not bother
+ * with the auto-reload bit, so that the DMA engine will not try
+ * and load another transfer after this one has finished...
+ */
+ if (chan->load_state == S3C2410_DMALOAD_NONE) {
+ pr_debug("load_state is none, checking for noreload (next=%p)\n",
+ buf->next);
+ reload = (buf->next == NULL) ? S3C2410_DCON_NORELOAD : 0;
+ } else {
+ //pr_debug("load_state is %d => autoreload\n", chan->load_state);
+ reload = S3C2410_DCON_AUTORELOAD;
+ }
+
+ if ((buf->data & 0xf0000000) != 0x30000000) {
+ dmawarn("dmaload: buffer is %p\n", (void *)buf->data);
+ }
+
+ writel(buf->data, chan->addr_reg);
+
+ dma_wrreg(chan, S3C2410_DMA_DCON,
+ chan->dcon | reload | (buf->size/chan->xfer_unit));
+
+ chan->next = buf->next;
+
+ /* update the state of the channel */
+
+ switch (chan->load_state) {
+ case S3C2410_DMALOAD_NONE:
+ chan->load_state = S3C2410_DMALOAD_1LOADED;
+ break;
+
+ case S3C2410_DMALOAD_1RUNNING:
+ chan->load_state = S3C2410_DMALOAD_1LOADED_1RUNNING;
+ break;
+
+ default:
+ dmawarn("dmaload: unknown state %d in loadbuffer\n",
+ chan->load_state);
+ break;
+ }
+
+ return 0;
+}
+
+/* s3c2410_dma_call_op
+ *
+ * small routine to call the op routine with the given op if it has been
+ * registered
+*/
+
+static void
+s3c2410_dma_call_op(struct s3c2410_dma_chan *chan, enum s3c2410_chan_op op)
+{
+ if (chan->op_fn != NULL) {
+ (chan->op_fn)(chan, op);
+ }
+}
+
+/* s3c2410_dma_buffdone
+ *
+ * small wrapper to check if callback routine needs to be called, and
+ * if so, call it
+*/
+
+static inline void
+s3c2410_dma_buffdone(struct s3c2410_dma_chan *chan, struct s3c2410_dma_buf *buf,
+ enum s3c2410_dma_buffresult result)
+{
+#if 0
+ pr_debug("callback_fn=%p, buf=%p, id=%p, size=%d, result=%d\n",
+ chan->callback_fn, buf, buf->id, buf->size, result);
+#endif
+
+ if (chan->callback_fn != NULL) {
+ (chan->callback_fn)(chan, buf->id, buf->size, result);
+ }
+}
+
+/* s3c2410_dma_start
+ *
+ * start a dma channel going
+*/
+
+static int s3c2410_dma_start(struct s3c2410_dma_chan *chan)
+{
+ unsigned long tmp;
+ unsigned long flags;
+
+ pr_debug("s3c2410_start_dma: channel=%d\n", chan->number);
+
+ local_irq_save(flags);
+
+ if (chan->state == S3C2410_DMA_RUNNING) {
+ pr_debug("s3c2410_start_dma: already running (%d)\n", chan->state);
+ local_irq_restore(flags);
+ return 0;
+ }
+
+ chan->state = S3C2410_DMA_RUNNING;
+
+ /* check whether there is anything to load, and if not, see
+ * if we can find anything to load
+ */
+
+ if (chan->load_state == S3C2410_DMALOAD_NONE) {
+ if (chan->next == NULL) {
+ printk(KERN_ERR "dma%d: channel has nothing loaded\n",
+ chan->number);
+ chan->state = S3C2410_DMA_IDLE;
+ local_irq_restore(flags);
+ return -EINVAL;
+ }
+
+ s3c2410_dma_loadbuffer(chan, chan->next);
+ }
+
+ dbg_showchan(chan);
+
+ /* enable the channel */
+
+ if (!chan->irq_enabled) {
+ enable_irq(chan->irq);
+ chan->irq_enabled = 1;
+ }
+
+ /* start the channel going */
+
+ tmp = dma_rdreg(chan, S3C2410_DMA_DMASKTRIG);
+ tmp &= ~S3C2410_DMASKTRIG_STOP;
+ tmp |= S3C2410_DMASKTRIG_ON;
+ dma_wrreg(chan, S3C2410_DMA_DMASKTRIG, tmp);
+
+ pr_debug("dma%d: %08lx to DMASKTRIG\n", chan->number, tmp);
+
+#if 0
+ /* the dma buffer loads should take care of clearing the AUTO
+ * reloading feature */
+ tmp = dma_rdreg(chan, S3C2410_DMA_DCON);
+ tmp &= ~S3C2410_DCON_NORELOAD;
+ dma_wrreg(chan, S3C2410_DMA_DCON, tmp);
+#endif
+
+ s3c2410_dma_call_op(chan, S3C2410_DMAOP_START);
+
+ dbg_showchan(chan);
+
+ /* if we've only loaded one buffer onto the channel, then chec
+ * to see if we have another, and if so, try and load it so when
+ * the first buffer is finished, the new one will be loaded onto
+ * the channel */
+
+ if (chan->next != NULL) {
+ if (chan->load_state == S3C2410_DMALOAD_1LOADED) {
+
+ if (s3c2410_dma_waitforload(chan, __LINE__) == 0) {
+ pr_debug("%s: buff not yet loaded, no more todo\n",
+ __func__);
+ } else {
+ chan->load_state = S3C2410_DMALOAD_1RUNNING;
+ s3c2410_dma_loadbuffer(chan, chan->next);
+ }
+
+ } else if (chan->load_state == S3C2410_DMALOAD_1RUNNING) {
+ s3c2410_dma_loadbuffer(chan, chan->next);
+ }
+ }
+
+
+ local_irq_restore(flags);
+
+ return 0;
+}
+
+/* s3c2410_dma_canload
+ *
+ * work out if we can queue another buffer into the DMA engine
+*/
+
+static int
+s3c2410_dma_canload(struct s3c2410_dma_chan *chan)
+{
+ if (chan->load_state == S3C2410_DMALOAD_NONE ||
+ chan->load_state == S3C2410_DMALOAD_1RUNNING)
+ return 1;
+
+ return 0;
+}
+
+/* s3c2410_dma_enqueue
+ *
+ * queue an given buffer for dma transfer.
+ *
+ * id the device driver's id information for this buffer
+ * data the physical address of the buffer data
+ * size the size of the buffer in bytes
+ *
+ * If the channel is not running, then the flag S3C2410_DMAF_AUTOSTART
+ * is checked, and if set, the channel is started. If this flag isn't set,
+ * then an error will be returned.
+ *
+ * It is possible to queue more than one DMA buffer onto a channel at
+ * once, and the code will deal with the re-loading of the next buffer
+ * when necessary.
+*/
+
+int s3c2410_dma_enqueue(enum dma_ch channel, void *id,
+ dma_addr_t data, int size)
+{
+ struct s3c2410_dma_chan *chan = s3c_dma_lookup_channel(channel);
+ struct s3c2410_dma_buf *buf;
+ unsigned long flags;
+
+ if (chan == NULL)
+ return -EINVAL;
+
+ pr_debug("%s: id=%p, data=%08x, size=%d\n",
+ __func__, id, (unsigned int)data, size);
+
+ buf = kmem_cache_alloc(dma_kmem, GFP_ATOMIC);
+ if (buf == NULL) {
+ pr_debug("%s: out of memory (%ld alloc)\n",
+ __func__, (long)sizeof(*buf));
+ return -ENOMEM;
+ }
+
+ //pr_debug("%s: new buffer %p\n", __func__, buf);
+ //dbg_showchan(chan);
+
+ buf->next = NULL;
+ buf->data = buf->ptr = data;
+ buf->size = size;
+ buf->id = id;
+ buf->magic = BUF_MAGIC;
+
+ local_irq_save(flags);
+
+ if (chan->curr == NULL) {
+ /* we've got nothing loaded... */
+ pr_debug("%s: buffer %p queued onto empty channel\n",
+ __func__, buf);
+
+ chan->curr = buf;
+ chan->end = buf;
+ chan->next = NULL;
+ } else {
+ pr_debug("dma%d: %s: buffer %p queued onto non-empty channel\n",
+ chan->number, __func__, buf);
+
+ if (chan->end == NULL) {
+ pr_debug("dma%d: %s: %p not empty, and chan->end==NULL?\n",
+ chan->number, __func__, chan);
+ } else {
+ chan->end->next = buf;
+ chan->end = buf;
+ }
+ }
+
+ /* if necessary, update the next buffer field */
+ if (chan->next == NULL)
+ chan->next = buf;
+
+ /* check to see if we can load a buffer */
+ if (chan->state == S3C2410_DMA_RUNNING) {
+ if (chan->load_state == S3C2410_DMALOAD_1LOADED && 1) {
+ if (s3c2410_dma_waitforload(chan, __LINE__) == 0) {
+ printk(KERN_ERR "dma%d: loadbuffer:"
+ "timeout loading buffer\n",
+ chan->number);
+ dbg_showchan(chan);
+ local_irq_restore(flags);
+ return -EINVAL;
+ }
+ }
+
+ while (s3c2410_dma_canload(chan) && chan->next != NULL) {
+ s3c2410_dma_loadbuffer(chan, chan->next);
+ }
+ } else if (chan->state == S3C2410_DMA_IDLE) {
+ if (chan->flags & S3C2410_DMAF_AUTOSTART) {
+ s3c2410_dma_ctrl(chan->number | DMACH_LOW_LEVEL,
+ S3C2410_DMAOP_START);
+ }
+ }
+
+ local_irq_restore(flags);
+ return 0;
+}
+
+EXPORT_SYMBOL(s3c2410_dma_enqueue);
+
+static inline void
+s3c2410_dma_freebuf(struct s3c2410_dma_buf *buf)
+{
+ int magicok = (buf->magic == BUF_MAGIC);
+
+ buf->magic = -1;
+
+ if (magicok) {
+ kmem_cache_free(dma_kmem, buf);
+ } else {
+ printk("s3c2410_dma_freebuf: buff %p with bad magic\n", buf);
+ }
+}
+
+/* s3c2410_dma_lastxfer
+ *
+ * called when the system is out of buffers, to ensure that the channel
+ * is prepared for shutdown.
+*/
+
+static inline void
+s3c2410_dma_lastxfer(struct s3c2410_dma_chan *chan)
+{
+#if 0
+ pr_debug("dma%d: s3c2410_dma_lastxfer: load_state %d\n",
+ chan->number, chan->load_state);
+#endif
+
+ switch (chan->load_state) {
+ case S3C2410_DMALOAD_NONE:
+ break;
+
+ case S3C2410_DMALOAD_1LOADED:
+ if (s3c2410_dma_waitforload(chan, __LINE__) == 0) {
+ /* flag error? */
+ printk(KERN_ERR "dma%d: timeout waiting for load (%s)\n",
+ chan->number, __func__);
+ return;
+ }
+ break;
+
+ case S3C2410_DMALOAD_1LOADED_1RUNNING:
+ /* I believe in this case we do not have anything to do
+ * until the next buffer comes along, and we turn off the
+ * reload */
+ return;
+
+ default:
+ pr_debug("dma%d: lastxfer: unhandled load_state %d with no next\n",
+ chan->number, chan->load_state);
+ return;
+
+ }
+
+ /* hopefully this'll shut the damned thing up after the transfer... */
+ dma_wrreg(chan, S3C2410_DMA_DCON, chan->dcon | S3C2410_DCON_NORELOAD);
+}
+
+
+#define dmadbg2(x...)
+
+static irqreturn_t
+s3c2410_dma_irq(int irq, void *devpw)
+{
+ struct s3c2410_dma_chan *chan = (struct s3c2410_dma_chan *)devpw;
+ struct s3c2410_dma_buf *buf;
+
+ buf = chan->curr;
+
+ dbg_showchan(chan);
+
+ /* modify the channel state */
+
+ switch (chan->load_state) {
+ case S3C2410_DMALOAD_1RUNNING:
+ /* TODO - if we are running only one buffer, we probably
+ * want to reload here, and then worry about the buffer
+ * callback */
+
+ chan->load_state = S3C2410_DMALOAD_NONE;
+ break;
+
+ case S3C2410_DMALOAD_1LOADED:
+ /* iirc, we should go back to NONE loaded here, we
+ * had a buffer, and it was never verified as being
+ * loaded.
+ */
+
+ chan->load_state = S3C2410_DMALOAD_NONE;
+ break;
+
+ case S3C2410_DMALOAD_1LOADED_1RUNNING:
+ /* we'll worry about checking to see if another buffer is
+ * ready after we've called back the owner. This should
+ * ensure we do not wait around too long for the DMA
+ * engine to start the next transfer
+ */
+
+ chan->load_state = S3C2410_DMALOAD_1LOADED;
+ break;
+
+ case S3C2410_DMALOAD_NONE:
+ printk(KERN_ERR "dma%d: IRQ with no loaded buffer?\n",
+ chan->number);
+ break;
+
+ default:
+ printk(KERN_ERR "dma%d: IRQ in invalid load_state %d\n",
+ chan->number, chan->load_state);
+ break;
+ }
+
+ if (buf != NULL) {
+ /* update the chain to make sure that if we load any more
+ * buffers when we call the callback function, things should
+ * work properly */
+
+ chan->curr = buf->next;
+ buf->next = NULL;
+
+ if (buf->magic != BUF_MAGIC) {
+ printk(KERN_ERR "dma%d: %s: buf %p incorrect magic\n",
+ chan->number, __func__, buf);
+ return IRQ_HANDLED;
+ }
+
+ s3c2410_dma_buffdone(chan, buf, S3C2410_RES_OK);
+
+ /* free resouces */
+ s3c2410_dma_freebuf(buf);
+ } else {
+ }
+
+ /* only reload if the channel is still running... our buffer done
+ * routine may have altered the state by requesting the dma channel
+ * to stop or shutdown... */
+
+ /* todo: check that when the channel is shut-down from inside this
+ * function, we cope with unsetting reload, etc */
+
+ if (chan->next != NULL && chan->state != S3C2410_DMA_IDLE) {
+ unsigned long flags;
+
+ switch (chan->load_state) {
+ case S3C2410_DMALOAD_1RUNNING:
+ /* don't need to do anything for this state */
+ break;
+
+ case S3C2410_DMALOAD_NONE:
+ /* can load buffer immediately */
+ break;
+
+ case S3C2410_DMALOAD_1LOADED:
+ if (s3c2410_dma_waitforload(chan, __LINE__) == 0) {
+ /* flag error? */
+ printk(KERN_ERR "dma%d: timeout waiting for load (%s)\n",
+ chan->number, __func__);
+ return IRQ_HANDLED;
+ }
+
+ break;
+
+ case S3C2410_DMALOAD_1LOADED_1RUNNING:
+ goto no_load;
+
+ default:
+ printk(KERN_ERR "dma%d: unknown load_state in irq, %d\n",
+ chan->number, chan->load_state);
+ return IRQ_HANDLED;
+ }
+
+ local_irq_save(flags);
+ s3c2410_dma_loadbuffer(chan, chan->next);
+ local_irq_restore(flags);
+ } else {
+ s3c2410_dma_lastxfer(chan);
+
+ /* see if we can stop this channel.. */
+ if (chan->load_state == S3C2410_DMALOAD_NONE) {
+ pr_debug("dma%d: end of transfer, stopping channel (%ld)\n",
+ chan->number, jiffies);
+ s3c2410_dma_ctrl(chan->number | DMACH_LOW_LEVEL,
+ S3C2410_DMAOP_STOP);
+ }
+ }
+
+ no_load:
+ return IRQ_HANDLED;
+}
+
+static struct s3c2410_dma_chan *s3c2410_dma_map_channel(int channel);
+
+/* s3c2410_request_dma
+ *
+ * get control of an dma channel
+*/
+
+int s3c2410_dma_request(enum dma_ch channel,
+ struct s3c2410_dma_client *client,
+ void *dev)
+{
+ struct s3c2410_dma_chan *chan;
+ unsigned long flags;
+ int err;
+
+ pr_debug("dma%d: s3c2410_request_dma: client=%s, dev=%p\n",
+ channel, client->name, dev);
+
+ local_irq_save(flags);
+
+ chan = s3c2410_dma_map_channel(channel);
+ if (chan == NULL) {
+ local_irq_restore(flags);
+ return -EBUSY;
+ }
+
+ dbg_showchan(chan);
+
+ chan->client = client;
+ chan->in_use = 1;
+
+ if (!chan->irq_claimed) {
+ pr_debug("dma%d: %s : requesting irq %d\n",
+ channel, __func__, chan->irq);
+
+ chan->irq_claimed = 1;
+ local_irq_restore(flags);
+
+ err = request_irq(chan->irq, s3c2410_dma_irq, IRQF_DISABLED,
+ client->name, (void *)chan);
+
+ local_irq_save(flags);
+
+ if (err) {
+ chan->in_use = 0;
+ chan->irq_claimed = 0;
+ local_irq_restore(flags);
+
+ printk(KERN_ERR "%s: cannot get IRQ %d for DMA %d\n",
+ client->name, chan->irq, chan->number);
+ return err;
+ }
+
+ chan->irq_enabled = 1;
+ }
+
+ local_irq_restore(flags);
+
+ /* need to setup */
+
+ pr_debug("%s: channel initialised, %p\n", __func__, chan);
+
+ return chan->number | DMACH_LOW_LEVEL;
+}
+
+EXPORT_SYMBOL(s3c2410_dma_request);
+
+/* s3c2410_dma_free
+ *
+ * release the given channel back to the system, will stop and flush
+ * any outstanding transfers, and ensure the channel is ready for the
+ * next claimant.
+ *
+ * Note, although a warning is currently printed if the freeing client
+ * info is not the same as the registrant's client info, the free is still
+ * allowed to go through.
+*/
+
+int s3c2410_dma_free(enum dma_ch channel, struct s3c2410_dma_client *client)
+{
+ struct s3c2410_dma_chan *chan = s3c_dma_lookup_channel(channel);
+ unsigned long flags;
+
+ if (chan == NULL)
+ return -EINVAL;
+
+ local_irq_save(flags);
+
+ if (chan->client != client) {
+ printk(KERN_WARNING "dma%d: possible free from different client (channel %p, passed %p)\n",
+ channel, chan->client, client);
+ }
+
+ /* sort out stopping and freeing the channel */
+
+ if (chan->state != S3C2410_DMA_IDLE) {
+ pr_debug("%s: need to stop dma channel %p\n",
+ __func__, chan);
+
+ /* possibly flush the channel */
+ s3c2410_dma_ctrl(channel, S3C2410_DMAOP_STOP);
+ }
+
+ chan->client = NULL;
+ chan->in_use = 0;
+
+ if (chan->irq_claimed)
+ free_irq(chan->irq, (void *)chan);
+
+ chan->irq_claimed = 0;
+
+ if (!(channel & DMACH_LOW_LEVEL))
+ s3c_dma_chan_map[channel] = NULL;
+
+ local_irq_restore(flags);
+
+ return 0;
+}
+
+EXPORT_SYMBOL(s3c2410_dma_free);
+
+static int s3c2410_dma_dostop(struct s3c2410_dma_chan *chan)
+{
+ unsigned long flags;
+ unsigned long tmp;
+
+ pr_debug("%s:\n", __func__);
+
+ dbg_showchan(chan);
+
+ local_irq_save(flags);
+
+ s3c2410_dma_call_op(chan, S3C2410_DMAOP_STOP);
+
+ tmp = dma_rdreg(chan, S3C2410_DMA_DMASKTRIG);
+ tmp |= S3C2410_DMASKTRIG_STOP;
+ //tmp &= ~S3C2410_DMASKTRIG_ON;
+ dma_wrreg(chan, S3C2410_DMA_DMASKTRIG, tmp);
+
+#if 0
+ /* should also clear interrupts, according to WinCE BSP */
+ tmp = dma_rdreg(chan, S3C2410_DMA_DCON);
+ tmp |= S3C2410_DCON_NORELOAD;
+ dma_wrreg(chan, S3C2410_DMA_DCON, tmp);
+#endif
+
+ /* should stop do this, or should we wait for flush? */
+ chan->state = S3C2410_DMA_IDLE;
+ chan->load_state = S3C2410_DMALOAD_NONE;
+
+ local_irq_restore(flags);
+
+ return 0;
+}
+
+static void s3c2410_dma_waitforstop(struct s3c2410_dma_chan *chan)
+{
+ unsigned long tmp;
+ unsigned int timeout = 0x10000;
+
+ while (timeout-- > 0) {
+ tmp = dma_rdreg(chan, S3C2410_DMA_DMASKTRIG);
+
+ if (!(tmp & S3C2410_DMASKTRIG_ON))
+ return;
+ }
+
+ pr_debug("dma%d: failed to stop?\n", chan->number);
+}
+
+
+/* s3c2410_dma_flush
+ *
+ * stop the channel, and remove all current and pending transfers
+*/
+
+static int s3c2410_dma_flush(struct s3c2410_dma_chan *chan)
+{
+ struct s3c2410_dma_buf *buf, *next;
+ unsigned long flags;
+
+ pr_debug("%s: chan %p (%d)\n", __func__, chan, chan->number);
+
+ dbg_showchan(chan);
+
+ local_irq_save(flags);
+
+ if (chan->state != S3C2410_DMA_IDLE) {
+ pr_debug("%s: stopping channel...\n", __func__ );
+ s3c2410_dma_ctrl(chan->number, S3C2410_DMAOP_STOP);
+ }
+
+ buf = chan->curr;
+ if (buf == NULL)
+ buf = chan->next;
+
+ chan->curr = chan->next = chan->end = NULL;
+
+ if (buf != NULL) {
+ for ( ; buf != NULL; buf = next) {
+ next = buf->next;
+
+ pr_debug("%s: free buffer %p, next %p\n",
+ __func__, buf, buf->next);
+
+ s3c2410_dma_buffdone(chan, buf, S3C2410_RES_ABORT);
+ s3c2410_dma_freebuf(buf);
+ }
+ }
+
+ dbg_showregs(chan);
+
+ s3c2410_dma_waitforstop(chan);
+
+#if 0
+ /* should also clear interrupts, according to WinCE BSP */
+ {
+ unsigned long tmp;
+
+ tmp = dma_rdreg(chan, S3C2410_DMA_DCON);
+ tmp |= S3C2410_DCON_NORELOAD;
+ dma_wrreg(chan, S3C2410_DMA_DCON, tmp);
+ }
+#endif
+
+ dbg_showregs(chan);
+
+ local_irq_restore(flags);
+
+ return 0;
+}
+
+static int s3c2410_dma_started(struct s3c2410_dma_chan *chan)
+{
+ unsigned long flags;
+
+ local_irq_save(flags);
+
+ dbg_showchan(chan);
+
+ /* if we've only loaded one buffer onto the channel, then chec
+ * to see if we have another, and if so, try and load it so when
+ * the first buffer is finished, the new one will be loaded onto
+ * the channel */
+
+ if (chan->next != NULL) {
+ if (chan->load_state == S3C2410_DMALOAD_1LOADED) {
+
+ if (s3c2410_dma_waitforload(chan, __LINE__) == 0) {
+ pr_debug("%s: buff not yet loaded, no more todo\n",
+ __func__);
+ } else {
+ chan->load_state = S3C2410_DMALOAD_1RUNNING;
+ s3c2410_dma_loadbuffer(chan, chan->next);
+ }
+
+ } else if (chan->load_state == S3C2410_DMALOAD_1RUNNING) {
+ s3c2410_dma_loadbuffer(chan, chan->next);
+ }
+ }
+
+
+ local_irq_restore(flags);
+
+ return 0;
+
+}
+
+int
+s3c2410_dma_ctrl(enum dma_ch channel, enum s3c2410_chan_op op)
+{
+ struct s3c2410_dma_chan *chan = s3c_dma_lookup_channel(channel);
+
+ if (chan == NULL)
+ return -EINVAL;
+
+ switch (op) {
+ case S3C2410_DMAOP_START:
+ return s3c2410_dma_start(chan);
+
+ case S3C2410_DMAOP_STOP:
+ return s3c2410_dma_dostop(chan);
+
+ case S3C2410_DMAOP_PAUSE:
+ case S3C2410_DMAOP_RESUME:
+ return -ENOENT;
+
+ case S3C2410_DMAOP_FLUSH:
+ return s3c2410_dma_flush(chan);
+
+ case S3C2410_DMAOP_STARTED:
+ return s3c2410_dma_started(chan);
+
+ case S3C2410_DMAOP_TIMEOUT:
+ return 0;
+
+ }
+
+ return -ENOENT; /* unknown, don't bother */
+}
+
+EXPORT_SYMBOL(s3c2410_dma_ctrl);
+
+/* DMA configuration for each channel
+ *
+ * DISRCC -> source of the DMA (AHB,APB)
+ * DISRC -> source address of the DMA
+ * DIDSTC -> destination of the DMA (AHB,APD)
+ * DIDST -> destination address of the DMA
+*/
+
+/* s3c2410_dma_config
+ *
+ * xfersize: size of unit in bytes (1,2,4)
+*/
+
+int s3c2410_dma_config(enum dma_ch channel,
+ int xferunit)
+{
+ struct s3c2410_dma_chan *chan = s3c_dma_lookup_channel(channel);
+ unsigned int dcon;
+
+ pr_debug("%s: chan=%d, xfer_unit=%d\n", __func__, channel, xferunit);
+
+ if (chan == NULL)
+ return -EINVAL;
+
+ dcon = chan->dcon & dma_sel.dcon_mask;
+ pr_debug("%s: dcon is %08x\n", __func__, dcon);
+
+ switch (chan->req_ch) {
+ case DMACH_I2S_IN:
+ case DMACH_I2S_OUT:
+ case DMACH_PCM_IN:
+ case DMACH_PCM_OUT:
+ case DMACH_MIC_IN:
+ default:
+ dcon |= S3C2410_DCON_HANDSHAKE;
+ dcon |= S3C2410_DCON_SYNC_PCLK;
+ break;
+
+ case DMACH_SDI:
+ /* note, ensure if need HANDSHAKE or not */
+ dcon |= S3C2410_DCON_SYNC_PCLK;
+ break;
+
+ case DMACH_XD0:
+ case DMACH_XD1:
+ dcon |= S3C2410_DCON_HANDSHAKE;
+ dcon |= S3C2410_DCON_SYNC_HCLK;
+ break;
+ }
+
+ switch (xferunit) {
+ case 1:
+ dcon |= S3C2410_DCON_BYTE;
+ break;
+
+ case 2:
+ dcon |= S3C2410_DCON_HALFWORD;
+ break;
+
+ case 4:
+ dcon |= S3C2410_DCON_WORD;
+ break;
+
+ default:
+ pr_debug("%s: bad transfer size %d\n", __func__, xferunit);
+ return -EINVAL;
+ }
+
+ dcon |= S3C2410_DCON_HWTRIG;
+ dcon |= S3C2410_DCON_INTREQ;
+
+ pr_debug("%s: dcon now %08x\n", __func__, dcon);
+
+ chan->dcon = dcon;
+ chan->xfer_unit = xferunit;
+
+ return 0;
+}
+
+EXPORT_SYMBOL(s3c2410_dma_config);
+
+
+/* s3c2410_dma_devconfig
+ *
+ * configure the dma source/destination hardware type and address
+ *
+ * source: DMA_FROM_DEVICE: source is hardware
+ * DMA_TO_DEVICE: source is memory
+ *
+ * devaddr: physical address of the source
+*/
+
+int s3c2410_dma_devconfig(enum dma_ch channel,
+ enum dma_data_direction source,
+ unsigned long devaddr)
+{
+ struct s3c2410_dma_chan *chan = s3c_dma_lookup_channel(channel);
+ unsigned int hwcfg;
+
+ if (chan == NULL)
+ return -EINVAL;
+
+ pr_debug("%s: source=%d, devaddr=%08lx\n",
+ __func__, (int)source, devaddr);
+
+ chan->source = source;
+ chan->dev_addr = devaddr;
+
+ switch (chan->req_ch) {
+ case DMACH_XD0:
+ case DMACH_XD1:
+ hwcfg = 0; /* AHB */
+ break;
+
+ default:
+ hwcfg = S3C2410_DISRCC_APB;
+ }
+
+ /* always assume our peripheral desintation is a fixed
+ * address in memory. */
+ hwcfg |= S3C2410_DISRCC_INC;
+
+ switch (source) {
+ case DMA_FROM_DEVICE:
+ /* source is hardware */
+ pr_debug("%s: hw source, devaddr=%08lx, hwcfg=%d\n",
+ __func__, devaddr, hwcfg);
+ dma_wrreg(chan, S3C2410_DMA_DISRCC, hwcfg & 3);
+ dma_wrreg(chan, S3C2410_DMA_DISRC, devaddr);
+ dma_wrreg(chan, S3C2410_DMA_DIDSTC, (0<<1) | (0<<0));
+
+ chan->addr_reg = dma_regaddr(chan, S3C2410_DMA_DIDST);
+ break;
+
+ case DMA_TO_DEVICE:
+ /* source is memory */
+ pr_debug("%s: mem source, devaddr=%08lx, hwcfg=%d\n",
+ __func__, devaddr, hwcfg);
+ dma_wrreg(chan, S3C2410_DMA_DISRCC, (0<<1) | (0<<0));
+ dma_wrreg(chan, S3C2410_DMA_DIDST, devaddr);
+ dma_wrreg(chan, S3C2410_DMA_DIDSTC, hwcfg & 3);
+
+ chan->addr_reg = dma_regaddr(chan, S3C2410_DMA_DISRC);
+ break;
+
+ default:
+ printk(KERN_ERR "dma%d: invalid source type (%d)\n",
+ channel, source);
+
+ return -EINVAL;
+ }
+
+ if (dma_sel.direction != NULL)
+ (dma_sel.direction)(chan, chan->map, source);
+
+ return 0;
+}
+
+EXPORT_SYMBOL(s3c2410_dma_devconfig);
+
+/* s3c2410_dma_getposition
+ *
+ * returns the current transfer points for the dma source and destination
+*/
+
+int s3c2410_dma_getposition(enum dma_ch channel, dma_addr_t *src, dma_addr_t *dst)
+{
+ struct s3c2410_dma_chan *chan = s3c_dma_lookup_channel(channel);
+
+ if (chan == NULL)
+ return -EINVAL;
+
+ if (src != NULL)
+ *src = dma_rdreg(chan, S3C2410_DMA_DCSRC);
+
+ if (dst != NULL)
+ *dst = dma_rdreg(chan, S3C2410_DMA_DCDST);
+
+ return 0;
+}
+
+EXPORT_SYMBOL(s3c2410_dma_getposition);
+
+/* system core operations */
+
+#ifdef CONFIG_PM
+
+static void s3c2410_dma_suspend_chan(struct s3c2410_dma_chan *cp)
+{
+ printk(KERN_DEBUG "suspending dma channel %d\n", cp->number);
+
+ if (dma_rdreg(cp, S3C2410_DMA_DMASKTRIG) & S3C2410_DMASKTRIG_ON) {
+ /* the dma channel is still working, which is probably
+ * a bad thing to do over suspend/resume. We stop the
+ * channel and assume that the client is either going to
+ * retry after resume, or that it is broken.
+ */
+
+ printk(KERN_INFO "dma: stopping channel %d due to suspend\n",
+ cp->number);
+
+ s3c2410_dma_dostop(cp);
+ }
+}
+
+static int s3c2410_dma_suspend(void)
+{
+ struct s3c2410_dma_chan *cp = s3c2410_chans;
+ int channel;
+
+ for (channel = 0; channel < dma_channels; cp++, channel++)
+ s3c2410_dma_suspend_chan(cp);
+
+ return 0;
+}
+
+static void s3c2410_dma_resume_chan(struct s3c2410_dma_chan *cp)
+{
+ unsigned int no = cp->number | DMACH_LOW_LEVEL;
+
+ /* restore channel's hardware configuration */
+
+ if (!cp->in_use)
+ return;
+
+ printk(KERN_INFO "dma%d: restoring configuration\n", cp->number);
+
+ s3c2410_dma_config(no, cp->xfer_unit);
+ s3c2410_dma_devconfig(no, cp->source, cp->dev_addr);
+
+ /* re-select the dma source for this channel */
+
+ if (cp->map != NULL)
+ dma_sel.select(cp, cp->map);
+}
+
+static void s3c2410_dma_resume(void)
+{
+ struct s3c2410_dma_chan *cp = s3c2410_chans + dma_channels - 1;
+ int channel;
+
+ for (channel = dma_channels - 1; channel >= 0; cp--, channel--)
+ s3c2410_dma_resume_chan(cp);
+}
+
+#else
+#define s3c2410_dma_suspend NULL
+#define s3c2410_dma_resume NULL
+#endif /* CONFIG_PM */
+
+struct syscore_ops dma_syscore_ops = {
+ .suspend = s3c2410_dma_suspend,
+ .resume = s3c2410_dma_resume,
+};
+
+/* kmem cache implementation */
+
+static void s3c2410_dma_cache_ctor(void *p)
+{
+ memset(p, 0, sizeof(struct s3c2410_dma_buf));
+}
+
+/* initialisation code */
+
+static int __init s3c24xx_dma_syscore_init(void)
+{
+ register_syscore_ops(&dma_syscore_ops);
+
+ return 0;
+}
+
+late_initcall(s3c24xx_dma_syscore_init);
+
+int __init s3c24xx_dma_init(unsigned int channels, unsigned int irq,
+ unsigned int stride)
+{
+ struct s3c2410_dma_chan *cp;
+ int channel;
+ int ret;
+
+ printk("S3C24XX DMA Driver, Copyright 2003-2006 Simtec Electronics\n");
+
+ dma_channels = channels;
+
+ dma_base = ioremap(S3C24XX_PA_DMA, stride * channels);
+ if (dma_base == NULL) {
+ printk(KERN_ERR "dma failed to remap register block\n");
+ return -ENOMEM;
+ }
+
+ dma_kmem = kmem_cache_create("dma_desc",
+ sizeof(struct s3c2410_dma_buf), 0,
+ SLAB_HWCACHE_ALIGN,
+ s3c2410_dma_cache_ctor);
+
+ if (dma_kmem == NULL) {
+ printk(KERN_ERR "dma failed to make kmem cache\n");
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ for (channel = 0; channel < channels; channel++) {
+ cp = &s3c2410_chans[channel];
+
+ memset(cp, 0, sizeof(struct s3c2410_dma_chan));
+
+ /* dma channel irqs are in order.. */
+ cp->number = channel;
+ cp->irq = channel + irq;
+ cp->regs = dma_base + (channel * stride);
+
+ /* point current stats somewhere */
+ cp->stats = &cp->stats_store;
+ cp->stats_store.timeout_shortest = LONG_MAX;
+
+ /* basic channel configuration */
+
+ cp->load_timeout = 1<<18;
+
+ printk("DMA channel %d at %p, irq %d\n",
+ cp->number, cp->regs, cp->irq);
+ }
+
+ return 0;
+
+ err:
+ kmem_cache_destroy(dma_kmem);
+ iounmap(dma_base);
+ dma_base = NULL;
+ return ret;
+}
+
+int __init s3c2410_dma_init(void)
+{
+ return s3c24xx_dma_init(4, IRQ_DMA0, 0x40);
+}
+
+static inline int is_channel_valid(unsigned int channel)
+{
+ return (channel & DMA_CH_VALID);
+}
+
+static struct s3c24xx_dma_order *dma_order;
+
+
+/* s3c2410_dma_map_channel()
+ *
+ * turn the virtual channel number into a real, and un-used hardware
+ * channel.
+ *
+ * first, try the dma ordering given to us by either the relevant
+ * dma code, or the board. Then just find the first usable free
+ * channel
+*/
+
+static struct s3c2410_dma_chan *s3c2410_dma_map_channel(int channel)
+{
+ struct s3c24xx_dma_order_ch *ord = NULL;
+ struct s3c24xx_dma_map *ch_map;
+ struct s3c2410_dma_chan *dmach;
+ int ch;
+
+ if (dma_sel.map == NULL || channel > dma_sel.map_size)
+ return NULL;
+
+ ch_map = dma_sel.map + channel;
+
+ /* first, try the board mapping */
+
+ if (dma_order) {
+ ord = &dma_order->channels[channel];
+
+ for (ch = 0; ch < dma_channels; ch++) {
+ int tmp;
+ if (!is_channel_valid(ord->list[ch]))
+ continue;
+
+ tmp = ord->list[ch] & ~DMA_CH_VALID;
+ if (s3c2410_chans[tmp].in_use == 0) {
+ ch = tmp;
+ goto found;
+ }
+ }
+
+ if (ord->flags & DMA_CH_NEVER)
+ return NULL;
+ }
+
+ /* second, search the channel map for first free */
+
+ for (ch = 0; ch < dma_channels; ch++) {
+ if (!is_channel_valid(ch_map->channels[ch]))
+ continue;
+
+ if (s3c2410_chans[ch].in_use == 0) {
+ printk("mapped channel %d to %d\n", channel, ch);
+ break;
+ }
+ }
+
+ if (ch >= dma_channels)
+ return NULL;
+
+ /* update our channel mapping */
+
+ found:
+ dmach = &s3c2410_chans[ch];
+ dmach->map = ch_map;
+ dmach->req_ch = channel;
+ s3c_dma_chan_map[channel] = dmach;
+
+ /* select the channel */
+
+ (dma_sel.select)(dmach, ch_map);
+
+ return dmach;
+}
+
+static int s3c24xx_dma_check_entry(struct s3c24xx_dma_map *map, int ch)
+{
+ return 0;
+}
+
+int __init s3c24xx_dma_init_map(struct s3c24xx_dma_selection *sel)
+{
+ struct s3c24xx_dma_map *nmap;
+ size_t map_sz = sizeof(*nmap) * sel->map_size;
+ int ptr;
+
+ nmap = kmemdup(sel->map, map_sz, GFP_KERNEL);
+ if (nmap == NULL)
+ return -ENOMEM;
+
+ memcpy(&dma_sel, sel, sizeof(*sel));
+
+ dma_sel.map = nmap;
+
+ for (ptr = 0; ptr < sel->map_size; ptr++)
+ s3c24xx_dma_check_entry(nmap+ptr, ptr);
+
+ return 0;
+}
+
+int __init s3c24xx_dma_order_set(struct s3c24xx_dma_order *ord)
+{
+ struct s3c24xx_dma_order *nord = dma_order;
+
+ if (nord == NULL)
+ nord = kmalloc(sizeof(struct s3c24xx_dma_order), GFP_KERNEL);
+
+ if (nord == NULL) {
+ printk(KERN_ERR "no memory to store dma channel order\n");
+ return -ENOMEM;
+ }
+
+ dma_order = nord;
+ memcpy(nord, ord, sizeof(struct s3c24xx_dma_order));
+ return 0;
+}
diff --git a/arch/arm/mach-s3c24xx/iotiming-s3c2410.c b/arch/arm/mach-s3c24xx/iotiming-s3c2410.c
new file mode 100644
index 000000000000..48ccfcf715fa
--- /dev/null
+++ b/arch/arm/mach-s3c24xx/iotiming-s3c2410.c
@@ -0,0 +1,477 @@
+/*
+ * Copyright (c) 2006-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 <linux/slab.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;
+}
diff --git a/arch/arm/mach-s3c24xx/iotiming-s3c2412.c b/arch/arm/mach-s3c24xx/iotiming-s3c2412.c
new file mode 100644
index 000000000000..95273424d657
--- /dev/null
+++ b/arch/arm/mach-s3c24xx/iotiming-s3c2412.c
@@ -0,0 +1,285 @@
+/*
+ * 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/device.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/slab.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);
+}
diff --git a/arch/arm/mach-s3c24xx/irq.c b/arch/arm/mach-s3c24xx/irq.c
new file mode 100644
index 000000000000..cb9f5e011e73
--- /dev/null
+++ b/arch/arm/mach-s3c24xx/irq.c
@@ -0,0 +1,822 @@
+/*
+ * S3C24XX IRQ handling
+ *
+ * Copyright (c) 2003-2004 Simtec Electronics
+ * Ben Dooks <ben@simtec.co.uk>
+ * Copyright (c) 2012 Heiko Stuebner <heiko@sntech.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+*/
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/ioport.h>
+#include <linux/device.h>
+#include <linux/irqdomain.h>
+
+#include <asm/mach/irq.h>
+
+#include <mach/regs-irq.h>
+#include <mach/regs-gpio.h>
+
+#include <plat/cpu.h>
+#include <plat/regs-irqtype.h>
+#include <plat/pm.h>
+#include <plat/irq.h>
+
+#define S3C_IRQTYPE_NONE 0
+#define S3C_IRQTYPE_EINT 1
+#define S3C_IRQTYPE_EDGE 2
+#define S3C_IRQTYPE_LEVEL 3
+
+struct s3c_irq_data {
+ unsigned int type;
+ unsigned long parent_irq;
+
+ /* data gets filled during init */
+ struct s3c_irq_intc *intc;
+ unsigned long sub_bits;
+ struct s3c_irq_intc *sub_intc;
+};
+
+/*
+ * Sructure holding the controller data
+ * @reg_pending register holding pending irqs
+ * @reg_intpnd special register intpnd in main intc
+ * @reg_mask mask register
+ * @domain irq_domain of the controller
+ * @parent parent controller for ext and sub irqs
+ * @irqs irq-data, always s3c_irq_data[32]
+ */
+struct s3c_irq_intc {
+ void __iomem *reg_pending;
+ void __iomem *reg_intpnd;
+ void __iomem *reg_mask;
+ struct irq_domain *domain;
+ struct s3c_irq_intc *parent;
+ struct s3c_irq_data *irqs;
+};
+
+static void s3c_irq_mask(struct irq_data *data)
+{
+ struct s3c_irq_intc *intc = data->domain->host_data;
+ struct s3c_irq_intc *parent_intc = intc->parent;
+ struct s3c_irq_data *irq_data = &intc->irqs[data->hwirq];
+ struct s3c_irq_data *parent_data;
+ unsigned long mask;
+ unsigned int irqno;
+
+ mask = __raw_readl(intc->reg_mask);
+ mask |= (1UL << data->hwirq);
+ __raw_writel(mask, intc->reg_mask);
+
+ if (parent_intc && irq_data->parent_irq) {
+ parent_data = &parent_intc->irqs[irq_data->parent_irq];
+
+ /* check to see if we need to mask the parent IRQ */
+ if ((mask & parent_data->sub_bits) == parent_data->sub_bits) {
+ irqno = irq_find_mapping(parent_intc->domain,
+ irq_data->parent_irq);
+ s3c_irq_mask(irq_get_irq_data(irqno));
+ }
+ }
+}
+
+static void s3c_irq_unmask(struct irq_data *data)
+{
+ struct s3c_irq_intc *intc = data->domain->host_data;
+ struct s3c_irq_intc *parent_intc = intc->parent;
+ struct s3c_irq_data *irq_data = &intc->irqs[data->hwirq];
+ unsigned long mask;
+ unsigned int irqno;
+
+ mask = __raw_readl(intc->reg_mask);
+ mask &= ~(1UL << data->hwirq);
+ __raw_writel(mask, intc->reg_mask);
+
+ if (parent_intc && irq_data->parent_irq) {
+ irqno = irq_find_mapping(parent_intc->domain,
+ irq_data->parent_irq);
+ s3c_irq_unmask(irq_get_irq_data(irqno));
+ }
+}
+
+static inline void s3c_irq_ack(struct irq_data *data)
+{
+ struct s3c_irq_intc *intc = data->domain->host_data;
+ unsigned long bitval = 1UL << data->hwirq;
+
+ __raw_writel(bitval, intc->reg_pending);
+ if (intc->reg_intpnd)
+ __raw_writel(bitval, intc->reg_intpnd);
+}
+
+static int s3c_irqext_type_set(void __iomem *gpcon_reg,
+ void __iomem *extint_reg,
+ unsigned long gpcon_offset,
+ unsigned long extint_offset,
+ unsigned int type)
+{
+ unsigned long newvalue = 0, value;
+
+ /* Set the GPIO to external interrupt mode */
+ value = __raw_readl(gpcon_reg);
+ value = (value & ~(3 << gpcon_offset)) | (0x02 << gpcon_offset);
+ __raw_writel(value, gpcon_reg);
+
+ /* Set the external interrupt to pointed trigger type */
+ switch (type)
+ {
+ case IRQ_TYPE_NONE:
+ pr_warn("No edge setting!\n");
+ break;
+
+ case IRQ_TYPE_EDGE_RISING:
+ newvalue = S3C2410_EXTINT_RISEEDGE;
+ break;
+
+ case IRQ_TYPE_EDGE_FALLING:
+ newvalue = S3C2410_EXTINT_FALLEDGE;
+ break;
+
+ case IRQ_TYPE_EDGE_BOTH:
+ newvalue = S3C2410_EXTINT_BOTHEDGE;
+ break;
+
+ case IRQ_TYPE_LEVEL_LOW:
+ newvalue = S3C2410_EXTINT_LOWLEV;
+ break;
+
+ case IRQ_TYPE_LEVEL_HIGH:
+ newvalue = S3C2410_EXTINT_HILEV;
+ break;
+
+ default:
+ pr_err("No such irq type %d", type);
+ return -EINVAL;
+ }
+
+ value = __raw_readl(extint_reg);
+ value = (value & ~(7 << extint_offset)) | (newvalue << extint_offset);
+ __raw_writel(value, extint_reg);
+
+ return 0;
+}
+
+/* FIXME: make static when it's out of plat-samsung/irq.h */
+int s3c_irqext_type(struct irq_data *data, unsigned int type)
+{
+ void __iomem *extint_reg;
+ void __iomem *gpcon_reg;
+ unsigned long gpcon_offset, extint_offset;
+
+ if ((data->hwirq >= 4) && (data->hwirq <= 7)) {
+ gpcon_reg = S3C2410_GPFCON;
+ extint_reg = S3C24XX_EXTINT0;
+ gpcon_offset = (data->hwirq) * 2;
+ extint_offset = (data->hwirq) * 4;
+ } else if ((data->hwirq >= 8) && (data->hwirq <= 15)) {
+ gpcon_reg = S3C2410_GPGCON;
+ extint_reg = S3C24XX_EXTINT1;
+ gpcon_offset = (data->hwirq - 8) * 2;
+ extint_offset = (data->hwirq - 8) * 4;
+ } else if ((data->hwirq >= 16) && (data->hwirq <= 23)) {
+ gpcon_reg = S3C2410_GPGCON;
+ extint_reg = S3C24XX_EXTINT2;
+ gpcon_offset = (data->hwirq - 8) * 2;
+ extint_offset = (data->hwirq - 16) * 4;
+ } else {
+ return -EINVAL;
+ }
+
+ return s3c_irqext_type_set(gpcon_reg, extint_reg, gpcon_offset,
+ extint_offset, type);
+}
+
+static int s3c_irqext0_type(struct irq_data *data, unsigned int type)
+{
+ void __iomem *extint_reg;
+ void __iomem *gpcon_reg;
+ unsigned long gpcon_offset, extint_offset;
+
+ if ((data->hwirq >= 0) && (data->hwirq <= 3)) {
+ gpcon_reg = S3C2410_GPFCON;
+ extint_reg = S3C24XX_EXTINT0;
+ gpcon_offset = (data->hwirq) * 2;
+ extint_offset = (data->hwirq) * 4;
+ } else {
+ return -EINVAL;
+ }
+
+ return s3c_irqext_type_set(gpcon_reg, extint_reg, gpcon_offset,
+ extint_offset, type);
+}
+
+struct irq_chip s3c_irq_chip = {
+ .name = "s3c",
+ .irq_ack = s3c_irq_ack,
+ .irq_mask = s3c_irq_mask,
+ .irq_unmask = s3c_irq_unmask,
+ .irq_set_wake = s3c_irq_wake
+};
+
+struct irq_chip s3c_irq_level_chip = {
+ .name = "s3c-level",
+ .irq_mask = s3c_irq_mask,
+ .irq_unmask = s3c_irq_unmask,
+ .irq_ack = s3c_irq_ack,
+};
+
+static struct irq_chip s3c_irqext_chip = {
+ .name = "s3c-ext",
+ .irq_mask = s3c_irq_mask,
+ .irq_unmask = s3c_irq_unmask,
+ .irq_ack = s3c_irq_ack,
+ .irq_set_type = s3c_irqext_type,
+ .irq_set_wake = s3c_irqext_wake
+};
+
+static struct irq_chip s3c_irq_eint0t4 = {
+ .name = "s3c-ext0",
+ .irq_ack = s3c_irq_ack,
+ .irq_mask = s3c_irq_mask,
+ .irq_unmask = s3c_irq_unmask,
+ .irq_set_wake = s3c_irq_wake,
+ .irq_set_type = s3c_irqext0_type,
+};
+
+static void s3c_irq_demux(unsigned int irq, struct irq_desc *desc)
+{
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ struct s3c_irq_intc *intc = desc->irq_data.domain->host_data;
+ struct s3c_irq_data *irq_data = &intc->irqs[desc->irq_data.hwirq];
+ struct s3c_irq_intc *sub_intc = irq_data->sub_intc;
+ unsigned long src;
+ unsigned long msk;
+ unsigned int n;
+
+ chained_irq_enter(chip, desc);
+
+ src = __raw_readl(sub_intc->reg_pending);
+ msk = __raw_readl(sub_intc->reg_mask);
+
+ src &= ~msk;
+ src &= irq_data->sub_bits;
+
+ while (src) {
+ n = __ffs(src);
+ src &= ~(1 << n);
+ generic_handle_irq(irq_find_mapping(sub_intc->domain, n));
+ }
+
+ chained_irq_exit(chip, desc);
+}
+
+#ifdef CONFIG_FIQ
+/**
+ * s3c24xx_set_fiq - set the FIQ routing
+ * @irq: IRQ number to route to FIQ on processor.
+ * @on: Whether to route @irq to the FIQ, or to remove the FIQ routing.
+ *
+ * Change the state of the IRQ to FIQ routing depending on @irq and @on. If
+ * @on is true, the @irq is checked to see if it can be routed and the
+ * interrupt controller updated to route the IRQ. If @on is false, the FIQ
+ * routing is cleared, regardless of which @irq is specified.
+ */
+int s3c24xx_set_fiq(unsigned int irq, bool on)
+{
+ u32 intmod;
+ unsigned offs;
+
+ if (on) {
+ offs = irq - FIQ_START;
+ if (offs > 31)
+ return -EINVAL;
+
+ intmod = 1 << offs;
+ } else {
+ intmod = 0;
+ }
+
+ __raw_writel(intmod, S3C2410_INTMOD);
+ return 0;
+}
+
+EXPORT_SYMBOL_GPL(s3c24xx_set_fiq);
+#endif
+
+static int s3c24xx_irq_map(struct irq_domain *h, unsigned int virq,
+ irq_hw_number_t hw)
+{
+ struct s3c_irq_intc *intc = h->host_data;
+ struct s3c_irq_data *irq_data = &intc->irqs[hw];
+ struct s3c_irq_intc *parent_intc;
+ struct s3c_irq_data *parent_irq_data;
+ unsigned int irqno;
+
+ if (!intc) {
+ pr_err("irq-s3c24xx: no controller found for hwirq %lu\n", hw);
+ return -EINVAL;
+ }
+
+ if (!irq_data) {
+ pr_err("irq-s3c24xx: no irq data found for hwirq %lu\n", hw);
+ return -EINVAL;
+ }
+
+ /* attach controller pointer to irq_data */
+ irq_data->intc = intc;
+
+ /* set handler and flags */
+ switch (irq_data->type) {
+ case S3C_IRQTYPE_NONE:
+ return 0;
+ case S3C_IRQTYPE_EINT:
+ if (irq_data->parent_irq)
+ irq_set_chip_and_handler(virq, &s3c_irqext_chip,
+ handle_edge_irq);
+ else
+ irq_set_chip_and_handler(virq, &s3c_irq_eint0t4,
+ handle_edge_irq);
+ break;
+ case S3C_IRQTYPE_EDGE:
+ if (irq_data->parent_irq ||
+ intc->reg_pending == S3C2416_SRCPND2)
+ irq_set_chip_and_handler(virq, &s3c_irq_level_chip,
+ handle_edge_irq);
+ else
+ irq_set_chip_and_handler(virq, &s3c_irq_chip,
+ handle_edge_irq);
+ break;
+ case S3C_IRQTYPE_LEVEL:
+ if (irq_data->parent_irq)
+ irq_set_chip_and_handler(virq, &s3c_irq_level_chip,
+ handle_level_irq);
+ else
+ irq_set_chip_and_handler(virq, &s3c_irq_chip,
+ handle_level_irq);
+ break;
+ default:
+ pr_err("irq-s3c24xx: unsupported irqtype %d\n", irq_data->type);
+ return -EINVAL;
+ }
+ set_irq_flags(virq, IRQF_VALID);
+
+ if (irq_data->parent_irq) {
+ parent_intc = intc->parent;
+ if (!parent_intc) {
+ pr_err("irq-s3c24xx: no parent controller found for hwirq %lu\n",
+ hw);
+ goto err;
+ }
+
+ parent_irq_data = &parent_intc->irqs[irq_data->parent_irq];
+ if (!irq_data) {
+ pr_err("irq-s3c24xx: no irq data found for hwirq %lu\n",
+ hw);
+ goto err;
+ }
+
+ parent_irq_data->sub_intc = intc;
+ parent_irq_data->sub_bits |= (1UL << hw);
+
+ /* attach the demuxer to the parent irq */
+ irqno = irq_find_mapping(parent_intc->domain,
+ irq_data->parent_irq);
+ if (!irqno) {
+ pr_err("irq-s3c24xx: could not find mapping for parent irq %lu\n",
+ irq_data->parent_irq);
+ goto err;
+ }
+ irq_set_chained_handler(irqno, s3c_irq_demux);
+ }
+
+ return 0;
+
+err:
+ set_irq_flags(virq, 0);
+
+ /* the only error can result from bad mapping data*/
+ return -EINVAL;
+}
+
+static struct irq_domain_ops s3c24xx_irq_ops = {
+ .map = s3c24xx_irq_map,
+ .xlate = irq_domain_xlate_twocell,
+};
+
+static void s3c24xx_clear_intc(struct s3c_irq_intc *intc)
+{
+ void __iomem *reg_source;
+ unsigned long pend;
+ unsigned long last;
+ int i;
+
+ /* if intpnd is set, read the next pending irq from there */
+ reg_source = intc->reg_intpnd ? intc->reg_intpnd : intc->reg_pending;
+
+ last = 0;
+ for (i = 0; i < 4; i++) {
+ pend = __raw_readl(reg_source);
+
+ if (pend == 0 || pend == last)
+ break;
+
+ __raw_writel(pend, intc->reg_pending);
+ if (intc->reg_intpnd)
+ __raw_writel(pend, intc->reg_intpnd);
+
+ pr_info("irq: clearing pending status %08x\n", (int)pend);
+ last = pend;
+ }
+}
+
+struct s3c_irq_intc *s3c24xx_init_intc(struct device_node *np,
+ struct s3c_irq_data *irq_data,
+ struct s3c_irq_intc *parent,
+ unsigned long address)
+{
+ struct s3c_irq_intc *intc;
+ void __iomem *base = (void *)0xf6000000; /* static mapping */
+ int irq_num;
+ int irq_start;
+ int irq_offset;
+ int ret;
+
+ intc = kzalloc(sizeof(struct s3c_irq_intc), GFP_KERNEL);
+ if (!intc)
+ return ERR_PTR(-ENOMEM);
+
+ intc->irqs = irq_data;
+
+ if (parent)
+ intc->parent = parent;
+
+ /* select the correct data for the controller.
+ * Need to hard code the irq num start and offset
+ * to preserve the static mapping for now
+ */
+ switch (address) {
+ case 0x4a000000:
+ pr_debug("irq: found main intc\n");
+ intc->reg_pending = base;
+ intc->reg_mask = base + 0x08;
+ intc->reg_intpnd = base + 0x10;
+ irq_num = 32;
+ irq_start = S3C2410_IRQ(0);
+ irq_offset = 0;
+ break;
+ case 0x4a000018:
+ pr_debug("irq: found subintc\n");
+ intc->reg_pending = base + 0x18;
+ intc->reg_mask = base + 0x1c;
+ irq_num = 29;
+ irq_start = S3C2410_IRQSUB(0);
+ irq_offset = 0;
+ break;
+ case 0x4a000040:
+ pr_debug("irq: found intc2\n");
+ intc->reg_pending = base + 0x40;
+ intc->reg_mask = base + 0x48;
+ intc->reg_intpnd = base + 0x50;
+ irq_num = 8;
+ irq_start = S3C2416_IRQ(0);
+ irq_offset = 0;
+ break;
+ case 0x560000a4:
+ pr_debug("irq: found eintc\n");
+ base = (void *)0xfd000000;
+
+ intc->reg_mask = base + 0xa4;
+ intc->reg_pending = base + 0x08;
+ irq_num = 20;
+ irq_start = S3C2410_IRQ(32);
+ irq_offset = 4;
+ break;
+ default:
+ pr_err("irq: unsupported controller address\n");
+ ret = -EINVAL;
+ goto err;
+ }
+
+ /* now that all the data is complete, init the irq-domain */
+ s3c24xx_clear_intc(intc);
+ intc->domain = irq_domain_add_legacy(np, irq_num, irq_start,
+ irq_offset, &s3c24xx_irq_ops,
+ intc);
+ if (!intc->domain) {
+ pr_err("irq: could not create irq-domain\n");
+ ret = -EINVAL;
+ goto err;
+ }
+
+ return intc;
+
+err:
+ kfree(intc);
+ return ERR_PTR(ret);
+}
+
+/* s3c24xx_init_irq
+ *
+ * Initialise S3C2410 IRQ system
+*/
+
+static struct s3c_irq_data init_base[32] = {
+ { .type = S3C_IRQTYPE_EINT, }, /* EINT0 */
+ { .type = S3C_IRQTYPE_EINT, }, /* EINT1 */
+ { .type = S3C_IRQTYPE_EINT, }, /* EINT2 */
+ { .type = S3C_IRQTYPE_EINT, }, /* EINT3 */
+ { .type = S3C_IRQTYPE_LEVEL, }, /* EINT4to7 */
+ { .type = S3C_IRQTYPE_LEVEL, }, /* EINT8to23 */
+ { .type = S3C_IRQTYPE_NONE, }, /* reserved */
+ { .type = S3C_IRQTYPE_EDGE, }, /* nBATT_FLT */
+ { .type = S3C_IRQTYPE_EDGE, }, /* TICK */
+ { .type = S3C_IRQTYPE_EDGE, }, /* WDT */
+ { .type = S3C_IRQTYPE_EDGE, }, /* TIMER0 */
+ { .type = S3C_IRQTYPE_EDGE, }, /* TIMER1 */
+ { .type = S3C_IRQTYPE_EDGE, }, /* TIMER2 */
+ { .type = S3C_IRQTYPE_EDGE, }, /* TIMER3 */
+ { .type = S3C_IRQTYPE_EDGE, }, /* TIMER4 */
+ { .type = S3C_IRQTYPE_LEVEL, }, /* UART2 */
+ { .type = S3C_IRQTYPE_EDGE, }, /* LCD */
+ { .type = S3C_IRQTYPE_EDGE, }, /* DMA0 */
+ { .type = S3C_IRQTYPE_EDGE, }, /* DMA1 */
+ { .type = S3C_IRQTYPE_EDGE, }, /* DMA2 */
+ { .type = S3C_IRQTYPE_EDGE, }, /* DMA3 */
+ { .type = S3C_IRQTYPE_EDGE, }, /* SDI */
+ { .type = S3C_IRQTYPE_EDGE, }, /* SPI0 */
+ { .type = S3C_IRQTYPE_LEVEL, }, /* UART1 */
+ { .type = S3C_IRQTYPE_NONE, }, /* reserved */
+ { .type = S3C_IRQTYPE_EDGE, }, /* USBD */
+ { .type = S3C_IRQTYPE_EDGE, }, /* USBH */
+ { .type = S3C_IRQTYPE_EDGE, }, /* IIC */
+ { .type = S3C_IRQTYPE_LEVEL, }, /* UART0 */
+ { .type = S3C_IRQTYPE_EDGE, }, /* SPI1 */
+ { .type = S3C_IRQTYPE_EDGE, }, /* RTC */
+ { .type = S3C_IRQTYPE_LEVEL, }, /* ADCPARENT */
+};
+
+static struct s3c_irq_data init_eint[32] = {
+ { .type = S3C_IRQTYPE_NONE, }, /* reserved */
+ { .type = S3C_IRQTYPE_NONE, }, /* reserved */
+ { .type = S3C_IRQTYPE_NONE, }, /* reserved */
+ { .type = S3C_IRQTYPE_NONE, }, /* reserved */
+ { .type = S3C_IRQTYPE_EINT, .parent_irq = 4 }, /* EINT4 */
+ { .type = S3C_IRQTYPE_EINT, .parent_irq = 4 }, /* EINT5 */
+ { .type = S3C_IRQTYPE_EINT, .parent_irq = 4 }, /* EINT6 */
+ { .type = S3C_IRQTYPE_EINT, .parent_irq = 4 }, /* EINT7 */
+ { .type = S3C_IRQTYPE_EINT, .parent_irq = 5 }, /* EINT8 */
+ { .type = S3C_IRQTYPE_EINT, .parent_irq = 5 }, /* EINT9 */
+ { .type = S3C_IRQTYPE_EINT, .parent_irq = 5 }, /* EINT10 */
+ { .type = S3C_IRQTYPE_EINT, .parent_irq = 5 }, /* EINT11 */
+ { .type = S3C_IRQTYPE_EINT, .parent_irq = 5 }, /* EINT12 */
+ { .type = S3C_IRQTYPE_EINT, .parent_irq = 5 }, /* EINT13 */
+ { .type = S3C_IRQTYPE_EINT, .parent_irq = 5 }, /* EINT14 */
+ { .type = S3C_IRQTYPE_EINT, .parent_irq = 5 }, /* EINT15 */
+ { .type = S3C_IRQTYPE_EINT, .parent_irq = 5 }, /* EINT16 */
+ { .type = S3C_IRQTYPE_EINT, .parent_irq = 5 }, /* EINT17 */
+ { .type = S3C_IRQTYPE_EINT, .parent_irq = 5 }, /* EINT18 */
+ { .type = S3C_IRQTYPE_EINT, .parent_irq = 5 }, /* EINT19 */
+ { .type = S3C_IRQTYPE_EINT, .parent_irq = 5 }, /* EINT20 */
+ { .type = S3C_IRQTYPE_EINT, .parent_irq = 5 }, /* EINT21 */
+ { .type = S3C_IRQTYPE_EINT, .parent_irq = 5 }, /* EINT22 */
+ { .type = S3C_IRQTYPE_EINT, .parent_irq = 5 }, /* EINT23 */
+};
+
+static struct s3c_irq_data init_subint[32] = {
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 28 }, /* UART0-RX */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 28 }, /* UART0-TX */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 28 }, /* UART0-ERR */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 23 }, /* UART1-RX */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 23 }, /* UART1-TX */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 23 }, /* UART1-ERR */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 15 }, /* UART2-RX */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 15 }, /* UART2-TX */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 15 }, /* UART2-ERR */
+ { .type = S3C_IRQTYPE_EDGE, .parent_irq = 31 }, /* TC */
+ { .type = S3C_IRQTYPE_EDGE, .parent_irq = 31 }, /* ADC */
+};
+
+void __init s3c24xx_init_irq(void)
+{
+ struct s3c_irq_intc *main_intc;
+
+#ifdef CONFIG_FIQ
+ init_FIQ(FIQ_START);
+#endif
+
+ main_intc = s3c24xx_init_intc(NULL, &init_base[0], NULL, 0x4a000000);
+ if (IS_ERR(main_intc)) {
+ pr_err("irq: could not create main interrupt controller\n");
+ return;
+ }
+
+ s3c24xx_init_intc(NULL, &init_subint[0], main_intc, 0x4a000018);
+ s3c24xx_init_intc(NULL, &init_eint[0], main_intc, 0x560000a4);
+}
+
+#ifdef CONFIG_CPU_S3C2416
+static struct s3c_irq_data init_s3c2416base[32] = {
+ { .type = S3C_IRQTYPE_EINT, }, /* EINT0 */
+ { .type = S3C_IRQTYPE_EINT, }, /* EINT1 */
+ { .type = S3C_IRQTYPE_EINT, }, /* EINT2 */
+ { .type = S3C_IRQTYPE_EINT, }, /* EINT3 */
+ { .type = S3C_IRQTYPE_LEVEL, }, /* EINT4to7 */
+ { .type = S3C_IRQTYPE_LEVEL, }, /* EINT8to23 */
+ { .type = S3C_IRQTYPE_NONE, }, /* reserved */
+ { .type = S3C_IRQTYPE_EDGE, }, /* nBATT_FLT */
+ { .type = S3C_IRQTYPE_EDGE, }, /* TICK */
+ { .type = S3C_IRQTYPE_LEVEL, }, /* WDT/AC97 */
+ { .type = S3C_IRQTYPE_EDGE, }, /* TIMER0 */
+ { .type = S3C_IRQTYPE_EDGE, }, /* TIMER1 */
+ { .type = S3C_IRQTYPE_EDGE, }, /* TIMER2 */
+ { .type = S3C_IRQTYPE_EDGE, }, /* TIMER3 */
+ { .type = S3C_IRQTYPE_EDGE, }, /* TIMER4 */
+ { .type = S3C_IRQTYPE_LEVEL, }, /* UART2 */
+ { .type = S3C_IRQTYPE_LEVEL, }, /* LCD */
+ { .type = S3C_IRQTYPE_LEVEL, }, /* DMA */
+ { .type = S3C_IRQTYPE_LEVEL, }, /* UART3 */
+ { .type = S3C_IRQTYPE_NONE, }, /* reserved */
+ { .type = S3C_IRQTYPE_EDGE, }, /* SDI1 */
+ { .type = S3C_IRQTYPE_EDGE, }, /* SDI0 */
+ { .type = S3C_IRQTYPE_EDGE, }, /* SPI0 */
+ { .type = S3C_IRQTYPE_LEVEL, }, /* UART1 */
+ { .type = S3C_IRQTYPE_EDGE, }, /* NAND */
+ { .type = S3C_IRQTYPE_EDGE, }, /* USBD */
+ { .type = S3C_IRQTYPE_EDGE, }, /* USBH */
+ { .type = S3C_IRQTYPE_EDGE, }, /* IIC */
+ { .type = S3C_IRQTYPE_LEVEL, }, /* UART0 */
+ { .type = S3C_IRQTYPE_NONE, },
+ { .type = S3C_IRQTYPE_EDGE, }, /* RTC */
+ { .type = S3C_IRQTYPE_LEVEL, }, /* ADCPARENT */
+};
+
+static struct s3c_irq_data init_s3c2416subint[32] = {
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 28 }, /* UART0-RX */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 28 }, /* UART0-TX */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 28 }, /* UART0-ERR */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 23 }, /* UART1-RX */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 23 }, /* UART1-TX */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 23 }, /* UART1-ERR */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 15 }, /* UART2-RX */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 15 }, /* UART2-TX */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 15 }, /* UART2-ERR */
+ { .type = S3C_IRQTYPE_EDGE, .parent_irq = 31 }, /* TC */
+ { .type = S3C_IRQTYPE_EDGE, .parent_irq = 31 }, /* ADC */
+ { .type = S3C_IRQTYPE_NONE }, /* reserved */
+ { .type = S3C_IRQTYPE_NONE }, /* reserved */
+ { .type = S3C_IRQTYPE_NONE }, /* reserved */
+ { .type = S3C_IRQTYPE_NONE }, /* reserved */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 16 }, /* LCD2 */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 16 }, /* LCD3 */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 16 }, /* LCD4 */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 17 }, /* DMA0 */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 17 }, /* DMA1 */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 17 }, /* DMA2 */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 17 }, /* DMA3 */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 17 }, /* DMA4 */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 17 }, /* DMA5 */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 18 }, /* UART3-RX */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 18 }, /* UART3-TX */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 18 }, /* UART3-ERR */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 9 }, /* WDT */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 9 }, /* AC97 */
+};
+
+static struct s3c_irq_data init_s3c2416_second[32] = {
+ { .type = S3C_IRQTYPE_EDGE }, /* 2D */
+ { .type = S3C_IRQTYPE_EDGE }, /* IIC1 */
+ { .type = S3C_IRQTYPE_NONE }, /* reserved */
+ { .type = S3C_IRQTYPE_NONE }, /* reserved */
+ { .type = S3C_IRQTYPE_EDGE }, /* PCM0 */
+ { .type = S3C_IRQTYPE_EDGE }, /* PCM1 */
+ { .type = S3C_IRQTYPE_EDGE }, /* I2S0 */
+ { .type = S3C_IRQTYPE_EDGE }, /* I2S1 */
+};
+
+void __init s3c2416_init_irq(void)
+{
+ struct s3c_irq_intc *main_intc;
+
+ pr_info("S3C2416: IRQ Support\n");
+
+#ifdef CONFIG_FIQ
+ init_FIQ(FIQ_START);
+#endif
+
+ main_intc = s3c24xx_init_intc(NULL, &init_s3c2416base[0], NULL, 0x4a000000);
+ if (IS_ERR(main_intc)) {
+ pr_err("irq: could not create main interrupt controller\n");
+ return;
+ }
+
+ s3c24xx_init_intc(NULL, &init_eint[0], main_intc, 0x560000a4);
+ s3c24xx_init_intc(NULL, &init_s3c2416subint[0], main_intc, 0x4a000018);
+
+ s3c24xx_init_intc(NULL, &init_s3c2416_second[0], NULL, 0x4a000040);
+}
+
+#endif
+
+#ifdef CONFIG_CPU_S3C2443
+static struct s3c_irq_data init_s3c2443base[32] = {
+ { .type = S3C_IRQTYPE_EINT, }, /* EINT0 */
+ { .type = S3C_IRQTYPE_EINT, }, /* EINT1 */
+ { .type = S3C_IRQTYPE_EINT, }, /* EINT2 */
+ { .type = S3C_IRQTYPE_EINT, }, /* EINT3 */
+ { .type = S3C_IRQTYPE_LEVEL, }, /* EINT4to7 */
+ { .type = S3C_IRQTYPE_LEVEL, }, /* EINT8to23 */
+ { .type = S3C_IRQTYPE_LEVEL, }, /* CAM */
+ { .type = S3C_IRQTYPE_EDGE, }, /* nBATT_FLT */
+ { .type = S3C_IRQTYPE_EDGE, }, /* TICK */
+ { .type = S3C_IRQTYPE_LEVEL, }, /* WDT/AC97 */
+ { .type = S3C_IRQTYPE_EDGE, }, /* TIMER0 */
+ { .type = S3C_IRQTYPE_EDGE, }, /* TIMER1 */
+ { .type = S3C_IRQTYPE_EDGE, }, /* TIMER2 */
+ { .type = S3C_IRQTYPE_EDGE, }, /* TIMER3 */
+ { .type = S3C_IRQTYPE_EDGE, }, /* TIMER4 */
+ { .type = S3C_IRQTYPE_LEVEL, }, /* UART2 */
+ { .type = S3C_IRQTYPE_LEVEL, }, /* LCD */
+ { .type = S3C_IRQTYPE_LEVEL, }, /* DMA */
+ { .type = S3C_IRQTYPE_LEVEL, }, /* UART3 */
+ { .type = S3C_IRQTYPE_EDGE, }, /* CFON */
+ { .type = S3C_IRQTYPE_EDGE, }, /* SDI1 */
+ { .type = S3C_IRQTYPE_EDGE, }, /* SDI0 */
+ { .type = S3C_IRQTYPE_EDGE, }, /* SPI0 */
+ { .type = S3C_IRQTYPE_LEVEL, }, /* UART1 */
+ { .type = S3C_IRQTYPE_EDGE, }, /* NAND */
+ { .type = S3C_IRQTYPE_EDGE, }, /* USBD */
+ { .type = S3C_IRQTYPE_EDGE, }, /* USBH */
+ { .type = S3C_IRQTYPE_EDGE, }, /* IIC */
+ { .type = S3C_IRQTYPE_LEVEL, }, /* UART0 */
+ { .type = S3C_IRQTYPE_EDGE, }, /* SPI1 */
+ { .type = S3C_IRQTYPE_EDGE, }, /* RTC */
+ { .type = S3C_IRQTYPE_LEVEL, }, /* ADCPARENT */
+};
+
+
+static struct s3c_irq_data init_s3c2443subint[32] = {
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 28 }, /* UART0-RX */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 28 }, /* UART0-TX */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 28 }, /* UART0-ERR */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 23 }, /* UART1-RX */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 23 }, /* UART1-TX */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 23 }, /* UART1-ERR */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 15 }, /* UART2-RX */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 15 }, /* UART2-TX */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 15 }, /* UART2-ERR */
+ { .type = S3C_IRQTYPE_EDGE, .parent_irq = 31 }, /* TC */
+ { .type = S3C_IRQTYPE_EDGE, .parent_irq = 31 }, /* ADC */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 6 }, /* CAM_C */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 6 }, /* CAM_P */
+ { .type = S3C_IRQTYPE_NONE }, /* reserved */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 16 }, /* LCD1 */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 16 }, /* LCD2 */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 16 }, /* LCD3 */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 16 }, /* LCD4 */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 17 }, /* DMA0 */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 17 }, /* DMA1 */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 17 }, /* DMA2 */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 17 }, /* DMA3 */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 17 }, /* DMA4 */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 17 }, /* DMA5 */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 18 }, /* UART3-RX */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 18 }, /* UART3-TX */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 18 }, /* UART3-ERR */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 9 }, /* WDT */
+ { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 9 }, /* AC97 */
+};
+
+void __init s3c2443_init_irq(void)
+{
+ struct s3c_irq_intc *main_intc;
+
+ pr_info("S3C2443: IRQ Support\n");
+
+#ifdef CONFIG_FIQ
+ init_FIQ(FIQ_START);
+#endif
+
+ main_intc = s3c24xx_init_intc(NULL, &init_s3c2443base[0], NULL, 0x4a000000);
+ if (IS_ERR(main_intc)) {
+ pr_err("irq: could not create main interrupt controller\n");
+ return;
+ }
+
+ s3c24xx_init_intc(NULL, &init_eint[0], main_intc, 0x560000a4);
+ s3c24xx_init_intc(NULL, &init_s3c2443subint[0], main_intc, 0x4a000018);
+}
+#endif