aboutsummaryrefslogtreecommitdiff
path: root/arch/arm/plat-s3c
diff options
context:
space:
mode:
authorBen Dooks <ben-linux@fluff.org>2008-12-18 14:52:00 +0000
committerBen Dooks <ben-linux@fluff.org>2008-12-18 14:52:00 +0000
commitc6ad115876763e4f15055982ecb9579cb7abab5f (patch)
tree75d2b0f5f31540ff8a2dcdde9e0f087b25c68868 /arch/arm/plat-s3c
parent1d19fdba149f4db055902c30b27adfb7d1ead058 (diff)
parent28ab44c5be60a9b91021a7cc7b4e202775c04764 (diff)
Merge branch 'next-s3c24xx' into next-merged
Diffstat (limited to 'arch/arm/plat-s3c')
-rw-r--r--arch/arm/plat-s3c/Kconfig8
-rw-r--r--arch/arm/plat-s3c/Makefile18
-rw-r--r--arch/arm/plat-s3c/clock.c359
-rw-r--r--arch/arm/plat-s3c/include/mach/io.h18
-rw-r--r--arch/arm/plat-s3c/include/mach/timex.h26
-rw-r--r--arch/arm/plat-s3c/include/mach/vmalloc.h20
-rw-r--r--arch/arm/plat-s3c/include/plat/adc.h29
-rw-r--r--arch/arm/plat-s3c/include/plat/clock.h72
-rw-r--r--arch/arm/plat-s3c/include/plat/cpu-freq.h94
-rw-r--r--arch/arm/plat-s3c/include/plat/cpu.h69
-rw-r--r--arch/arm/plat-s3c/include/plat/devs.h49
-rw-r--r--arch/arm/plat-s3c/include/plat/regs-irqtype.h21
-rw-r--r--arch/arm/plat-s3c/include/plat/uncompress.h28
-rw-r--r--arch/arm/plat-s3c/init.c161
-rw-r--r--arch/arm/plat-s3c/pwm-clock.c482
-rw-r--r--arch/arm/plat-s3c/time.c260
16 files changed, 1712 insertions, 2 deletions
diff --git a/arch/arm/plat-s3c/Kconfig b/arch/arm/plat-s3c/Kconfig
index 31656c33e05..10c1dd3cd46 100644
--- a/arch/arm/plat-s3c/Kconfig
+++ b/arch/arm/plat-s3c/Kconfig
@@ -57,6 +57,14 @@ config S3C_BOOT_ERROR_RESET
Say y here to use the watchdog to reset the system if the
kernel decompressor detects an error during decompression.
+config S3C_BOOT_UART_FORCE_FIFO
+ bool "Force UART FIFO on during boot process"
+ depends on PLAT_S3C
+ default y
+ help
+ Say Y here to force the UART FIFOs on during the kernel
+ uncompressor
+
comment "Power management"
config S3C2410_PM_DEBUG
diff --git a/arch/arm/plat-s3c/Makefile b/arch/arm/plat-s3c/Makefile
index f03d7b35ba3..a2fe3c77564 100644
--- a/arch/arm/plat-s3c/Makefile
+++ b/arch/arm/plat-s3c/Makefile
@@ -1,3 +1,17 @@
-# dummy makefile, currently just including asm/arm/plat-s3c/include/plat
+# arch/arm/plat-s3c/Makefile
+#
+# Copyright 2008 Simtec Electronics
+#
+# Licensed under GPLv2
-obj-n := dummy.o
+obj-y :=
+obj-m :=
+obj-n :=
+obj- :=
+
+# Core support for all Samsung SoCs
+
+obj-y += init.o
+obj-y += time.o
+obj-y += clock.o
+obj-y += pwm-clock.o \ No newline at end of file
diff --git a/arch/arm/plat-s3c/clock.c b/arch/arm/plat-s3c/clock.c
new file mode 100644
index 00000000000..da7ac07c7a0
--- /dev/null
+++ b/arch/arm/plat-s3c/clock.c
@@ -0,0 +1,359 @@
+/* linux/arch/arm/plat-s3c24xx/clock.c
+ *
+ * Copyright (c) 2004-2005 Simtec Electronics
+ * Ben Dooks <ben@simtec.co.uk>
+ *
+ * S3C24XX Core clock control support
+ *
+ * Based on, and code from linux/arch/arm/mach-versatile/clock.c
+ **
+ ** Copyright (C) 2004 ARM Limited.
+ ** Written by Deep Blue Solutions Limited.
+ *
+ *
+ * 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/platform_device.h>
+#include <linux/sysdev.h>
+#include <linux/interrupt.h>
+#include <linux/ioport.h>
+#include <linux/clk.h>
+#include <linux/spinlock.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+
+#include <mach/hardware.h>
+#include <asm/irq.h>
+
+#include <plat/cpu-freq.h>
+
+#include <plat/clock.h>
+#include <plat/cpu.h>
+
+/* clock information */
+
+static LIST_HEAD(clocks);
+
+/* We originally used an mutex here, but some contexts (see resume)
+ * are calling functions such as clk_set_parent() with IRQs disabled
+ * causing an BUG to be triggered.
+ */
+DEFINE_SPINLOCK(clocks_lock);
+
+/* enable and disable calls for use with the clk struct */
+
+static int clk_null_enable(struct clk *clk, int enable)
+{
+ return 0;
+}
+
+/* Clock API calls */
+
+struct clk *clk_get(struct device *dev, const char *id)
+{
+ struct clk *p;
+ struct clk *clk = ERR_PTR(-ENOENT);
+ int idno;
+
+ if (dev == NULL || dev->bus != &platform_bus_type)
+ idno = -1;
+ else
+ idno = to_platform_device(dev)->id;
+
+ spin_lock(&clocks_lock);
+
+ list_for_each_entry(p, &clocks, list) {
+ if (p->id == idno &&
+ strcmp(id, p->name) == 0 &&
+ try_module_get(p->owner)) {
+ clk = p;
+ break;
+ }
+ }
+
+ /* check for the case where a device was supplied, but the
+ * clock that was being searched for is not device specific */
+
+ if (IS_ERR(clk)) {
+ list_for_each_entry(p, &clocks, list) {
+ if (p->id == -1 && strcmp(id, p->name) == 0 &&
+ try_module_get(p->owner)) {
+ clk = p;
+ break;
+ }
+ }
+ }
+
+ spin_unlock(&clocks_lock);
+ return clk;
+}
+
+void clk_put(struct clk *clk)
+{
+ module_put(clk->owner);
+}
+
+int clk_enable(struct clk *clk)
+{
+ if (IS_ERR(clk) || clk == NULL)
+ return -EINVAL;
+
+ clk_enable(clk->parent);
+
+ spin_lock(&clocks_lock);
+
+ if ((clk->usage++) == 0)
+ (clk->enable)(clk, 1);
+
+ spin_unlock(&clocks_lock);
+ return 0;
+}
+
+void clk_disable(struct clk *clk)
+{
+ if (IS_ERR(clk) || clk == NULL)
+ return;
+
+ spin_lock(&clocks_lock);
+
+ if ((--clk->usage) == 0)
+ (clk->enable)(clk, 0);
+
+ spin_unlock(&clocks_lock);
+ clk_disable(clk->parent);
+}
+
+
+unsigned long clk_get_rate(struct clk *clk)
+{
+ if (IS_ERR(clk))
+ return 0;
+
+ if (clk->rate != 0)
+ return clk->rate;
+
+ if (clk->get_rate != NULL)
+ return (clk->get_rate)(clk);
+
+ if (clk->parent != NULL)
+ return clk_get_rate(clk->parent);
+
+ return clk->rate;
+}
+
+long clk_round_rate(struct clk *clk, unsigned long rate)
+{
+ if (!IS_ERR(clk) && clk->round_rate)
+ return (clk->round_rate)(clk, rate);
+
+ return rate;
+}
+
+int clk_set_rate(struct clk *clk, unsigned long rate)
+{
+ int ret;
+
+ if (IS_ERR(clk))
+ return -EINVAL;
+
+ /* We do not default just do a clk->rate = rate as
+ * the clock may have been made this way by choice.
+ */
+
+ WARN_ON(clk->set_rate == NULL);
+
+ if (clk->set_rate == NULL)
+ return -EINVAL;
+
+ spin_lock(&clocks_lock);
+ ret = (clk->set_rate)(clk, rate);
+ spin_unlock(&clocks_lock);
+
+ return ret;
+}
+
+struct clk *clk_get_parent(struct clk *clk)
+{
+ return clk->parent;
+}
+
+int clk_set_parent(struct clk *clk, struct clk *parent)
+{
+ int ret = 0;
+
+ if (IS_ERR(clk))
+ return -EINVAL;
+
+ spin_lock(&clocks_lock);
+
+ if (clk->set_parent)
+ ret = (clk->set_parent)(clk, parent);
+
+ spin_unlock(&clocks_lock);
+
+ return ret;
+}
+
+EXPORT_SYMBOL(clk_get);
+EXPORT_SYMBOL(clk_put);
+EXPORT_SYMBOL(clk_enable);
+EXPORT_SYMBOL(clk_disable);
+EXPORT_SYMBOL(clk_get_rate);
+EXPORT_SYMBOL(clk_round_rate);
+EXPORT_SYMBOL(clk_set_rate);
+EXPORT_SYMBOL(clk_get_parent);
+EXPORT_SYMBOL(clk_set_parent);
+
+/* base clocks */
+
+static int clk_default_setrate(struct clk *clk, unsigned long rate)
+{
+ clk->rate = rate;
+ return 0;
+}
+
+struct clk clk_xtal = {
+ .name = "xtal",
+ .id = -1,
+ .rate = 0,
+ .parent = NULL,
+ .ctrlbit = 0,
+};
+
+struct clk clk_mpll = {
+ .name = "mpll",
+ .id = -1,
+ .set_rate = clk_default_setrate,
+};
+
+struct clk clk_upll = {
+ .name = "upll",
+ .id = -1,
+ .parent = NULL,
+ .ctrlbit = 0,
+};
+
+struct clk clk_f = {
+ .name = "fclk",
+ .id = -1,
+ .rate = 0,
+ .parent = &clk_mpll,
+ .ctrlbit = 0,
+ .set_rate = clk_default_setrate,
+};
+
+struct clk clk_h = {
+ .name = "hclk",
+ .id = -1,
+ .rate = 0,
+ .parent = NULL,
+ .ctrlbit = 0,
+ .set_rate = clk_default_setrate,
+};
+
+struct clk clk_p = {
+ .name = "pclk",
+ .id = -1,
+ .rate = 0,
+ .parent = NULL,
+ .ctrlbit = 0,
+ .set_rate = clk_default_setrate,
+};
+
+struct clk clk_usb_bus = {
+ .name = "usb-bus",
+ .id = -1,
+ .rate = 0,
+ .parent = &clk_upll,
+};
+
+
+
+struct clk s3c24xx_uclk = {
+ .name = "uclk",
+ .id = -1,
+};
+
+/* initialise the clock system */
+
+int s3c24xx_register_clock(struct clk *clk)
+{
+ clk->owner = THIS_MODULE;
+
+ if (clk->enable == NULL)
+ clk->enable = clk_null_enable;
+
+ /* add to the list of available clocks */
+
+ /* Quick check to see if this clock has already been registered. */
+ BUG_ON(clk->list.prev != clk->list.next);
+
+ spin_lock(&clocks_lock);
+ list_add(&clk->list, &clocks);
+ spin_unlock(&clocks_lock);
+
+ return 0;
+}
+
+int s3c24xx_register_clocks(struct clk **clks, int nr_clks)
+{
+ int fails = 0;
+
+ for (; nr_clks > 0; nr_clks--, clks++) {
+ if (s3c24xx_register_clock(*clks) < 0)
+ fails++;
+ }
+
+ return fails;
+}
+
+/* initalise all the clocks */
+
+int __init s3c24xx_register_baseclocks(unsigned long xtal)
+{
+ printk(KERN_INFO "S3C24XX Clocks, (c) 2004 Simtec Electronics\n");
+
+ clk_xtal.rate = xtal;
+
+ /* register our clocks */
+
+ if (s3c24xx_register_clock(&clk_xtal) < 0)
+ printk(KERN_ERR "failed to register master xtal\n");
+
+ if (s3c24xx_register_clock(&clk_mpll) < 0)
+ printk(KERN_ERR "failed to register mpll clock\n");
+
+ if (s3c24xx_register_clock(&clk_upll) < 0)
+ printk(KERN_ERR "failed to register upll clock\n");
+
+ if (s3c24xx_register_clock(&clk_f) < 0)
+ printk(KERN_ERR "failed to register cpu fclk\n");
+
+ if (s3c24xx_register_clock(&clk_h) < 0)
+ printk(KERN_ERR "failed to register cpu hclk\n");
+
+ if (s3c24xx_register_clock(&clk_p) < 0)
+ printk(KERN_ERR "failed to register cpu pclk\n");
+
+ return 0;
+}
+
diff --git a/arch/arm/plat-s3c/include/mach/io.h b/arch/arm/plat-s3c/include/mach/io.h
new file mode 100644
index 00000000000..10d28d66ace
--- /dev/null
+++ b/arch/arm/plat-s3c/include/mach/io.h
@@ -0,0 +1,18 @@
+/* arch/arm/plat-s3c/include/mach/io.h
+ *
+ * Copyright 2008 Simtec Electronics
+ * Ben Dooks <ben-linux@fluff.org>
+ *
+ * Default IO routines for plat-s3c based systems, such as S3C24A0
+ */
+
+#ifndef __ASM_ARM_ARCH_IO_H
+#define __ASM_ARM_ARCH_IO_H
+
+/* No current ISA/PCI bus support. */
+#define __io(a) ((void __iomem *)(a))
+#define __mem_pci(a) (a)
+
+#define IO_SPACE_LIMIT (0xFFFFFFFF)
+
+#endif
diff --git a/arch/arm/plat-s3c/include/mach/timex.h b/arch/arm/plat-s3c/include/mach/timex.h
new file mode 100644
index 00000000000..2a425ed0a7e
--- /dev/null
+++ b/arch/arm/plat-s3c/include/mach/timex.h
@@ -0,0 +1,26 @@
+/* arch/arm/mach-s3c2410/include/mach/timex.h
+ *
+ * Copyright (c) 2003-2005 Simtec Electronics
+ * Ben Dooks <ben@simtec.co.uk>
+ *
+ * S3C2410 - time parameters
+ *
+ * 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.
+*/
+
+#ifndef __ASM_ARCH_TIMEX_H
+#define __ASM_ARCH_TIMEX_H
+
+/* CLOCK_TICK_RATE needs to be evaluatable by the cpp, so making it
+ * a variable is useless. It seems as long as we make our timers an
+ * exact multiple of HZ, any value that makes a 1->1 correspondence
+ * for the time conversion functions to/from jiffies is acceptable.
+*/
+
+
+#define CLOCK_TICK_RATE 12000000
+
+
+#endif /* __ASM_ARCH_TIMEX_H */
diff --git a/arch/arm/plat-s3c/include/mach/vmalloc.h b/arch/arm/plat-s3c/include/mach/vmalloc.h
new file mode 100644
index 00000000000..bfd2ca6e307
--- /dev/null
+++ b/arch/arm/plat-s3c/include/mach/vmalloc.h
@@ -0,0 +1,20 @@
+/* arch/arm/plat-s3c/include/mach/vmalloc.h
+ *
+ * from arch/arm/mach-iop3xx/include/mach/vmalloc.h
+ *
+ * Copyright (c) 2003 Simtec Electronics <linux@simtec.co.uk>
+ * http://www.simtec.co.uk/products/SWLINUX/
+ *
+ * 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.
+ *
+ * S3C2410 vmalloc definition
+*/
+
+#ifndef __ASM_ARCH_VMALLOC_H
+#define __ASM_ARCH_VMALLOC_H
+
+#define VMALLOC_END (0xE0000000)
+
+#endif /* __ASM_ARCH_VMALLOC_H */
diff --git a/arch/arm/plat-s3c/include/plat/adc.h b/arch/arm/plat-s3c/include/plat/adc.h
new file mode 100644
index 00000000000..43df2a404b0
--- /dev/null
+++ b/arch/arm/plat-s3c/include/plat/adc.h
@@ -0,0 +1,29 @@
+/* arch/arm/plat-s3c/include/plat/adc.h
+ *
+ * Copyright (c) 2008 Simtec Electronics
+ * http://armlinux.simnte.co.uk/
+ * Ben Dooks <ben@simtec.co.uk>
+ *
+ * S3C24XX ADC driver information
+ *
+ * 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.
+*/
+
+#ifndef __ASM_PLAT_ADC_H
+#define __ASM_PLAT_ADC_H __FILE__
+
+struct s3c_adc_client;
+
+extern int s3c_adc_start(struct s3c_adc_client *client,
+ unsigned int channel, unsigned int nr_samples);
+
+extern struct s3c_adc_client *s3c_adc_register(struct platform_device *pdev,
+ void (*select)(unsigned selected),
+ void (*conv)(unsigned d0, unsigned d1),
+ unsigned int is_ts);
+
+extern void s3c_adc_release(struct s3c_adc_client *client);
+
+#endif /* __ASM_PLAT_ADC_H */
diff --git a/arch/arm/plat-s3c/include/plat/clock.h b/arch/arm/plat-s3c/include/plat/clock.h
new file mode 100644
index 00000000000..d871609738f
--- /dev/null
+++ b/arch/arm/plat-s3c/include/plat/clock.h
@@ -0,0 +1,72 @@
+/* linux/arch/arm/plat-s3c/include/plat/clock.h
+ *
+ * Copyright (c) 2004-2005 Simtec Electronics
+ * http://www.simtec.co.uk/products/SWLINUX/
+ * Written by Ben Dooks, <ben@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.
+*/
+
+#include <linux/spinlock.h>
+
+struct clk {
+ struct list_head list;
+ struct module *owner;
+ struct clk *parent;
+ const char *name;
+ int id;
+ int usage;
+ unsigned long rate;
+ unsigned long ctrlbit;
+
+ int (*enable)(struct clk *, int enable);
+ int (*set_rate)(struct clk *c, unsigned long rate);
+ unsigned long (*get_rate)(struct clk *c);
+ unsigned long (*round_rate)(struct clk *c, unsigned long rate);
+ int (*set_parent)(struct clk *c, struct clk *parent);
+};
+
+/* other clocks which may be registered by board support */
+
+extern struct clk s3c24xx_dclk0;
+extern struct clk s3c24xx_dclk1;
+extern struct clk s3c24xx_clkout0;
+extern struct clk s3c24xx_clkout1;
+extern struct clk s3c24xx_uclk;
+
+extern struct clk clk_usb_bus;
+
+/* core clock support */
+
+extern struct clk clk_f;
+extern struct clk clk_h;
+extern struct clk clk_p;
+extern struct clk clk_mpll;
+extern struct clk clk_upll;
+extern struct clk clk_xtal;
+
+/* exports for arch/arm/mach-s3c2410
+ *
+ * Please DO NOT use these outside of arch/arm/mach-s3c2410
+*/
+
+extern spinlock_t clocks_lock;
+
+extern int s3c2410_clkcon_enable(struct clk *clk, int enable);
+
+extern int s3c24xx_register_clock(struct clk *clk);
+extern int s3c24xx_register_clocks(struct clk **clk, int nr_clks);
+
+extern int s3c24xx_register_baseclocks(unsigned long xtal);
+
+extern void s3c24xx_setup_clocks(unsigned long fclk,
+ unsigned long hclk,
+ unsigned long pclk);
+
+extern void s3c2410_setup_clocks(void);
+extern void s3c2412_setup_clocks(void);
+extern void s3c244x_setup_clocks(void);
+extern void s3c2443_setup_clocks(void);
+
diff --git a/arch/arm/plat-s3c/include/plat/cpu-freq.h b/arch/arm/plat-s3c/include/plat/cpu-freq.h
new file mode 100644
index 00000000000..c86a13307e9
--- /dev/null
+++ b/arch/arm/plat-s3c/include/plat/cpu-freq.h
@@ -0,0 +1,94 @@
+/* arch/arm/plat-s3c/include/plat/cpu-freq.h
+ *
+ * Copyright (c) 2006,2007 Simtec Electronics
+ * http://armlinux.simtec.co.uk/
+ * Ben Dooks <ben@simtec.co.uk>
+ *
+ * S3C CPU frequency scaling support - driver and board
+ *
+ * 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/cpufreq.h>
+
+struct s3c_cpufreq_info;
+struct s3c_cpufreq_board;
+struct s3c_iotimings;
+
+struct s3c_freq {
+ unsigned long fclk;
+ unsigned long armclk;
+ unsigned long hclk_tns; /* in 10ths of ns */
+ unsigned long hclk;
+ unsigned long pclk;
+};
+
+/* wrapper 'struct cpufreq_freqs' so that any drivers receiving the
+ * notification can use this information that is not provided by just
+ * having the core frequency alone.
+ */
+
+struct s3c_cpufreq_freqs {
+ struct cpufreq_freqs freqs;
+ struct s3c_freq old;
+ struct s3c_freq new;
+};
+
+#define to_s3c_cpufreq(_cf) container_of(_cf, struct s3c_cpufreq_freqs, freqs)
+
+struct s3c_clkdivs {
+ int p_divisor; /* fclk / pclk */
+ int h_divisor; /* fclk / hclk */
+ int arm_divisor; /* not all cpus have this. */
+ unsigned char dvs; /* using dvs mode to arm. */
+};
+
+#define PLLVAL(_m, _p, _s) (((_m) << 12) | ((_p) << 4) | (_s))
+
+struct s3c_pllval {
+ unsigned long freq;
+ unsigned long pll_reg;
+};
+
+struct s3c_cpufreq_config {
+ struct s3c_freq freq;
+ struct s3c_pllval pll;
+ struct s3c_clkdivs divs;
+ struct s3c_cpufreq_info *info; /* for core, not drivers */
+ struct s3c_cpufreq_board *board;
+};
+
+/* s3c_cpufreq_board
+ *
+ * per-board configuraton information, such as memory refresh and
+ * how to initialise IO timings.
+ */
+struct s3c_cpufreq_board {
+ unsigned int refresh; /* refresh period in ns */
+ unsigned int auto_io:1; /* automatically init io timings. */
+ unsigned int need_io:1; /* set if needs io timing support. */
+
+ /* any non-zero field in here is taken as an upper limit. */
+ struct s3c_freq max; /* frequency limits */
+};
+
+/* Things depending on frequency scaling. */
+#ifdef CONFIG_CPU_FREQ_S3C
+#define __init_or_cpufreq
+#else
+#define __init_or_cpufreq __init
+#endif
+
+/* Board functions */
+
+#ifdef CONFIG_CPU_FREQ_S3C
+extern int s3c_cpufreq_setboard(struct s3c_cpufreq_board *board);
+#else
+
+static inline int s3c_cpufreq_setboard(struct s3c_cpufreq_board *board)
+{
+ return 0;
+}
+#endif /* CONFIG_CPU_FREQ_S3C */
diff --git a/arch/arm/plat-s3c/include/plat/cpu.h b/arch/arm/plat-s3c/include/plat/cpu.h
new file mode 100644
index 00000000000..011157ea871
--- /dev/null
+++ b/arch/arm/plat-s3c/include/plat/cpu.h
@@ -0,0 +1,69 @@
+/* linux/arch/arm/plat-s3c/include/plat/cpu.h
+ *
+ * Copyright (c) 2004-2005 Simtec Electronics
+ * Ben Dooks <ben@simtec.co.uk>
+ *
+ * Header file for S3C24XX CPU 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.
+*/
+
+/* todo - fix when rmk changes iodescs to use `void __iomem *` */
+
+#define IODESC_ENT(x) { (unsigned long)S3C24XX_VA_##x, __phys_to_pfn(S3C24XX_PA_##x), S3C24XX_SZ_##x, MT_DEVICE }
+
+#ifndef MHZ
+#define MHZ (1000*1000)
+#endif
+
+#define print_mhz(m) ((m) / MHZ), (((m) / 1000) % 1000)
+
+/* forward declaration */
+struct s3c24xx_uart_resources;
+struct platform_device;
+struct s3c2410_uartcfg;
+struct map_desc;
+
+/* per-cpu initialisation function table. */
+
+struct cpu_table {
+ unsigned long idcode;
+ unsigned long idmask;
+ void (*map_io)(void);
+ void (*init_uarts)(struct s3c2410_uartcfg *cfg, int no);
+ void (*init_clocks)(int xtal);
+ int (*init)(void);
+ const char *name;
+};
+
+extern void s3c_init_cpu(unsigned long idcode,
+ struct cpu_table *cpus, unsigned int cputab_size);
+
+/* core initialisation functions */
+
+extern void s3c24xx_init_irq(void);
+
+extern void s3c24xx_init_io(struct map_desc *mach_desc, int size);
+
+extern void s3c24xx_init_uarts(struct s3c2410_uartcfg *cfg, int no);
+
+extern void s3c24xx_init_clocks(int xtal);
+
+extern void s3c24xx_init_uartdevs(char *name,
+ struct s3c24xx_uart_resources *res,
+ struct s3c2410_uartcfg *cfg, int no);
+
+/* timer for 2410/2440 */
+
+struct sys_timer;
+extern struct sys_timer s3c24xx_timer;
+
+/* system device classes */
+
+extern struct sysdev_class s3c2410_sysclass;
+extern struct sysdev_class s3c2412_sysclass;
+extern struct sysdev_class s3c2440_sysclass;
+extern struct sysdev_class s3c2442_sysclass;
+extern struct sysdev_class s3c2443_sysclass;
diff --git a/arch/arm/plat-s3c/include/plat/devs.h b/arch/arm/plat-s3c/include/plat/devs.h
new file mode 100644
index 00000000000..badaac9d64a
--- /dev/null
+++ b/arch/arm/plat-s3c/include/plat/devs.h
@@ -0,0 +1,49 @@
+/* linux/include/asm-arm/plat-s3c24xx/devs.h
+ *
+ * Copyright (c) 2004 Simtec Electronics
+ * Ben Dooks <ben@simtec.co.uk>
+ *
+ * Header file for s3c2410 standard platform devices
+ *
+ * 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/platform_device.h>
+
+struct s3c24xx_uart_resources {
+ struct resource *resources;
+ unsigned long nr_resources;
+};
+
+extern struct s3c24xx_uart_resources s3c2410_uart_resources[];
+
+extern struct platform_device *s3c24xx_uart_devs[];
+extern struct platform_device *s3c24xx_uart_src[];
+
+extern struct platform_device s3c_device_timer[];
+
+extern struct platform_device s3c_device_usb;
+extern struct platform_device s3c_device_lcd;
+extern struct platform_device s3c_device_wdt;
+extern struct platform_device s3c_device_i2c;
+extern struct platform_device s3c_device_iis;
+extern struct platform_device s3c_device_rtc;
+extern struct platform_device s3c_device_adc;
+extern struct platform_device s3c_device_sdi;
+extern struct platform_device s3c_device_hsmmc;
+
+extern struct platform_device s3c_device_spi0;
+extern struct platform_device s3c_device_spi1;
+
+extern struct platform_device s3c_device_nand;
+
+extern struct platform_device s3c_device_usbgadget;
+
+/* s3c2440 specific devices */
+
+#ifdef CONFIG_CPU_S3C2440
+
+extern struct platform_device s3c_device_camif;
+
+#endif
diff --git a/arch/arm/plat-s3c/include/plat/regs-irqtype.h b/arch/arm/plat-s3c/include/plat/regs-irqtype.h
new file mode 100644
index 00000000000..c63cd3fc5ad
--- /dev/null
+++ b/arch/arm/plat-s3c/include/plat/regs-irqtype.h
@@ -0,0 +1,21 @@
+/* arch/arm/plat-s3c/include/plat/regs-irqtype.h
+ *
+ * Copyright 2008 Simtec Electronics
+ * Ben Dooks <ben@simtec.co.uk>
+ * http://armlinux.simtec.co.uk/
+ *
+ * S3C - IRQ detection types.
+ *
+ * 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.
+ */
+
+/* values for S3C2410_EXTINT0/1/2 and other cpus in the series, including
+ * the S3C64XX
+*/
+#define S3C2410_EXTINT_LOWLEV (0x00)
+#define S3C2410_EXTINT_HILEV (0x01)
+#define S3C2410_EXTINT_FALLEDGE (0x02)
+#define S3C2410_EXTINT_RISEEDGE (0x04)
+#define S3C2410_EXTINT_BOTHEDGE (0x06)
diff --git a/arch/arm/plat-s3c/include/plat/uncompress.h b/arch/arm/plat-s3c/include/plat/uncompress.h
index 8a8a927292e..2c39a309aeb 100644
--- a/arch/arm/plat-s3c/include/plat/uncompress.h
+++ b/arch/arm/plat-s3c/include/plat/uncompress.h
@@ -139,6 +139,28 @@ static void arch_decomp_error(const char *x)
static void error(char *err);
+#ifdef CONFIG_S3C_BOOT_UART_FORCE_FIFO
+static inline void arch_enable_uart_fifo(void)
+{
+ u32 fifocon = uart_rd(S3C2410_UFCON);
+
+ if (!(fifocon & S3C2410_UFCON_FIFOMODE)) {
+ fifocon |= S3C2410_UFCON_RESETBOTH;
+ uart_wr(S3C2410_UFCON, fifocon);
+
+ /* wait for fifo reset to complete */
+ while (1) {
+ fifocon = uart_rd(S3C2410_UFCON);
+ if (!(fifocon & S3C2410_UFCON_RESETBOTH))
+ break;
+ }
+ }
+}
+#else
+#define arch_enable_uart_fifo() do { } while(0)
+#endif
+
+
static void
arch_decomp_setup(void)
{
@@ -149,6 +171,12 @@ arch_decomp_setup(void)
arch_detect_cpu();
arch_decomp_wdog_start();
+
+ /* Enable the UART FIFOs if they where not enabled and our
+ * configuration says we should turn them on.
+ */
+
+ arch_enable_uart_fifo();
}
diff --git a/arch/arm/plat-s3c/init.c b/arch/arm/plat-s3c/init.c
new file mode 100644
index 00000000000..85f086ee930
--- /dev/null
+++ b/arch/arm/plat-s3c/init.c
@@ -0,0 +1,161 @@
+/* linux/arch/arm/plat-s3c/init.c
+ *
+ * Copyright (c) 2008 Simtec Electronics
+ * Ben Dooks <ben@simtec.co.uk>
+ * http://armlinux.simtec.co.uk/
+ *
+ * S3C series CPU initialisation
+ *
+ * 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/serial_core.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+
+#include <mach/hardware.h>
+
+#include <asm/mach/arch.h>
+#include <asm/mach/map.h>
+
+#include <plat/cpu.h>
+#include <plat/devs.h>
+#include <plat/clock.h>
+
+#include <plat/regs-serial.h>
+
+static struct cpu_table *cpu;
+
+static struct cpu_table * __init s3c_lookup_cpu(unsigned long idcode,
+ struct cpu_table *tab,
+ unsigned int count)
+{
+ for (; count != 0; count--, tab++) {
+ if ((idcode & tab->idmask) == tab->idcode)
+ return tab;
+ }
+
+ return NULL;
+}
+
+void __init s3c_init_cpu(unsigned long idcode,
+ struct cpu_table *cputab, unsigned int cputab_size)
+{
+ cpu = s3c_lookup_cpu(idcode, cputab, cputab_size);
+
+ if (cpu == NULL) {
+ printk(KERN_ERR "Unknown CPU type 0x%08lx\n", idcode);
+ panic("Unknown S3C24XX CPU");
+ }
+
+ printk("CPU %s (id 0x%08lx)\n", cpu->name, idcode);
+
+ if (cpu->map_io == NULL || cpu->init == NULL) {
+ printk(KERN_ERR "CPU %s support not enabled\n", cpu->name);
+ panic("Unsupported Samsung CPU");
+ }
+
+ cpu->map_io();
+}
+
+/* s3c24xx_init_clocks
+ *
+ * Initialise the clock subsystem and associated information from the
+ * given master crystal value.
+ *
+ * xtal = 0 -> use default PLL crystal value (normally 12MHz)
+ * != 0 -> PLL crystal value in Hz
+*/
+
+void __init s3c24xx_init_clocks(int xtal)
+{
+ if (xtal == 0)
+ xtal = 12*1000*1000;
+
+ if (cpu == NULL)
+ panic("s3c24xx_init_clocks: no cpu setup?\n");
+
+ if (cpu->init_clocks == NULL)
+ panic("s3c24xx_init_clocks: cpu has no clock init\n");
+ else
+ (cpu->init_clocks)(xtal);
+}
+
+/* uart management */
+
+static int nr_uarts __initdata = 0;
+
+static struct s3c2410_uartcfg uart_cfgs[3];
+
+/* s3c24xx_init_uartdevs
+ *
+ * copy the specified platform data and configuration into our central
+ * set of devices, before the data is thrown away after the init process.
+ *
+ * This also fills in the array passed to the serial driver for the
+ * early initialisation of the console.
+*/
+
+void __init s3c24xx_init_uartdevs(char *name,
+ struct s3c24xx_uart_resources *res,
+ struct s3c2410_uartcfg *cfg, int no)
+{
+ struct platform_device *platdev;
+ struct s3c2410_uartcfg *cfgptr = uart_cfgs;
+ struct s3c24xx_uart_resources *resp;
+ int uart;
+
+ memcpy(cfgptr, cfg, sizeof(struct s3c2410_uartcfg) * no);
+
+ for (uart = 0; uart < no; uart++, cfg++, cfgptr++) {
+ platdev = s3c24xx_uart_src[cfgptr->hwport];
+
+ resp = res + cfgptr->hwport;
+
+ s3c24xx_uart_devs[uart] = platdev;
+
+ platdev->name = name;
+ platdev->resource = resp->resources;
+ platdev->num_resources = resp->nr_resources;
+
+ platdev->dev.platform_data = cfgptr;
+ }
+
+ nr_uarts = no;
+}
+
+void __init s3c24xx_init_uarts(struct s3c2410_uartcfg *cfg, int no)
+{
+ if (cpu == NULL)
+ return;
+
+ if (cpu->init_uarts == NULL) {
+ printk(KERN_ERR "s3c24xx_init_uarts: cpu has no uart init\n");
+ } else
+ (cpu->init_uarts)(cfg, no);
+}
+
+static int __init s3c_arch_init(void)
+{
+ int ret;
+
+ // do the correct init for cpu
+
+ if (cpu == NULL)
+ panic("s3c_arch_init: NULL cpu\n");
+
+ ret = (cpu->init)();
+ if (ret != 0)
+ return ret;
+
+ ret = platform_add_devices(s3c24xx_uart_devs, nr_uarts);
+ return ret;
+}
+
+arch_initcall(s3c_arch_init);
diff --git a/arch/arm/plat-s3c/pwm-clock.c b/arch/arm/plat-s3c/pwm-clock.c
new file mode 100644
index 00000000000..e07d82891a9
--- /dev/null
+++ b/arch/arm/plat-s3c/pwm-clock.c
@@ -0,0 +1,482 @@
+/* linux/arch/arm/plat-s3c24xx/pwm-clock.c
+ *
+ * Copyright (c) 2007 Simtec Electronics
+ * Copyright (c) 2007, 2008 Ben Dooks
+ * Ben Dooks <ben-linux@fluff.org>
+ *
+ * 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.
+*/
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/errno.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+
+#include <mach/hardware.h>
+#include <asm/irq.h>
+
+#include <mach/regs-clock.h>
+#include <mach/regs-gpio.h>
+
+#include <plat/clock.h>
+#include <plat/cpu.h>
+
+#include <plat/regs-timer.h>
+
+/* Each of the timers 0 through 5 go through the following
+ * clock tree, with the inputs depending on the timers.
+ *
+ * pclk ---- [ prescaler 0 ] -+---> timer 0
+ * +---> timer 1
+ *
+ * pclk ---- [ prescaler 1 ] -+---> timer 2
+ * +---> timer 3
+ * \---> timer 4
+ *
+ * Which are fed into the timers as so:
+ *
+ * prescaled 0 ---- [ div 2,4,8,16 ] ---\
+ * [mux] -> timer 0
+ * tclk 0 ------------------------------/
+ *
+ * prescaled 0 ---- [ div 2,4,8,16 ] ---\
+ * [mux] -> timer 1
+ * tclk 0 ------------------------------/
+ *
+ *
+ * prescaled 1 ---- [ div 2,4,8,16 ] ---\
+ * [mux] -> timer 2
+ * tclk 1 ------------------------------/
+ *
+ * prescaled 1 ---- [ div 2,4,8,16 ] ---\
+ * [mux] -> timer 3
+ * tclk 1 ------------------------------/
+ *
+ * prescaled 1 ---- [ div 2,4,8, 16 ] --\
+ * [mux] -> timer 4
+ * tclk 1 ------------------------------/
+ *
+ * Since the mux and the divider are tied together in the
+ * same register space, it is impossible to set the parent
+ * and the rate at the same time. To avoid this, we add an
+ * intermediate 'prescaled-and-divided' clock to select
+ * as the parent for the timer input clock called tdiv.
+ *
+ * prescaled clk --> pwm-tdiv ---\
+ * [ mux ] --> timer X
+ * tclk -------------------------/
+*/
+
+static struct clk clk_timer_scaler[];
+
+static unsigned long clk_pwm_scaler_get_rate(struct clk *clk)
+{
+ unsigned long tcfg0 = __raw_readl(S3C2410_TCFG0);
+
+ if (clk == &clk_timer_scaler[1]) {
+ tcfg0 &= S3C2410_TCFG_PRESCALER1_MASK;
+ tcfg0 >>= S3C2410_TCFG_PRESCALER1_SHIFT;
+ } else {
+ tcfg0 &= S3C2410_TCFG_PRESCALER0_MASK;
+ }
+
+ return clk_get_rate(clk->parent) / (tcfg0 + 1);
+}
+
+static unsigned long clk_pwm_scaler_round_rate(struct clk *clk,
+ unsigned long rate)
+{
+ unsigned long parent_rate = clk_get_rate(clk->parent);
+ unsigned long divisor = parent_rate / rate;
+
+ if (divisor > 256)
+ divisor = 256;
+ else if (divisor < 2)
+ divisor = 2;
+
+ return parent_rate / divisor;
+}
+
+static int clk_pwm_scaler_set_rate(struct clk *clk, unsigned long rate)
+{
+ unsigned long round = clk_pwm_scaler_round_rate(clk, rate);
+ unsigned long tcfg0;
+ unsigned long divisor;
+ unsigned long flags;
+
+ divisor = clk_get_rate(clk->parent) / round;
+ divisor--;
+
+ local_irq_save(flags);
+ tcfg0 = __raw_readl(S3C2410_TCFG0);
+
+ if (clk == &clk_timer_scaler[1]) {
+ tcfg0 &= ~S3C2410_TCFG_PRESCALER1_MASK;
+ tcfg0 |= divisor << S3C2410_TCFG_PRESCALER1_SHIFT;
+ } else {
+ tcfg0 &= ~S3C2410_TCFG_PRESCALER0_MASK;
+ tcfg0 |= divisor;
+ }
+
+ __raw_writel(tcfg0, S3C2410_TCFG0);
+ local_irq_restore(flags);
+
+ return 0;
+}
+
+static struct clk clk_timer_scaler[] = {
+ [0] = {
+ .name = "pwm-scaler0",
+ .id = -1,
+ .get_rate = clk_pwm_scaler_get_rate,
+ .set_rate = clk_pwm_scaler_set_rate,
+ .round_rate = clk_pwm_scaler_round_rate,
+ },
+ [1] = {
+ .name = "pwm-scaler1",
+ .id = -1,
+ .get_rate = clk_pwm_scaler_get_rate,
+ .set_rate = clk_pwm_scaler_set_rate,
+ .round_rate = clk_pwm_scaler_round_rate,
+ },
+};
+
+static struct clk clk_timer_tclk[] = {
+ [0] = {
+ .name = "pwm-tclk0",
+ .id = -1,
+ },
+ [1] = {
+ .name = "pwm-tclk1",
+ .id = -1,
+ },
+};
+
+struct pwm_tdiv_clk {
+ struct clk clk;
+ unsigned int divisor;
+};
+
+static inline struct pwm_tdiv_clk *to_tdiv(struct clk *clk)
+{
+ return container_of(clk, struct pwm_tdiv_clk, clk);
+}
+
+static inline unsigned long tcfg_to_divisor(unsigned long tcfg1)
+{
+ return 1 << (1 + tcfg1);
+}
+
+static unsigned long clk_pwm_tdiv_get_rate(struct clk *clk)
+{
+ unsigned long tcfg1 = __raw_readl(S3C2410_TCFG1);
+ unsigned int divisor;
+
+ tcfg1 >>= S3C2410_TCFG1_SHIFT(clk->id);
+ tcfg1 &= S3C2410_TCFG1_MUX_MASK;
+
+ if (tcfg1 == S3C2410_TCFG1_MUX_TCLK)
+ divisor = to_tdiv(clk)->divisor;
+ else
+ divisor = tcfg_to_divisor(tcfg1);
+
+ return clk_get_rate(clk->parent) / divisor;
+}
+
+static unsigned long clk_pwm_tdiv_round_rate(struct clk *clk,
+ unsigned long rate)
+{
+ unsigned long parent_rate;
+ unsigned long divisor;
+
+ parent_rate = clk_get_rate(clk->parent);
+ divisor = parent_rate / rate;
+
+ if (divisor <= 2)
+ divisor = 2;
+ else if (divisor <= 4)
+ divisor = 4;
+ else if (divisor <= 8)
+ divisor = 8;
+ else
+ divisor = 16;
+
+ return parent_rate / divisor;
+}
+
+static unsigned long clk_pwm_tdiv_bits(struct pwm_tdiv_clk *divclk)
+{
+ unsigned long bits;
+
+ switch (divclk->divisor) {
+ case 2:
+ bits = S3C2410_TCFG1_MUX_DIV2;
+ break;
+ case 4:
+ bits = S3C2410_TCFG1_MUX_DIV4;
+ break;
+ case 8:
+ bits = S3C2410_TCFG1_MUX_DIV8;
+ break;
+ case 16:
+ default:
+ bits = S3C2410_TCFG1_MUX_DIV16;
+ break;
+ }
+
+ return bits;
+}
+
+static void clk_pwm_tdiv_update(struct pwm_tdiv_clk *divclk)
+{
+ unsigned long tcfg1 = __raw_readl(S3C2410_TCFG1);
+ unsigned long bits = clk_pwm_tdiv_bits(divclk);
+ unsigned long flags;
+ unsigned long shift = S3C2410_TCFG1_SHIFT(divclk->clk.id);
+
+ local_irq_save(flags);
+
+ tcfg1 = __raw_readl(S3C2410_TCFG1);
+ tcfg1 &= ~(S3C2410_TCFG1_MUX_MASK << shift);
+ tcfg1 |= bits << shift;
+ __raw_writel(tcfg1, S3C2410_TCFG1);
+
+ local_irq_restore(flags);
+}
+
+static int clk_pwm_tdiv_set_rate(struct clk *clk, unsigned long rate)
+{
+ struct pwm_tdiv_clk *divclk = to_tdiv(clk);
+ unsigned long tcfg1 = __raw_readl(S3C2410_TCFG1);
+ unsigned long parent_rate = clk_get_rate(clk->parent);
+ unsigned long divisor;
+
+ tcfg1 >>= S3C2410_TCFG1_SHIFT(clk->id);
+ tcfg1 &= S3C2410_TCFG1_MUX_MASK;
+
+ rate = clk_round_rate(clk, rate);
+ divisor = parent_rate / rate;
+
+ if (divisor > 16)
+ return -EINVAL;
+
+ divclk->divisor = divisor;
+
+ /* Update the current MUX settings if we are currently
+ * selected as the clock source for this clock. */
+
+ if (tcfg1 != S3C2410_TCFG1_MUX_TCLK)
+ clk_pwm_tdiv_update(divclk);
+
+ return 0;
+}
+
+static struct pwm_tdiv_clk clk_timer_tdiv[] = {
+ [0] = {
+ .clk = {
+ .name = "pwm-tdiv",
+ .parent = &clk_timer_scaler[0],
+ .get_rate = clk_pwm_tdiv_get_rate,
+ .set_rate = clk_pwm_tdiv_set_rate,
+ .round_rate = clk_pwm_tdiv_round_rate,
+ },
+ },
+ [1] = {
+ .clk = {
+ .name = "pwm-tdiv",
+ .parent = &clk_timer_scaler[0],
+ .get_rate = clk_pwm_tdiv_get_rate,
+ .set_rate = clk_pwm_tdiv_set_rate,
+ .round_rate = clk_pwm_tdiv_round_rate,
+ }
+ },
+ [2] = {
+ .clk = {
+ .name = "pwm-tdiv",
+ .parent = &clk_timer_scaler[1],
+ .get_rate = clk_pwm_tdiv_get_rate,
+ .set_rate = clk_pwm_tdiv_set_rate,
+ .round_rate = clk_pwm_tdiv_round_rate,
+ },
+ },
+ [3] = {
+ .clk = {
+ .name = "pwm-tdiv",
+ .parent = &clk_timer_scaler[1],
+ .get_rate = clk_pwm_tdiv_get_rate,
+ .set_rate = clk_pwm_tdiv_set_rate,
+ .round_rate = clk_pwm_tdiv_round_rate,
+ },
+ },
+ [4] = {
+ .clk = {
+ .name = "pwm-tdiv",
+ .parent = &clk_timer_scaler[1],
+ .get_rate = clk_pwm_tdiv_get_rate,
+ .set_rate = clk_pwm_tdiv_set_rate,
+ .round_rate = clk_pwm_tdiv_round_rate,
+ },
+ },
+};
+
+static int __init clk_pwm_tdiv_register(unsigned int id)
+{
+ struct pwm_tdiv_clk *divclk = &clk_timer_tdiv[id];
+ unsigned long tcfg1 = __raw_readl(S3C2410_TCFG1);
+
+ tcfg1 >>= S3C2410_TCFG1_SHIFT(id);
+ tcfg1 &= S3C2410_TCFG1_MUX_MASK;
+
+ divclk->clk.id = id;
+ divclk->divisor = tcfg_to_divisor(tcfg1);
+
+ return s3c24xx_register_clock(&divclk->clk);
+}
+
+static inline struct clk *s3c24xx_pwmclk_tclk(unsigned int id)
+{
+ return (id >= 2) ? &clk_timer_tclk[1] : &clk_timer_tclk[0];
+}
+
+static inline struct clk *s3c24xx_pwmclk_tdiv(unsigned int id)
+{
+ return &clk_timer_tdiv[id].clk;
+}
+
+static int clk_pwm_tin_set_parent(struct clk *clk, struct clk *parent)
+{
+ unsigned int id = clk->id;
+ unsigned long tcfg1;
+ unsigned long flags;
+ unsigned long bits;
+ unsigned long shift = S3C2410_TCFG1_SHIFT(id);
+
+ if (parent == s3c24xx_pwmclk_tclk(id))
+ bits = S3C2410_TCFG1_MUX_TCLK << shift;
+ else if (parent == s3c24xx_pwmclk_tdiv(id))
+ bits = clk_pwm_tdiv_bits(to_tdiv(parent)) << shift;
+ else
+ return -EINVAL;
+
+ clk->parent = parent;
+
+ local_irq_save(flags);
+
+ tcfg1 = __raw_readl(S3C2410_TCFG1);
+ tcfg1 &= ~(S3C2410_TCFG1_MUX_MASK << shift);
+ __raw_writel(tcfg1 | bits, S3C2410_TCFG1);
+
+ local_irq_restore(flags);
+
+ return 0;
+}
+
+static struct clk clk_tin[] = {
+ [0] = {
+ .name = "pwm-tin",
+ .id = 0,
+ .set_parent = clk_pwm_tin_set_parent,
+ },
+ [1] = {
+ .name = "pwm-tin",
+ .id = 1,
+ .set_parent = clk_pwm_tin_set_parent,
+ },
+ [2] = {
+ .name = "pwm-tin",
+ .id = 2,
+ .set_parent = clk_pwm_tin_set_parent,
+ },
+ [3] = {
+ .name = "pwm-tin",
+ .id = 3,
+ .set_parent = clk_pwm_tin_set_parent,
+ },
+ [4] = {
+ .name = "pwm-tin",
+ .id = 4,
+ .set_parent = clk_pwm_tin_set_parent,
+ },
+};
+
+static __init int clk_pwm_tin_register(struct clk *pwm)
+{
+ unsigned long tcfg1 = __raw_readl(S3C2410_TCFG1);
+ unsigned int id = pwm->id;
+
+ struct clk *parent;
+ int ret;
+
+ ret = s3c24xx_register_clock(pwm);
+ if (ret < 0)
+ return ret;
+
+ tcfg1 >>= S3C2410_TCFG1_SHIFT(id);
+ tcfg1 &= S3C2410_TCFG1_MUX_MASK;
+
+ if (tcfg1 == S3C2410_TCFG1_MUX_TCLK)
+ parent = s3c24xx_pwmclk_tclk(id);
+ else
+ parent = s3c24xx_pwmclk_tdiv(id);
+
+ return clk_set_parent(pwm, parent);
+}
+
+static __init int s3c24xx_pwmclk_init(void)
+{
+ struct clk *clk_timers;
+ unsigned int clk;
+ int ret;
+
+ clk_timers = clk_get(NULL, "timers");
+ if (IS_ERR(clk_timers)) {
+ printk(KERN_ERR "%s: no parent clock\n", __func__);
+ return -EINVAL;
+ }
+
+ for (clk = 0; clk < ARRAY_SIZE(clk_timer_scaler); clk++) {
+ clk_timer_scaler[clk].parent = clk_timers;
+ ret = s3c24xx_register_clock(&clk_timer_scaler[clk]);
+ if (ret < 0) {
+ printk(KERN_ERR "error adding pwm scaler%d clock\n", clk);
+ goto err;
+ }
+ }
+
+ for (clk = 0; clk < ARRAY_SIZE(clk_timer_tclk); clk++) {
+ ret = s3c24xx_register_clock(&clk_timer_tclk[clk]);
+ if (ret < 0) {
+ printk(KERN_ERR "error adding pww tclk%d\n", clk);
+ goto err;
+ }
+ }
+
+ for (clk = 0; clk < ARRAY_SIZE(clk_timer_tdiv); clk++) {
+ ret = clk_pwm_tdiv_register(clk);
+ if (ret < 0) {
+ printk(KERN_ERR "error adding pwm%d tdiv clock\n", clk);
+ goto err;
+ }
+ }
+
+ for (clk = 0; clk < ARRAY_SIZE(clk_tin); clk++) {
+ ret = clk_pwm_tin_register(&clk_tin[clk]);
+ if (ret < 0) {
+ printk(KERN_ERR "error adding pwm%d tin clock\n", clk);
+ goto err;
+ }
+ }
+
+ return 0;
+
+ err:
+ return ret;
+}
+
+arch_initcall(s3c24xx_pwmclk_init);
diff --git a/arch/arm/plat-s3c/time.c b/arch/arm/plat-s3c/time.c
new file mode 100644
index 00000000000..c6861a05a29
--- /dev/null
+++ b/arch/arm/plat-s3c/time.c
@@ -0,0 +1,260 @@
+/* linux/arch/arm/plat-s3c24xx/time.c
+ *
+ * Copyright (C) 2003-2005 Simtec Electronics
+ * Ben Dooks, <ben@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 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/kernel.h>
+#include <linux/sched.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+
+#include <asm/system.h>
+#include <asm/leds.h>
+#include <asm/mach-types.h>
+
+#include <asm/irq.h>
+#include <mach/map.h>
+#include <plat/regs-timer.h>
+#include <mach/regs-irq.h>
+#include <asm/mach/time.h>
+
+#include <plat/clock.h>
+#include <plat/cpu.h>
+
+static unsigned long timer_startval;
+static unsigned long timer_usec_ticks;
+
+#define TIMER_USEC_SHIFT 16
+
+/* we use the shifted arithmetic to work out the ratio of timer ticks
+ * to usecs, as often the peripheral clock is not a nice even multiple
+ * of 1MHz.
+ *
+ * shift of 14 and 15 are too low for the 12MHz, 16 seems to be ok
+ * for the current HZ value of 200 without producing overflows.
+ *
+ * Original patch by Dimitry Andric, updated by Ben Dooks
+*/
+
+
+/* timer_mask_usec_ticks
+ *
+ * given a clock and divisor, make the value to pass into timer_ticks_to_usec
+ * to scale the ticks into usecs
+*/
+
+static inline unsigned long
+timer_mask_usec_ticks(unsigned long scaler, unsigned long pclk)
+{
+ unsigned long den = pclk / 1000;
+
+ return ((1000 << TIMER_USEC_SHIFT) * scaler + (den >> 1)) / den;
+}
+
+/* timer_ticks_to_usec
+ *
+ * convert timer ticks to usec.
+*/
+
+static inline unsigned long timer_ticks_to_usec(unsigned long ticks)
+{
+ unsigned long res;
+
+ res = ticks * timer_usec_ticks;
+ res += 1 << (TIMER_USEC_SHIFT - 4); /* round up slightly */
+
+ return res >> TIMER_USEC_SHIFT;
+}
+
+/***
+ * Returns microsecond since last clock interrupt. Note that interrupts
+ * will have been disabled by do_gettimeoffset()
+ * IRQs are disabled before entering here from do_gettimeofday()
+ */
+
+#define SRCPND_TIMER4 (1<<(IRQ_TIMER4 - IRQ_EINT0))
+
+static unsigned long s3c2410_gettimeoffset (void)
+{
+ unsigned long tdone;
+ unsigned long irqpend;
+ unsigned long tval;
+
+ /* work out how many ticks have gone since last timer interrupt */
+
+ tval = __raw_readl(S3C2410_TCNTO(4));
+ tdone = timer_startval - tval;
+
+ /* check to see if there is an interrupt pending */
+
+ irqpend = __raw_readl(S3C2410_SRCPND);
+ if (irqpend & SRCPND_TIMER4) {
+ /* re-read the timer, and try and fix up for the missed
+ * interrupt. Note, the interrupt may go off before the
+ * timer has re-loaded from wrapping.
+ */
+
+ tval = __raw_readl(S3C2410_TCNTO(4));
+ tdone = timer_startval - tval;
+
+ if (tval != 0)
+ tdone += timer_startval;
+ }
+
+ return timer_ticks_to_usec(tdone);
+}
+
+
+/*
+ * IRQ handler for the timer
+ */
+static irqreturn_t
+s3c2410_timer_interrupt(int irq, void *dev_id)
+{
+ timer_tick();
+ return IRQ_HANDLED;
+}
+
+static struct irqaction s3c2410_timer_irq = {
+ .name = "S3C2410 Timer Tick",
+ .flags = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL,
+ .handler = s3c2410_timer_interrupt,
+};
+
+#define use_tclk1_12() ( \
+ machine_is_bast() || \
+ machine_is_vr1000() || \
+ machine_is_anubis() || \
+ machine_is_osiris())
+
+/*
+ * Set up timer interrupt, and return the current time in seconds.
+ *
+ * Currently we only use timer4, as it is the only timer which has no
+ * other function that can be exploited externally
+ */
+static void s3c2410_timer_setup (void)
+{
+ unsigned long tcon;
+ unsigned long tcnt;
+ unsigned long tcfg1;
+ unsigned long tcfg0;
+
+ tcnt = 0xffff; /* default value for tcnt */
+
+ /* read the current timer configuration bits */
+
+ tcon = __raw_readl(S3C2410_TCON);
+ tcfg1 = __raw_readl(S3C2410_TCFG1);
+ tcfg0 = __raw_readl(S3C2410_TCFG0);
+
+ /* configure the system for whichever machine is in use */
+
+ if (use_tclk1_12()) {
+ /* timer is at 12MHz, scaler is 1 */
+ timer_usec_ticks = timer_mask_usec_ticks(1, 12000000);
+ tcnt = 12000000 / HZ;
+
+ tcfg1 &= ~S3C2410_TCFG1_MUX4_MASK;
+ tcfg1 |= S3C2410_TCFG1_MUX4_TCLK1;
+ } else {
+ unsigned long pclk;
+ struct clk *clk;
+
+ /* for the h1940 (and others), we use the pclk from the core
+ * to generate the timer values. since values around 50 to
+ * 70MHz are not values we can directly generate the timer
+ * value from, we need to pre-scale and divide before using it.
+ *
+ * for instance, using 50.7MHz and dividing by 6 gives 8.45MHz
+ * (8.45 ticks per usec)
+ */
+
+ /* this is used as default if no other timer can be found */
+
+ clk = clk_get(NULL, "timers");
+ if (IS_ERR(clk))
+ panic("failed to get clock for system timer");
+
+ clk_enable(clk);
+
+ pclk = clk_get_rate(clk);
+
+ /* configure clock tick */
+
+ timer_usec_ticks = timer_mask_usec_ticks(6, pclk);
+
+ tcfg1 &= ~S3C2410_TCFG1_MUX4_MASK;
+ tcfg1 |= S3C2410_TCFG1_MUX4_DIV2;
+
+ tcfg0 &= ~S3C2410_TCFG_PRESCALER1_MASK;
+ tcfg0 |= ((6 - 1) / 2) << S3C2410_TCFG_PRESCALER1_SHIFT;
+
+ tcnt = (pclk / 6) / HZ;
+ }
+
+ /* timers reload after counting zero, so reduce the count by 1 */
+
+ tcnt--;
+
+ printk(KERN_DEBUG "timer tcon=%08lx, tcnt %04lx, tcfg %08lx,%08lx, usec %08lx\n",
+ tcon, tcnt, tcfg0, tcfg1, timer_usec_ticks);
+
+ /* check to see if timer is within 16bit range... */
+ if (tcnt > 0xffff) {
+ panic("setup_timer: HZ is too small, cannot configure timer!");
+ return;
+ }
+
+ __raw_writel(tcfg1, S3C2410_TCFG1);
+ __raw_writel(tcfg0, S3C2410_TCFG0);
+
+ timer_startval = tcnt;
+ __raw_writel(tcnt, S3C2410_TCNTB(4));
+
+ /* ensure timer is stopped... */
+
+ tcon &= ~(7<<20);
+ tcon |= S3C2410_TCON_T4RELOAD;
+ tcon |= S3C2410_TCON_T4MANUALUPD;
+
+ __raw_writel(tcon, S3C2410_TCON);
+ __raw_writel(tcnt, S3C2410_TCNTB(4));
+ __raw_writel(tcnt, S3C2410_TCMPB(4));
+
+ /* start the timer running */
+ tcon |= S3C2410_TCON_T4START;
+ tcon &= ~S3C2410_TCON_T4MANUALUPD;
+ __raw_writel(tcon, S3C2410_TCON);
+}
+
+static void __init s3c2410_timer_init(void)
+{
+ s3c2410_timer_setup();
+ setup_irq(IRQ_TIMER4, &s3c2410_timer_irq);
+}
+
+struct sys_timer s3c24xx_timer = {
+ .init = s3c2410_timer_init,
+ .offset = s3c2410_gettimeoffset,
+ .resume = s3c2410_timer_setup
+};