diff options
Diffstat (limited to 'target/linux/s3c24xx/patches-2.6.24/1053-introduce-fiq-basis.patch.patch')
-rw-r--r-- | target/linux/s3c24xx/patches-2.6.24/1053-introduce-fiq-basis.patch.patch | 601 |
1 files changed, 601 insertions, 0 deletions
diff --git a/target/linux/s3c24xx/patches-2.6.24/1053-introduce-fiq-basis.patch.patch b/target/linux/s3c24xx/patches-2.6.24/1053-introduce-fiq-basis.patch.patch new file mode 100644 index 0000000..066fe0f --- /dev/null +++ b/target/linux/s3c24xx/patches-2.6.24/1053-introduce-fiq-basis.patch.patch @@ -0,0 +1,601 @@ +From f83b2007a1e3552a5f15faacf42f7383cd73129a Mon Sep 17 00:00:00 2001 +From: mokopatches <mokopatches@openmoko.org> +Date: Sun, 13 Apr 2008 07:23:50 +0100 +Subject: [PATCH] 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> +--- + arch/arm/mach-s3c2440/Kconfig | 7 + + arch/arm/mach-s3c2440/Makefile | 1 + + arch/arm/mach-s3c2440/fiq_c_isr.c | 250 ++++++++++++++++++++++++++ + arch/arm/mach-s3c2440/fiq_c_isr.h | 64 +++++++ + arch/arm/mach-s3c2440/mach-gta02.c | 22 +++ + arch/arm/plat-s3c24xx/irq.c | 32 +++- + include/asm-arm/arch-s3c2410/fiq_ipc_gta02.h | 28 +++ + include/asm-arm/arch-s3c2410/irqs.h | 4 + + include/asm-arm/plat-s3c24xx/irq.h | 20 ++ + 9 files changed, 426 insertions(+), 2 deletions(-) + create mode 100644 arch/arm/mach-s3c2440/fiq_c_isr.c + create mode 100644 arch/arm/mach-s3c2440/fiq_c_isr.h + create mode 100644 include/asm-arm/arch-s3c2410/fiq_ipc_gta02.h + +diff --git a/arch/arm/mach-s3c2440/Kconfig b/arch/arm/mach-s3c2440/Kconfig +index 1fab1c0..f7bea5d 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 5305fdb..e3ca9e3 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 0000000..12f4527 +--- /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 0000000..f08740e +--- /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 46acede..0bdd0e0 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/irq.c b/arch/arm/plat-s3c24xx/irq.c +index 8fbc884..82f0b04 100644 +--- a/arch/arm/plat-s3c24xx/irq.c ++++ b/arch/arm/plat-s3c24xx/irq.c +@@ -133,12 +133,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 +@@ -155,9 +163,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); +@@ -168,15 +186,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 0000000..341f2bb +--- /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 */ +diff --git a/include/asm-arm/arch-s3c2410/irqs.h b/include/asm-arm/arch-s3c2410/irqs.h +index 9522cd1..ab50ffb 100644 +--- a/include/asm-arm/arch-s3c2410/irqs.h ++++ b/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 */ +diff --git a/include/asm-arm/plat-s3c24xx/irq.h b/include/asm-arm/plat-s3c24xx/irq.h +index 8af6d95..f70b1fc 100644 +--- a/include/asm-arm/plat-s3c24xx/irq.h ++++ b/include/asm-arm/plat-s3c24xx/irq.h +@@ -23,8 +23,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)); +@@ -37,6 +44,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 + + } + +@@ -45,8 +55,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)); +@@ -55,6 +72,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 + } + + +-- +1.5.6.5 + |