diff options
author | mokopatches <mokopatches@openmoko.org> | 2008-11-19 17:03:18 +0000 |
---|---|---|
committer | warmcat <andy@warmcat.com> | 2008-11-19 17:03:18 +0000 |
commit | 8fd8547b742a608695823e38ca7cfc531d50f341 (patch) | |
tree | 05961aeb23ba98de4bb8d42b4552147be3f3060a | |
parent | ce82f1353a5b9e1466369106ba7be5eca86c5e49 (diff) |
introduce-fiq-basis.patch
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>
-rw-r--r-- | arch/arm/mach-s3c2440/Kconfig | 7 | ||||
-rw-r--r-- | arch/arm/mach-s3c2440/Makefile | 1 | ||||
-rw-r--r-- | arch/arm/mach-s3c2440/fiq_c_isr.c | 250 | ||||
-rw-r--r-- | arch/arm/mach-s3c2440/fiq_c_isr.h | 64 | ||||
-rw-r--r-- | arch/arm/mach-s3c2440/mach-gta02.c | 22 | ||||
-rw-r--r-- | arch/arm/plat-s3c24xx/include/plat/irq.h | 20 | ||||
-rw-r--r-- | arch/arm/plat-s3c24xx/irq.c | 32 | ||||
-rw-r--r-- | include/asm-arm/arch-s3c2410/fiq_ipc_gta02.h | 28 |
8 files changed, 422 insertions, 2 deletions
diff --git a/arch/arm/mach-s3c2440/Kconfig b/arch/arm/mach-s3c2440/Kconfig index 8b6548cc748..ed3f2cd4d7f 100644 --- a/arch/arm/mach-s3c2440/Kconfig +++ b/arch/arm/mach-s3c2440/Kconfig @@ -22,6 +22,13 @@ config S3C2440_DMA 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" diff --git a/arch/arm/mach-s3c2440/Makefile b/arch/arm/mach-s3c2440/Makefile index 1a4defd8556..49322329e6c 100644 --- a/arch/arm/mach-s3c2440/Makefile +++ b/arch/arm/mach-s3c2440/Makefile @@ -13,6 +13,7 @@ obj-$(CONFIG_CPU_S3C2440) += s3c2440.o dsc.o 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 diff --git a/arch/arm/mach-s3c2440/fiq_c_isr.c b/arch/arm/mach-s3c2440/fiq_c_isr.c new file mode 100644 index 00000000000..12f45276b03 --- /dev/null +++ b/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(®s, 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(®s); + /* 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); diff --git a/arch/arm/mach-s3c2440/fiq_c_isr.h b/arch/arm/mach-s3c2440/fiq_c_isr.h new file mode 100644 index 00000000000..f08740ee8d3 --- /dev/null +++ b/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 */ diff --git a/arch/arm/mach-s3c2440/mach-gta02.c b/arch/arm/mach-s3c2440/mach-gta02.c index 46acede46f2..0bdd0e02010 100644 --- a/arch/arm/mach-s3c2440/mach-gta02.c +++ b/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, diff --git a/arch/arm/plat-s3c24xx/include/plat/irq.h b/arch/arm/plat-s3c24xx/include/plat/irq.h index 45746a99534..bf15e1c5d90 100644 --- a/arch/arm/plat-s3c24xx/include/plat/irq.h +++ b/arch/arm/plat-s3c24xx/include/plat/irq.h @@ -25,8 +25,15 @@ s3c_irqsub_mask(unsigned int irqno, unsigned int parentbit, { 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)); @@ -39,6 +46,9 @@ s3c_irqsub_mask(unsigned int irqno, unsigned int parentbit, /* write back masks */ __raw_writel(submask, S3C2410_INTSUBMSK); +#ifdef CONFIG_S3C2440_C_FIQ + local_irq_restore(flags); +#endif } @@ -47,8 +57,15 @@ s3c_irqsub_unmask(unsigned int irqno, unsigned int parentbit) { 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)); @@ -57,6 +74,9 @@ s3c_irqsub_unmask(unsigned int irqno, unsigned int parentbit) /* write back masks */ __raw_writel(submask, S3C2410_INTSUBMSK); __raw_writel(mask, S3C2410_INTMSK); +#ifdef CONFIG_S3C2440_C_FIQ + local_irq_restore(flags); +#endif } diff --git a/arch/arm/plat-s3c24xx/irq.c b/arch/arm/plat-s3c24xx/irq.c index 0192ecdc144..f0ed85c6b88 100644 --- a/arch/arm/plat-s3c24xx/irq.c +++ b/arch/arm/plat-s3c24xx/irq.c @@ -134,12 +134,20 @@ static void 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 @@ -156,9 +164,19 @@ s3c_irq_maskack(unsigned int irqno) { 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); @@ -169,15 +187,25 @@ static void 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 = { diff --git a/include/asm-arm/arch-s3c2410/fiq_ipc_gta02.h b/include/asm-arm/arch-s3c2410/fiq_ipc_gta02.h new file mode 100644 index 00000000000..341f2bbd22b --- /dev/null +++ b/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 */ |