Support for S3C2410 and S3C2410A family from the S3C24XX line
          of Samsung Mobile CPUs.
 
+# internal node to signify if we are only dealing with an S3C2412
+
+config CPU_S3C2412_ONLY
+       bool
+       depends on ARCH_S3C2410 && !CPU_S3C2400 && !CPU_S3C2410 && \
+                  !CPU_S3C2440 && !CPU_S3C2442 && CPU_S3C2412
+       default y if CPU_S3C2412
+
+config CPU_S3C2412
+       bool
+       depends on ARCH_S3C2410
+       help
+         Support for the S3C2412 and S3C2413 SoCs from the S3C24XX line
+
 config CPU_S3C244X
        bool
        depends on ARCH_S3C2410 && (CPU_S3C2440 || CPU_S3C2442)
 
 obj-$(CONFIG_PM)          += pm.o sleep.o
 obj-$(CONFIG_PM_SIMTEC)           += pm-simtec.o
 
+# S3C2412 support
+obj-$(CONFIG_CPU_S3C2412)  += s3c2412.o
+
+#
 # S3C244X support
 
 obj-$(CONFIG_CPU_S3C244X)  += s3c244x.o
 
 #include "clock.h"
 #include "s3c2400.h"
 #include "s3c2410.h"
+#include "s3c2412.h"
 #include "s3c244x.h"
 #include "s3c2440.h"
 #include "s3c2442.h"
 
 static const char name_s3c2400[]  = "S3C2400";
 static const char name_s3c2410[]  = "S3C2410";
+static const char name_s3c2412[]  = "S3C2412";
 static const char name_s3c2440[]  = "S3C2440";
 static const char name_s3c2442[]  = "S3C2442";
 static const char name_s3c2410a[] = "S3C2410A";
                .init           = s3c2442_init,
                .name           = name_s3c2442
        },
+       {
+               .idcode         = 0x32412001,
+               .idmask         = 0xffffffff,
+               .map_io         = s3c2412_map_io,
+               .init_clocks    = s3c2412_init_clocks,
+               .init_uarts     = s3c2412_init_uarts,
+               .init           = s3c2412_init,
+               .name           = name_s3c2412,
+       },
        {
                .idcode         = 0x0,   /* S3C2400 doesn't have an idcode */
                .idmask         = 0xffffffff,
 
 static struct cpu_table *cpu;
 
+static unsigned long s3c24xx_read_idcode_v5(void)
+{
+#if defined(CONFIG_CPU_S3C2412) || defined(CONFIG_CPU_S3C2413)
+       return __raw_readl(S3C2412_GSTATUS1);
+#else
+       return 1UL;     /* don't look like an 2400 */
+#endif
+}
+
+static unsigned long s3c24xx_read_idcode_v4(void)
+{
+#ifndef CONFIG_CPU_S3C2400
+       return __raw_readl(S3C2410_GSTATUS1);
+#else
+       return 0UL;
+#endif
+}
+
 void __init s3c24xx_init_io(struct map_desc *mach_desc, int size)
 {
        unsigned long idcode = 0x0;
        /* initialise the io descriptors we need for initialisation */
        iotable_init(s3c_iodesc, ARRAY_SIZE(s3c_iodesc));
 
-#ifndef CONFIG_CPU_S3C2400
-       idcode = __raw_readl(S3C2410_GSTATUS1);
-#endif
+       if (cpu_architecture() >= CPU_ARCH_ARMv5) {
+               idcode = s3c24xx_read_idcode_v5();
+       } else {
+               idcode = s3c24xx_read_idcode_v4();
+       }
 
        cpu = s3c_lookup_cpu(idcode);
 
 
 /* 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;
 
--- /dev/null
+/* linux/arch/arm/mach-s3c2410/s3c2412.c
+ *
+ * Copyright (c) 2006 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.
+ *
+ * Modifications:
+ *     16-May-2003 BJD  Created initial version
+ *     16-Aug-2003 BJD  Fixed header files and copyright, added URL
+ *     05-Sep-2003 BJD  Moved to kernel v2.6
+ *     18-Jan-2004 BJD  Added serial port configuration
+ *     21-Aug-2004 BJD  Added new struct s3c2410_board handler
+ *     28-Sep-2004 BJD  Updates for new serial port bits
+ *     04-Nov-2004 BJD  Updated UART configuration process
+ *     10-Jan-2005 BJD  Removed s3c2410_clock_tick_rate
+ *     13-Aug-2005 DA   Removed UART from initial I/O mappings
+*/
+
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/interrupt.h>
+#include <linux/list.h>
+#include <linux/timer.h>
+#include <linux/init.h>
+#include <linux/sysdev.h>
+#include <linux/platform_device.h>
+
+#include <asm/mach/arch.h>
+#include <asm/mach/map.h>
+#include <asm/mach/irq.h>
+
+#include <asm/hardware.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+
+#include <asm/arch/regs-clock.h>
+#include <asm/arch/regs-serial.h>
+#include <asm/arch/regs-gpio.h>
+#include <asm/arch/regs-gpioj.h>
+#include <asm/arch/regs-dsc.h>
+
+#include "s3c2412.h"
+#include "cpu.h"
+#include "devs.h"
+#include "clock.h"
+#include "pm.h"
+
+#ifndef CONFIG_CPU_S3C2412_ONLY
+void __iomem *s3c24xx_va_gpio2 = S3C24XX_VA_GPIO;
+#endif
+
+/* Initial IO mappings */
+
+static struct map_desc s3c2412_iodesc[] __initdata = {
+       IODESC_ENT(CLKPWR),
+       IODESC_ENT(LCD),
+       IODESC_ENT(TIMER),
+       IODESC_ENT(ADC),
+       IODESC_ENT(WATCHDOG),
+};
+
+/* uart registration process */
+
+void __init s3c2412_init_uarts(struct s3c2410_uartcfg *cfg, int no)
+{
+       s3c24xx_init_uartdevs("s3c2412-uart", s3c2410_uart_resources, cfg, no);
+
+       /* rename devices that are s3c2412/s3c2413 specific */
+       s3c_device_sdi.name  = "s3c2412-sdi";
+       s3c_device_nand.name = "s3c2412-nand";
+}
+
+/* s3c2412_map_io
+ *
+ * register the standard cpu IO areas, and any passed in from the
+ * machine specific initialisation.
+*/
+
+void __init s3c2412_map_io(struct map_desc *mach_desc, int mach_size)
+{
+       /* move base of IO */
+
+       s3c24xx_va_gpio2 = S3C24XX_VA_GPIO + 0x10;
+
+       /* register our io-tables */
+
+       iotable_init(s3c2412_iodesc, ARRAY_SIZE(s3c2412_iodesc));
+       iotable_init(mach_desc, mach_size);
+}
+
+void __init s3c2412_init_clocks(int xtal)
+{
+       unsigned long tmp;
+       unsigned long fclk;
+       unsigned long hclk;
+       unsigned long pclk;
+
+       /* now we've got our machine bits initialised, work out what
+        * clocks we've got */
+
+       fclk = s3c2410_get_pll(__raw_readl(S3C2410_MPLLCON), xtal*2);
+
+       tmp = __raw_readl(S3C2410_CLKDIVN);
+
+       /* work out clock scalings */
+
+       hclk = fclk / ((tmp & S3C2412_CLKDIVN_HDIVN_MASK) + 1);
+       hclk /= ((tmp & S3C2421_CLKDIVN_ARMDIVN) ? 2 : 1);
+       pclk = hclk / ((tmp & S3C2412_CLKDIVN_PDIVN) ? 2 : 1);
+
+       /* print brieft summary of clocks, etc */
+
+       printk("S3C2412: core %ld.%03ld MHz, memory %ld.%03ld MHz, peripheral %ld.%03ld MHz\n",
+              print_mhz(fclk), print_mhz(hclk), print_mhz(pclk));
+
+       /* initialise the clocks here, to allow other things like the
+        * console to use them
+        */
+
+       s3c24xx_setup_clocks(xtal, fclk, hclk, pclk);
+       s3c2412_baseclk_add();
+}
+
+/* need to register class before we actually register the device, and
+ * we also need to ensure that it has been initialised before any of the
+ * drivers even try to use it (even if not on an s3c2412 based system)
+ * as a driver which may support both 2410 and 2440 may try and use it.
+*/
+
+#ifdef CONFIG_PM
+static struct sleep_save s3c2412_sleep[] = {
+       SAVE_ITEM(S3C2412_DSC0),
+       SAVE_ITEM(S3C2412_DSC1),
+       SAVE_ITEM(S3C2413_GPJDAT),
+       SAVE_ITEM(S3C2413_GPJCON),
+       SAVE_ITEM(S3C2413_GPJUP),
+
+       /* save the sleep configuration anyway, just in case these
+        * get damaged during wakeup */
+
+       SAVE_ITEM(S3C2412_GPBSLPCON),
+       SAVE_ITEM(S3C2412_GPCSLPCON),
+       SAVE_ITEM(S3C2412_GPDSLPCON),
+       SAVE_ITEM(S3C2412_GPESLPCON),
+       SAVE_ITEM(S3C2412_GPFSLPCON),
+       SAVE_ITEM(S3C2412_GPGSLPCON),
+       SAVE_ITEM(S3C2412_GPHSLPCON),
+       SAVE_ITEM(S3C2413_GPJSLPCON),
+};
+
+static int s3c2412_suspend(struct sys_device *dev, pm_message_t state)
+{
+       s3c2410_pm_do_save(s3c2412_sleep, ARRAY_SIZE(s3c2412_sleep));
+       return 0;
+}
+
+static int s3c2412_resume(struct sys_device *dev)
+{
+       s3c2410_pm_do_restore(s3c2412_sleep, ARRAY_SIZE(s3c2412_sleep));
+       return 0;
+}
+
+#else
+#define s3c2412_suspend NULL
+#define s3c2412_resume  NULL
+#endif
+
+struct sysdev_class s3c2412_sysclass = {
+       set_kset_name("s3c2412-core"),
+       .suspend        = s3c2412_suspend,
+       .resume         = s3c2412_resume
+};
+
+static int __init s3c2412_core_init(void)
+{
+       return sysdev_class_register(&s3c2412_sysclass);
+}
+
+core_initcall(s3c2412_core_init);
+
+static struct sys_device s3c2412_sysdev = {
+       .cls            = &s3c2412_sysclass,
+};
+
+int __init s3c2412_init(void)
+{
+       printk("S3C2412: Initialising architecture\n");
+
+       return sysdev_register(&s3c2412_sysdev);
+}
 
--- /dev/null
+/* arch/arm/mach-s3c2410/s3c2412.h
+ *
+ * Copyright (c) 2006 Simtec Electronics
+ *     Ben Dooks <ben@simtec.co.uk>
+ *
+ * Header file for s3c2412 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.
+*/
+
+#ifdef CONFIG_CPU_S3C2412
+
+extern  int s3c2412_init(void);
+
+extern void s3c2412_map_io(struct map_desc *mach_desc, int size);
+
+extern void s3c2412_init_uarts(struct s3c2410_uartcfg *cfg, int no);
+
+extern void s3c2412_init_clocks(int xtal);
+
+extern  int s3c2412_baseclk_add(void);
+#else
+#define s3c2412_init_clocks NULL
+#define s3c2412_init_uarts NULL
+#define s3c2412_map_io NULL
+#define s3c2412_init NULL
+#endif
 
                .endm
 
                .macro  senduart,rd,rx
-               str     \rd, [\rx, # S3C2410_UTXH ]
+               strb    \rd, [\rx, # S3C2410_UTXH ]
                .endm
 
                .macro  busyuart, rd, rx
                beq     1001f                           @
                @ FIFO enabled...
 1003:
+               @ check for arm920 vs arm926. currently assume all arm926
+               @ devices have an 64 byte FIFO identical to the s3c2440
+               mrc     p15, 0, \rd, c0, c0
+               and     \rd, \rd, #0xff0
+               teq     \rd, #0x260
+               beq     1004f
                mrc     p15, 0, \rd, c1, c0
                tst     \rd, #1
                addeq   \rd, \rx, #(S3C24XX_PA_GPIO - S3C24XX_PA_UART)
                ldr     \rd, [ \rd, # S3C2410_GSTATUS1 - S3C2410_GPIOREG(0) ]
                and     \rd, \rd, #0x00ff0000
                teq     \rd, #0x00440000                @ is it 2440?
-
+1004:
                ldr     \rd, [ \rx, # S3C2410_UFSTAT ]
                moveq   \rd, \rd, lsr #SHIFT_2440TXF
                tst     \rd, #S3C2410_UFSTAT_TXFULL
 
 #define S3C24XX_PA_SPI      S3C2410_PA_SPI
 #endif
 
+/* deal with the registers that move under the 2412/2413 */
+
+#if defined(CONFIG_CPU_S3C2412) || defined(CONFIG_CPU_S3C2413)
+#ifndef __ASSEMBLY__
+extern void __iomem *s3c24xx_va_gpio2;
+#endif
+#ifdef CONFIG_CPU_S3C2412_ONLY
+#define S3C24XX_VA_GPIO2 (S3C24XX_VA_GPIO + 0x10)
+#else
+#define S3C24XX_VA_GPIO2 s3c24xx_va_gpio2
+#endif
+#else
+#define s3c24xx_va_gpio2 S3C24XX_VA_GPIO
+#define S3C24XX_VA_GPIO2 S3C24XX_VA_GPIO
+#endif
+
 #endif /* __ASM_ARCH_MAP_H */
 
 #define S3C2440_DSC0      S3C2410_GPIOREG(0xc4)
 #define S3C2440_DSC1      S3C2410_GPIOREG(0xc8)
 
+#define S3C2412_DSC0      S3C2410_GPIOREG(0xdc)
+#define S3C2412_DSC1      S3C2410_GPIOREG(0xe0)
+
 #define S3C2440_SELECT_DSC0 (0)
 #define S3C2440_SELECT_DSC1 (1<<31)
 
 
 #define S3C24XX_MISCCR        S3C2400_MISCCR
 #else
 #define S3C24XX_GPIO_BASE(x)  S3C2410_GPIO_BASE(x)
-#define S3C24XX_MISCCR        S3C2410_MISCCR
+#define S3C24XX_MISCCR       S3C24XX_GPIOREG2(0x80)
 #endif /* CONFIG_CPU_S3C2400 */
 
 
 #define S3C2410_GPIO_SFN2    (0xFFFFFFF2)      /* not available on A */
 #define S3C2410_GPIO_SFN3    (0xFFFFFFF3)      /* not available on A */
 
-/* configure GPIO ports A..G */
+/* register address for the GPIO registers.
+ * S3C24XX_GPIOREG2 is for the second set of registers in the
+ * GPIO which move between s3c2410 and s3c2412 type systems */
 
 #define S3C2410_GPIOREG(x) ((x) + S3C24XX_VA_GPIO)
+#define S3C24XX_GPIOREG2(x) ((x) + S3C24XX_VA_GPIO2)
+
+
+/* configure GPIO ports A..G */
 
 /* port A - S3C2410: 22bits, zero in bit X makes pin X output
  *          S3C2400: 18bits, zero in bit X makes pin X output
 #define S3C2410_GPH10_OUTP    (0x01 << 20)
 #define S3C2410_GPH10_CLKOUT1 (0x02 << 20)
 
+/* The S3C2412 and S3C2413 move the GPJ register set to after
+ * GPH, which means all registers after 0x80 are now offset by 0x10
+ * for the 2412/2413 from the 2410/2440/2442
+*/
+
 /* miscellaneous control */
 #define S3C2400_MISCCR    S3C2410_GPIOREG(0x54)
 #define S3C2410_MISCCR    S3C2410_GPIOREG(0x80)
 #define S3C2410_DCLKCON           S3C2410_GPIOREG(0x84)
 
+#define S3C24XX_DCLKCON           S3C24XX_GPIOREG2(0x84)
+
 /* see clock.h for dclk definitions */
 
 /* pullup control on databus */
 #define S3C2410_MISCCR_CLK0_DCLK0   (5<<4)
 #define S3C2410_MISCCR_CLK0_MASK    (7<<4)
 
+#define S3C2412_MISCCR_CLK0_RTC            (2<<4)
+
 #define S3C2410_MISCCR_CLK1_MPLL    (0<<8)
 #define S3C2410_MISCCR_CLK1_UPLL    (1<<8)
 #define S3C2410_MISCCR_CLK1_FCLK    (2<<8)
 #define S3C2410_MISCCR_CLK1_DCLK1   (5<<8)
 #define S3C2410_MISCCR_CLK1_MASK    (7<<8)
 
+#define S3C2412_MISCCR_CLK1_CLKsrc  (0<<8)
+
 #define S3C2410_MISCCR_USBSUSPND0   (1<<12)
 #define S3C2410_MISCCR_USBSUSPND1   (1<<13)
 
 
 #define S3C2410_MISCCR_nEN_SCLK0    (1<<17)
 #define S3C2410_MISCCR_nEN_SCLK1    (1<<18)
-#define S3C2410_MISCCR_nEN_SCLKE    (1<<19)
+#define S3C2410_MISCCR_nEN_SCLKE    (1<<19)    /* not 2412 */
 #define S3C2410_MISCCR_SDSLEEP     (7<<17)
 
 /* external interrupt control... */
 #define S3C2410_EXTINT1           S3C2410_GPIOREG(0x8C)
 #define S3C2410_EXTINT2           S3C2410_GPIOREG(0x90)
 
+#define S3C24XX_EXTINT0           S3C24XX_GPIOREG2(0x88)
+#define S3C24XX_EXTINT1           S3C24XX_GPIOREG2(0x8C)
+#define S3C24XX_EXTINT2           S3C24XX_GPIOREG2(0x90)
+
 /* values for S3C2410_EXTINT0/1/2 */
 #define S3C2410_EXTINT_LOWLEV   (0x00)
 #define S3C2410_EXTINT_HILEV    (0x01)
 #define S3C2410_EINFLT2           S3C2410_GPIOREG(0x9C)
 #define S3C2410_EINFLT3           S3C2410_GPIOREG(0xA0)
 
+#define S3C24XX_EINFLT0           S3C24XX_GPIOREG2(0x94)
+#define S3C24XX_EINFLT1           S3C24XX_GPIOREG2(0x98)
+#define S3C24XX_EINFLT2           S3C24XX_GPIOREG2(0x9C)
+#define S3C24XX_EINFLT3           S3C24XX_GPIOREG2(0xA0)
+
 /* values for interrupt filtering */
 #define S3C2410_EINTFLT_PCLK           (0x00)
 #define S3C2410_EINTFLT_EXTCLK         (1<<7)
 
 /* GSTATUS have miscellaneous information in them
  *
+ * These move between s3c2410 and s3c2412 style systems.
  */
 
 #define S3C2410_GSTATUS0   S3C2410_GPIOREG(0x0AC)
 #define S3C2410_GSTATUS3   S3C2410_GPIOREG(0x0B8)
 #define S3C2410_GSTATUS4   S3C2410_GPIOREG(0x0BC)
 
+#define S3C2412_GSTATUS0   S3C2410_GPIOREG(0x0BC)
+#define S3C2412_GSTATUS1   S3C2410_GPIOREG(0x0C0)
+#define S3C2412_GSTATUS2   S3C2410_GPIOREG(0x0C4)
+#define S3C2412_GSTATUS3   S3C2410_GPIOREG(0x0C8)
+#define S3C2412_GSTATUS4   S3C2410_GPIOREG(0x0CC)
+
+#define S3C24XX_GSTATUS0   S3C24XX_GPIOREG2(0x0AC)
+#define S3C24XX_GSTATUS1   S3C24XX_GPIOREG2(0x0B0)
+#define S3C24XX_GSTATUS2   S3C24XX_GPIOREG2(0x0B4)
+#define S3C24XX_GSTATUS3   S3C24XX_GPIOREG2(0x0B8)
+#define S3C24XX_GSTATUS4   S3C24XX_GPIOREG2(0x0BC)
+
 #define S3C2410_GSTATUS0_nWAIT    (1<<3)
 #define S3C2410_GSTATUS0_NCON     (1<<2)
 #define S3C2410_GSTATUS0_RnB      (1<<1)
 
 #define S3C2410_GSTATUS1_IDMASK           (0xffff0000)
 #define S3C2410_GSTATUS1_2410     (0x32410000)
+#define S3C2410_GSTATUS1_2412     (0x32412001)
 #define S3C2410_GSTATUS1_2440     (0x32440000)
 #define S3C2410_GSTATUS1_2442     (0x32440aaa)
 
 #define S3C2400_OPENCR_OPC_MOSIDIS  (0<<5)
 #define S3C2400_OPENCR_OPC_MOSIEN   (1<<5)
 
+/* 2412/2413 sleep configuration registers */
+
+#define S3C2412_GPBSLPCON      S3C2410_GPIOREG(0x1C)
+#define S3C2412_GPCSLPCON      S3C2410_GPIOREG(0x2C)
+#define S3C2412_GPDSLPCON      S3C2410_GPIOREG(0x3C)
+#define S3C2412_GPESLPCON      S3C2410_GPIOREG(0x4C)
+#define S3C2412_GPFSLPCON      S3C2410_GPIOREG(0x5C)
+#define S3C2412_GPGSLPCON      S3C2410_GPIOREG(0x6C)
+#define S3C2412_GPHSLPCON      S3C2410_GPIOREG(0x7C)
+
+/* definitions for each pin bit */
+#define S3C2412_SLPCON_LOW(x)  ( 0x00 << ((x) * 2))
+#define S3C2412_SLPCON_HI(x)   ( 0x01 << ((x) * 2))
+#define S3C2412_SLPCON_IN(x)   ( 0x02 << ((x) * 2))
+#define S3C2412_SLPCON_PDWN(x) ( 0x03 << ((x) * 2))
+#define S3C2412_SLPCON_MASK(x) ( 0x03 << ((x) * 2))
+
 #endif /* __ASM_ARCH_REGS_GPIO_H */
 
 
 #define S3C2440_GPJDAT     S3C2410_GPIOREG(0xd4)
 #define S3C2440_GPJUP      S3C2410_GPIOREG(0xd8)
 
+#define S3C2413_GPJCON         S3C2410_GPIOREG(0x80)
+#define S3C2413_GPJDAT         S3C2410_GPIOREG(0x84)
+#define S3C2413_GPJUP          S3C2410_GPIOREG(0x88)
+#define S3C2413_GPJSLPCON      S3C2410_GPIOREG(0x8C)
+
 #define S3C2440_GPJ0            S3C2410_GPIONO(S3C2440_GPIO_BANKJ, 0)
 #define S3C2440_GPJ0_INP        (0x00 << 0)
 #define S3C2440_GPJ0_OUTP       (0x01 << 0)