Adds a C-based FIQ ISR which is very convenient (and unusual --
normally you have to do FIQ ISR in assembler only).
Based on my article:

http://warmcat.com/_wp/2007/09/17/at91rm9200-fiq-faq-and-simple-example-code-patch/

Implemented as a platform device and driver.

Suspend / resume is tested and works.

Signed-off-by: Andy Green <andy@warmcat.com>

Index: linux-2.6.24/arch/arm/mach-s3c2440/Kconfig
===================================================================
--- linux-2.6.24.orig/arch/arm/mach-s3c2440/Kconfig
+++ linux-2.6.24/arch/arm/mach-s3c2440/Kconfig
@@ -22,6 +22,13 @@
 	help
 	  Support for S3C2440 specific DMA code5A
 
+config S3C2440_C_FIQ
+	bool "FIQ ISR support in C"
+	depends on ARCH_S3C2410
+	select FIQ
+	help
+	  Support for S3C2440 FIQ support in C -- see
+	  ./arch/arm/macs3c2440/fiq_c_isr.c
 
 menu "S3C2440 Machines"
 
Index: linux-2.6.24/arch/arm/mach-s3c2440/Makefile
===================================================================
--- linux-2.6.24.orig/arch/arm/mach-s3c2440/Makefile
+++ linux-2.6.24/arch/arm/mach-s3c2440/Makefile
@@ -13,6 +13,7 @@
 obj-$(CONFIG_CPU_S3C2440)	+= irq.o
 obj-$(CONFIG_CPU_S3C2440)	+= clock.o
 obj-$(CONFIG_S3C2440_DMA)	+= dma.o
+obj-$(CONFIG_S3C2440_C_FIQ)	+= fiq_c_isr.o
 
 # Machine support
 
Index: linux-2.6.24/arch/arm/mach-s3c2440/fiq_c_isr.c
===================================================================
--- /dev/null
+++ linux-2.6.24/arch/arm/mach-s3c2440/fiq_c_isr.c
@@ -0,0 +1,250 @@
+/*
+ * Copyright 2007  Andy Green <andy@warmcat.com>
+ * S3C modfifications
+ * Copyright 2008 Andy Green <andy@openmoko.com>
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <asm/hardware.h>
+#include <asm/fiq.h>
+#include "fiq_c_isr.h"
+#include <linux/sysfs.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+
+#include <asm/io.h>
+
+#include <asm/plat-s3c24xx/cpu.h>
+#include <asm/plat-s3c24xx/irq.h>
+
+/*
+ * Major Caveats for using FIQ
+ * ---------------------------
+ *
+ * 1) it CANNOT touch any vmalloc()'d memory, only memory
+ *    that was kmalloc()'d.  Static allocations in the monolithic kernel
+ *    are kmalloc()'d so they are okay.  You can touch memory-mapped IO, but
+ *    the pointer for it has to have been stored in kmalloc'd memory.  The
+ *    reason for this is simple: every now and then Linux turns off interrupts
+ *    and reorders the paging tables.  If a FIQ happens during this time, the
+ *    virtual memory space can be partly or entirely disordered or missing.
+ *
+ * 2) Because vmalloc() is used when a module is inserted, THIS FIQ
+ *    ISR HAS TO BE IN THE MONOLITHIC KERNEL, not a module.  But the way
+ *    it is set up, you can all to enable and disable it from your module
+ *    and intercommunicate with it through struct fiq_ipc
+ *    fiq_ipc which you can define in
+ *    asm/archfiq_ipc_type.h.  The reason is the same as above, a
+ *    FIQ could happen while even the ISR is not present in virtual memory
+ *    space due to pagetables being changed at the time.
+ *
+ * 3) You can't call any Linux API code except simple macros
+ *    - understand that FIQ can come in at any time, no matter what
+ *      state of undress the kernel may privately be in, thinking it
+ *      locked the door by turning off interrupts... FIQ is an
+ *      unstoppable monster force (which is its value)
+ *    - they are not vmalloc()'d memory safe
+ *    - they might do crazy stuff like sleep: FIQ pisses fire and
+ *      is not interested in 'sleep' that the weak seem to need
+ *    - calling APIs from FIQ can re-enter un-renterable things
+ *    - summary: you cannot interoperate with linux APIs directly in the FIQ ISR
+ *
+ * If you follow these rules, it is fantastic, an extremely powerful, solid,
+ * genuine hard realtime feature.
+ *
+ */
+
+/* more than enough to cover our jump instruction to the isr */
+#define SIZEOF_FIQ_JUMP 8
+/* more than enough to cover s3c2440_fiq_isr() in 4K blocks */
+#define SIZEOF_FIQ_ISR 0x2000
+/* increase the size of the stack that is active during FIQ as needed */
+static u8 u8aFiqStack[4096];
+
+/* only one FIQ ISR possible, okay to do these here */
+u32 _fiq_ack_mask; /* used by isr exit define */
+unsigned long _fiq_count_fiqs; /* used by isr exit define */
+static int _fiq_irq; /* private ; irq index we were started with, or 0 */
+
+/* this function must live in the monolithic kernel somewhere!  A module is
+ * NOT good enough!
+ */
+extern void __attribute__ ((naked)) s3c2440_fiq_isr(void);
+
+/* this is copied into the hard FIQ vector during init */
+
+static void __attribute__ ((naked)) s3c2440_FIQ_Branch(void)
+{
+	asm __volatile__ (
+		"mov pc, r8 ; "
+	);
+}
+
+/* sysfs */
+
+static ssize_t show_count(struct device *dev, struct device_attribute *attr,
+			 char *buf)
+{
+	return sprintf(buf, "%ld\n", _fiq_count_fiqs);
+}
+
+static DEVICE_ATTR(count, 0444, show_count, NULL);
+
+static struct attribute *s3c2440_fiq_sysfs_entries[] = {
+	&dev_attr_count.attr,
+	NULL
+};
+
+static struct attribute_group s3c2440_fiq_attr_group = {
+	.name	= "fiq",
+	.attrs	= s3c2440_fiq_sysfs_entries,
+};
+
+/*
+ * call this from your kernel module to set up the FIQ ISR to service FIQs,
+ * You need to have configured your FIQ input pin before anything will happen
+ *
+ * call it with, eg, IRQ_TIMER3 from asm-arm/arch-s3c2410/irqs.h
+ *
+ * you still need to clear the source interrupt in S3C2410_INTMSK to get
+ * anything good happening
+ */
+static void fiq_init_irq_source(int irq_index_fiq)
+{
+	if (!irq_index_fiq) /* no interrupt */
+		return;
+
+	printk(KERN_INFO"Enabling FIQ using int idx %d\n",
+	       irq_index_fiq - S3C2410_CPUIRQ_OFFSET);
+	local_fiq_disable();
+
+	_fiq_irq = irq_index_fiq;
+	_fiq_ack_mask = 1 << (irq_index_fiq - S3C2410_CPUIRQ_OFFSET);
+
+	/* let our selected interrupt be a magic FIQ interrupt */
+	__raw_writel(_fiq_ack_mask, S3C2410_INTMOD);
+
+	/* it's ready to go as soon as we unmask the source in S3C2410_INTMSK */
+	local_fiq_enable();
+}
+
+
+/* call this from your kernel module to disable generation of FIQ actions */
+static void fiq_disable_irq_source(void)
+{
+	/* nothing makes FIQ any more */
+	__raw_writel(0, S3C2410_INTMOD);
+	local_fiq_disable();
+	_fiq_irq = 0; /* no active source interrupt now either */
+}
+
+/* this starts FIQ timer events... they continue until the FIQ ISR sees that
+ * its work is done and it turns off the timer.  After setting up the fiq_ipc
+ * struct with new work, you call this to start FIQ timer actions up again.
+ * Only the FIQ ISR decides when it is done and controls turning off the
+ * timer events.
+ */
+void fiq_kick(void)
+{
+	unsigned long flags;
+
+	/* we have to take care about FIQ because this modification is
+	 * non-atomic, FIQ could come in after the read and before the
+	 * writeback and its changes to the register would be lost
+	 * (platform INTMSK mod code is taken care of already)
+	 */
+	local_save_flags(flags);
+	local_fiq_disable();
+	__raw_writel(__raw_readl(S3C2410_INTMSK) &
+		     ~(1 << (_fiq_irq - S3C2410_CPUIRQ_OFFSET)),
+		     S3C2410_INTMSK);
+	local_irq_restore(flags);
+}
+EXPORT_SYMBOL_GPL(fiq_kick);
+
+
+
+static int __init sc32440_fiq_probe(struct platform_device *pdev)
+{
+	struct resource *r = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+
+	if (!r)
+		return -EIO;
+	/* configure for the interrupt we are meant to use */
+	fiq_init_irq_source(r->start);
+
+	return sysfs_create_group(&pdev->dev.kobj, &s3c2440_fiq_attr_group);
+}
+
+static int sc32440_fiq_remove(struct platform_device *pdev)
+{
+	fiq_disable_irq_source();
+	sysfs_remove_group(&pdev->dev.kobj, &s3c2440_fiq_attr_group);
+	return 0;
+}
+
+static void fiq_set_vector_and_regs(void)
+{
+	struct pt_regs regs;
+
+	/* prep the special FIQ mode regs */
+	memset(&regs, 0, sizeof(regs));
+	regs.ARM_r8 = (unsigned long)s3c2440_fiq_isr;
+	regs.ARM_sp = (unsigned long)u8aFiqStack + sizeof(u8aFiqStack) - 4;
+	/* set up the special FIQ-mode-only registers from our regs */
+	set_fiq_regs(&regs);
+	/* copy our jump to the real ISR into the hard vector address */
+	set_fiq_handler(s3c2440_FIQ_Branch, SIZEOF_FIQ_JUMP);
+}
+
+#ifdef CONFIG_PM
+static int sc32440_fiq_suspend(struct platform_device *pdev, pm_message_t state)
+{
+	/* nothing makes FIQ any more */
+	__raw_writel(0, S3C2410_INTMOD);
+	local_fiq_disable();
+
+	return 0;
+}
+
+static int sc32440_fiq_resume(struct platform_device *pdev)
+{
+	fiq_set_vector_and_regs();
+	fiq_init_irq_source(_fiq_irq);
+	return 0;
+}
+#else
+#define sc32440_fiq_suspend	NULL
+#define sc32440_fiq_resume	NULL
+#endif
+
+static struct platform_driver sc32440_fiq_driver = {
+	.driver = {
+		.name	= "sc32440_fiq",
+		.owner	= THIS_MODULE,
+	},
+
+	.probe	 = sc32440_fiq_probe,
+	.remove	 = __devexit_p(sc32440_fiq_remove),
+	.suspend = sc32440_fiq_suspend,
+	.resume	 = sc32440_fiq_resume,
+};
+
+static int __init sc32440_fiq_init(void)
+{
+	fiq_set_vector_and_regs();
+
+	return platform_driver_register(&sc32440_fiq_driver);
+}
+
+static void __exit sc32440_fiq_exit(void)
+{
+	fiq_disable_irq_source();
+}
+
+MODULE_AUTHOR("Andy Green <andy@openmoko.com>");
+MODULE_LICENSE("GPL");
+
+module_init(sc32440_fiq_init);
+module_exit(sc32440_fiq_exit);
Index: linux-2.6.24/arch/arm/mach-s3c2440/fiq_c_isr.h
===================================================================
--- /dev/null
+++ linux-2.6.24/arch/arm/mach-s3c2440/fiq_c_isr.h
@@ -0,0 +1,64 @@
+#ifndef _LINUX_FIQ_C_ISR_H
+#define _LINUX_FIQ_C_ISR_H
+
+#include <asm/arch-s3c2410/regs-irq.h>
+
+extern unsigned long _fiq_count_fiqs;
+extern u32 _fiq_ack_mask;
+
+/* This CANNOT be implemented in a module -- it has to be used in code
+ * included in the monolithic kernel
+ */
+
+#define FIQ_HANDLER_START() \
+void __attribute__ ((naked)) s3c2440_fiq_isr(void) \
+{\
+	/*\
+	 * you can declare local vars here, take care to set the frame size\
+	 *  below accordingly if there are more than a few dozen bytes of them\
+	 */\
+
+/* stick your locals here :-)
+ * Do NOT initialize them here!  define them and initialize them after
+ * FIQ_HANDLER_ENTRY() is done.
+ */
+
+#define FIQ_HANDLER_ENTRY(LOCALS, FRAME) \
+	const int _FIQ_FRAME_SIZE = FRAME; \
+	/* entry takes care to store registers we will be treading on here */\
+	asm __volatile__ (\
+		"mov     ip, sp ;"\
+		/* stash FIQ and r0-r8 normal regs */\
+		"stmdb	sp!, {r0-r12, lr};"\
+		/* allow SP to get some space */\
+		"sub     sp, sp, %1 ;"\
+		/* !! THIS SETS THE FRAME, adjust to > sizeof locals */\
+		"sub     fp, sp, %0 ;"\
+		:\
+		: "rI" (LOCALS), "rI" (FRAME)\
+		:"r9"\
+	);
+
+/* stick your ISR code here and then end with... */
+
+#define FIQ_HANDLER_END() \
+	_fiq_count_fiqs++;\
+	__raw_writel(_fiq_ack_mask, S3C2410_SRCPND);\
+\
+	/* exit back to normal mode restoring everything */\
+	asm __volatile__ (\
+		/* pop our allocation */\
+		"add     sp, sp, %0 ;"\
+		/* return FIQ regs back to pristine state\
+		 * and get normal regs back\
+		 */\
+		"ldmia	sp!, {r0-r12, lr};"\
+\
+		/* return */\
+		"subs	pc, lr, #4;"\
+		: \
+		: "rI" (_FIQ_FRAME_SIZE) \
+	);\
+}
+
+#endif /* _LINUX_FIQ_C_ISR_H */
Index: linux-2.6.24/arch/arm/mach-s3c2440/mach-gta02.c
===================================================================
--- linux-2.6.24.orig/arch/arm/mach-s3c2440/mach-gta02.c
+++ linux-2.6.24/arch/arm/mach-s3c2440/mach-gta02.c
@@ -78,9 +78,31 @@
 
 #include <linux/glamofb.h>
 
+#include <asm/arch/fiq_ipc_gta02.h>
+#include "fiq_c_isr.h"
+
 /* arbitrates which sensor IRQ owns the shared SPI bus */
 static spinlock_t motion_irq_lock;
 
+/* define FIQ IPC struct */
+/*
+ * contains stuff FIQ ISR modifies and normal kernel code can see and use
+ * this is defined in <asm/arch/fiq_ipc_gta02.h>, you should customize
+ * the definition in there and include the same definition in your kernel
+ * module that wants to interoperate with your FIQ code.
+ */
+struct fiq_ipc fiq_ipc;
+EXPORT_SYMBOL(fiq_ipc);
+
+/* define FIQ ISR */
+
+FIQ_HANDLER_START()
+/* define your locals here -- no initializers though */
+FIQ_HANDLER_ENTRY(256, 512)
+/* Your ISR here :-) */
+FIQ_HANDLER_END()
+
+
 static struct map_desc gta02_iodesc[] __initdata = {
 	{
 		.virtual	= 0xe0000000,
Index: linux-2.6.24/arch/arm/plat-s3c24xx/irq.c
===================================================================
--- linux-2.6.24.orig/arch/arm/plat-s3c24xx/irq.c
+++ linux-2.6.24/arch/arm/plat-s3c24xx/irq.c
@@ -133,12 +133,20 @@
 s3c_irq_mask(unsigned int irqno)
 {
 	unsigned long mask;
-
+#ifdef CONFIG_S3C2440_C_FIQ
+	unsigned long flags;
+#endif
 	irqno -= IRQ_EINT0;
-
+#ifdef CONFIG_S3C2440_C_FIQ
+	local_save_flags(flags);
+	local_fiq_disable();
+#endif
 	mask = __raw_readl(S3C2410_INTMSK);
 	mask |= 1UL << irqno;
 	__raw_writel(mask, S3C2410_INTMSK);
+#ifdef CONFIG_S3C2440_C_FIQ
+	local_irq_restore(flags);
+#endif
 }
 
 static inline void
@@ -155,9 +163,19 @@
 {
 	unsigned long bitval = 1UL << (irqno - IRQ_EINT0);
 	unsigned long mask;
+#ifdef CONFIG_S3C2440_C_FIQ
+	unsigned long flags;
+#endif
 
+#ifdef CONFIG_S3C2440_C_FIQ
+	local_save_flags(flags);
+	local_fiq_disable();
+#endif
 	mask = __raw_readl(S3C2410_INTMSK);
 	__raw_writel(mask|bitval, S3C2410_INTMSK);
+#ifdef CONFIG_S3C2440_C_FIQ
+	local_irq_restore(flags);
+#endif
 
 	__raw_writel(bitval, S3C2410_SRCPND);
 	__raw_writel(bitval, S3C2410_INTPND);
@@ -168,15 +186,25 @@
 s3c_irq_unmask(unsigned int irqno)
 {
 	unsigned long mask;
+#ifdef CONFIG_S3C2440_C_FIQ
+	unsigned long flags;
+#endif
 
 	if (irqno != IRQ_TIMER4 && irqno != IRQ_EINT8t23)
 		irqdbf2("s3c_irq_unmask %d\n", irqno);
 
 	irqno -= IRQ_EINT0;
 
+#ifdef CONFIG_S3C2440_C_FIQ
+	local_save_flags(flags);
+	local_fiq_disable();
+#endif
 	mask = __raw_readl(S3C2410_INTMSK);
 	mask &= ~(1UL << irqno);
 	__raw_writel(mask, S3C2410_INTMSK);
+#ifdef CONFIG_S3C2440_C_FIQ
+	local_irq_restore(flags);
+#endif
 }
 
 struct irq_chip s3c_irq_level_chip = {
Index: linux-2.6.24/include/asm-arm/arch-s3c2410/fiq_ipc_gta02.h
===================================================================
--- /dev/null
+++ linux-2.6.24/include/asm-arm/arch-s3c2410/fiq_ipc_gta02.h
@@ -0,0 +1,28 @@
+#ifndef _LINUX_FIQ_IPC_H
+#define _LINUX_FIQ_IPC_H
+
+/*
+ * this defines the struct which is used to communicate between the FIQ
+ * world and the normal linux kernel world.  One of these structs is
+ * statically defined for you in the monolithic kernel so the FIQ ISR code
+ * can safely touch it any any time.
+ *
+ * You also want to include this file in your kernel module that wants to
+ * communicate with your FIQ code.  Add any kinds of vars that are used by
+ * the FIQ ISR and the module in here.
+ *
+ * To get you started there is just an int that is incremented every FIQ
+ * you can remove this when you are ready to customize, but it is useful
+ * for testing
+ */
+
+struct fiq_ipc {
+	u8 u8a[0]; /* placeholder */
+};
+
+/* actual definition lives in arch/arm/mach-s3c2440/fiq_c_isr.c */
+extern struct fiq_ipc fiq_ipc;
+
+extern void fiq_kick(void);  /* provoke a FIQ "immediately" */
+
+#endif /* _LINUX_FIQ_IPC_H */
Index: linux-2.6.24/include/asm-arm/arch-s3c2410/irqs.h
===================================================================
--- linux-2.6.24.orig/include/asm-arm/arch-s3c2410/irqs.h
+++ linux-2.6.24/include/asm-arm/arch-s3c2410/irqs.h
@@ -188,4 +188,8 @@
 #define IRQ_GLAMO_MMC		IRQ_GLAMO(7)
 #define IRQ_GLAMO_RISC		IRQ_GLAMO(8)
 
+/* offset for FIQ IRQ numbers - used by arm fiq.c */
+
+#define FIQ_START 0
+
 #endif /* __ASM_ARCH_IRQ_H */
Index: linux-2.6.24/include/asm-arm/plat-s3c24xx/irq.h
===================================================================
--- linux-2.6.24.orig/include/asm-arm/plat-s3c24xx/irq.h
+++ linux-2.6.24/include/asm-arm/plat-s3c24xx/irq.h
@@ -23,8 +23,15 @@
 {
 	unsigned long mask;
 	unsigned long submask;
+#ifdef CONFIG_S3C2440_C_FIQ
+	unsigned long flags;
+#endif
 
 	submask = __raw_readl(S3C2410_INTSUBMSK);
+#ifdef CONFIG_S3C2440_C_FIQ
+	local_save_flags(flags);
+	local_fiq_disable();
+#endif
 	mask = __raw_readl(S3C2410_INTMSK);
 
 	submask |= (1UL << (irqno - IRQ_S3CUART_RX0));
@@ -37,6 +44,9 @@
 
 	/* write back masks */
 	__raw_writel(submask, S3C2410_INTSUBMSK);
+#ifdef CONFIG_S3C2440_C_FIQ
+	local_irq_restore(flags);
+#endif
 
 }
 
@@ -45,8 +55,15 @@
 {
 	unsigned long mask;
 	unsigned long submask;
+#ifdef CONFIG_S3C2440_C_FIQ
+	unsigned long flags;
+#endif
 
 	submask = __raw_readl(S3C2410_INTSUBMSK);
+#ifdef CONFIG_S3C2440_C_FIQ
+	local_save_flags(flags);
+	local_fiq_disable();
+#endif
 	mask = __raw_readl(S3C2410_INTMSK);
 
 	submask &= ~(1UL << (irqno - IRQ_S3CUART_RX0));
@@ -55,6 +72,9 @@
 	/* write back masks */
 	__raw_writel(submask, S3C2410_INTSUBMSK);
 	__raw_writel(mask, S3C2410_INTMSK);
+#ifdef CONFIG_S3C2440_C_FIQ
+	local_irq_restore(flags);
+#endif
 }
 
 
