1 From 6a2c7de90f47b7eb74f3cb2d181f950ece22b3fb Mon Sep 17 00:00:00 2001
2 From: mokopatches <mokopatches@openmoko.org>
3 Date: Fri, 25 Jul 2008 22:21:22 +0100
4 Subject: [PATCH] introduce-fiq-basis.patch
5 Adds a C-based FIQ ISR which is very convenient (and unusual --
6 normally you have to do FIQ ISR in assembler only).
9 http://warmcat.com/_wp/2007/09/17/at91rm9200-fiq-faq-and-simple-example-code-patch/
11 Implemented as a platform device and driver.
13 Suspend / resume is tested and works.
15 Signed-off-by: Andy Green <andy@warmcat.com>
17 arch/arm/mach-s3c2440/Kconfig | 7 +
18 arch/arm/mach-s3c2440/Makefile | 1 +
19 arch/arm/mach-s3c2440/fiq_c_isr.c | 250 ++++++++++++++++++++++++++
20 arch/arm/mach-s3c2440/fiq_c_isr.h | 64 +++++++
21 arch/arm/mach-s3c2440/mach-gta02.c | 22 +++
22 arch/arm/plat-s3c24xx/irq.c | 32 +++-
23 include/asm-arm/arch-s3c2410/fiq_ipc_gta02.h | 28 +++
24 include/asm-arm/plat-s3c24xx/irq.h | 20 ++
25 8 files changed, 422 insertions(+), 2 deletions(-)
26 create mode 100644 arch/arm/mach-s3c2440/fiq_c_isr.c
27 create mode 100644 arch/arm/mach-s3c2440/fiq_c_isr.h
28 create mode 100644 include/asm-arm/arch-s3c2410/fiq_ipc_gta02.h
30 diff --git a/arch/arm/mach-s3c2440/Kconfig b/arch/arm/mach-s3c2440/Kconfig
31 index 6b317d1..3015aaf 100644
32 --- a/arch/arm/mach-s3c2440/Kconfig
33 +++ b/arch/arm/mach-s3c2440/Kconfig
34 @@ -22,6 +22,13 @@ config S3C2440_DMA
36 Support for S3C2440 specific DMA code5A
39 + bool "FIQ ISR support in C"
40 + depends on ARCH_S3C2410
43 + Support for S3C2440 FIQ support in C -- see
44 + ./arch/arm/macs3c2440/fiq_c_isr.c
46 menu "S3C2440 Machines"
48 diff --git a/arch/arm/mach-s3c2440/Makefile b/arch/arm/mach-s3c2440/Makefile
49 index 1a4defd..4932232 100644
50 --- a/arch/arm/mach-s3c2440/Makefile
51 +++ b/arch/arm/mach-s3c2440/Makefile
52 @@ -13,6 +13,7 @@ obj-$(CONFIG_CPU_S3C2440) += s3c2440.o dsc.o
53 obj-$(CONFIG_CPU_S3C2440) += irq.o
54 obj-$(CONFIG_CPU_S3C2440) += clock.o
55 obj-$(CONFIG_S3C2440_DMA) += dma.o
56 +obj-$(CONFIG_S3C2440_C_FIQ) += fiq_c_isr.o
60 diff --git a/arch/arm/mach-s3c2440/fiq_c_isr.c b/arch/arm/mach-s3c2440/fiq_c_isr.c
62 index 0000000..12f4527
64 +++ b/arch/arm/mach-s3c2440/fiq_c_isr.c
67 + * Copyright 2007 Andy Green <andy@warmcat.com>
68 + * S3C modfifications
69 + * Copyright 2008 Andy Green <andy@openmoko.com>
72 +#include <linux/module.h>
73 +#include <linux/kernel.h>
74 +#include <asm/hardware.h>
76 +#include "fiq_c_isr.h"
77 +#include <linux/sysfs.h>
78 +#include <linux/device.h>
79 +#include <linux/platform_device.h>
83 +#include <asm/plat-s3c24xx/cpu.h>
84 +#include <asm/plat-s3c24xx/irq.h>
87 + * Major Caveats for using FIQ
88 + * ---------------------------
90 + * 1) it CANNOT touch any vmalloc()'d memory, only memory
91 + * that was kmalloc()'d. Static allocations in the monolithic kernel
92 + * are kmalloc()'d so they are okay. You can touch memory-mapped IO, but
93 + * the pointer for it has to have been stored in kmalloc'd memory. The
94 + * reason for this is simple: every now and then Linux turns off interrupts
95 + * and reorders the paging tables. If a FIQ happens during this time, the
96 + * virtual memory space can be partly or entirely disordered or missing.
98 + * 2) Because vmalloc() is used when a module is inserted, THIS FIQ
99 + * ISR HAS TO BE IN THE MONOLITHIC KERNEL, not a module. But the way
100 + * it is set up, you can all to enable and disable it from your module
101 + * and intercommunicate with it through struct fiq_ipc
102 + * fiq_ipc which you can define in
103 + * asm/archfiq_ipc_type.h. The reason is the same as above, a
104 + * FIQ could happen while even the ISR is not present in virtual memory
105 + * space due to pagetables being changed at the time.
107 + * 3) You can't call any Linux API code except simple macros
108 + * - understand that FIQ can come in at any time, no matter what
109 + * state of undress the kernel may privately be in, thinking it
110 + * locked the door by turning off interrupts... FIQ is an
111 + * unstoppable monster force (which is its value)
112 + * - they are not vmalloc()'d memory safe
113 + * - they might do crazy stuff like sleep: FIQ pisses fire and
114 + * is not interested in 'sleep' that the weak seem to need
115 + * - calling APIs from FIQ can re-enter un-renterable things
116 + * - summary: you cannot interoperate with linux APIs directly in the FIQ ISR
118 + * If you follow these rules, it is fantastic, an extremely powerful, solid,
119 + * genuine hard realtime feature.
123 +/* more than enough to cover our jump instruction to the isr */
124 +#define SIZEOF_FIQ_JUMP 8
125 +/* more than enough to cover s3c2440_fiq_isr() in 4K blocks */
126 +#define SIZEOF_FIQ_ISR 0x2000
127 +/* increase the size of the stack that is active during FIQ as needed */
128 +static u8 u8aFiqStack[4096];
130 +/* only one FIQ ISR possible, okay to do these here */
131 +u32 _fiq_ack_mask; /* used by isr exit define */
132 +unsigned long _fiq_count_fiqs; /* used by isr exit define */
133 +static int _fiq_irq; /* private ; irq index we were started with, or 0 */
135 +/* this function must live in the monolithic kernel somewhere! A module is
138 +extern void __attribute__ ((naked)) s3c2440_fiq_isr(void);
140 +/* this is copied into the hard FIQ vector during init */
142 +static void __attribute__ ((naked)) s3c2440_FIQ_Branch(void)
151 +static ssize_t show_count(struct device *dev, struct device_attribute *attr,
154 + return sprintf(buf, "%ld\n", _fiq_count_fiqs);
157 +static DEVICE_ATTR(count, 0444, show_count, NULL);
159 +static struct attribute *s3c2440_fiq_sysfs_entries[] = {
160 + &dev_attr_count.attr,
164 +static struct attribute_group s3c2440_fiq_attr_group = {
166 + .attrs = s3c2440_fiq_sysfs_entries,
170 + * call this from your kernel module to set up the FIQ ISR to service FIQs,
171 + * You need to have configured your FIQ input pin before anything will happen
173 + * call it with, eg, IRQ_TIMER3 from asm-arm/arch-s3c2410/irqs.h
175 + * you still need to clear the source interrupt in S3C2410_INTMSK to get
176 + * anything good happening
178 +static void fiq_init_irq_source(int irq_index_fiq)
180 + if (!irq_index_fiq) /* no interrupt */
183 + printk(KERN_INFO"Enabling FIQ using int idx %d\n",
184 + irq_index_fiq - S3C2410_CPUIRQ_OFFSET);
185 + local_fiq_disable();
187 + _fiq_irq = irq_index_fiq;
188 + _fiq_ack_mask = 1 << (irq_index_fiq - S3C2410_CPUIRQ_OFFSET);
190 + /* let our selected interrupt be a magic FIQ interrupt */
191 + __raw_writel(_fiq_ack_mask, S3C2410_INTMOD);
193 + /* it's ready to go as soon as we unmask the source in S3C2410_INTMSK */
194 + local_fiq_enable();
198 +/* call this from your kernel module to disable generation of FIQ actions */
199 +static void fiq_disable_irq_source(void)
201 + /* nothing makes FIQ any more */
202 + __raw_writel(0, S3C2410_INTMOD);
203 + local_fiq_disable();
204 + _fiq_irq = 0; /* no active source interrupt now either */
207 +/* this starts FIQ timer events... they continue until the FIQ ISR sees that
208 + * its work is done and it turns off the timer. After setting up the fiq_ipc
209 + * struct with new work, you call this to start FIQ timer actions up again.
210 + * Only the FIQ ISR decides when it is done and controls turning off the
215 + unsigned long flags;
217 + /* we have to take care about FIQ because this modification is
218 + * non-atomic, FIQ could come in after the read and before the
219 + * writeback and its changes to the register would be lost
220 + * (platform INTMSK mod code is taken care of already)
222 + local_save_flags(flags);
223 + local_fiq_disable();
224 + __raw_writel(__raw_readl(S3C2410_INTMSK) &
225 + ~(1 << (_fiq_irq - S3C2410_CPUIRQ_OFFSET)),
227 + local_irq_restore(flags);
229 +EXPORT_SYMBOL_GPL(fiq_kick);
233 +static int __init sc32440_fiq_probe(struct platform_device *pdev)
235 + struct resource *r = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
239 + /* configure for the interrupt we are meant to use */
240 + fiq_init_irq_source(r->start);
242 + return sysfs_create_group(&pdev->dev.kobj, &s3c2440_fiq_attr_group);
245 +static int sc32440_fiq_remove(struct platform_device *pdev)
247 + fiq_disable_irq_source();
248 + sysfs_remove_group(&pdev->dev.kobj, &s3c2440_fiq_attr_group);
252 +static void fiq_set_vector_and_regs(void)
254 + struct pt_regs regs;
256 + /* prep the special FIQ mode regs */
257 + memset(®s, 0, sizeof(regs));
258 + regs.ARM_r8 = (unsigned long)s3c2440_fiq_isr;
259 + regs.ARM_sp = (unsigned long)u8aFiqStack + sizeof(u8aFiqStack) - 4;
260 + /* set up the special FIQ-mode-only registers from our regs */
261 + set_fiq_regs(®s);
262 + /* copy our jump to the real ISR into the hard vector address */
263 + set_fiq_handler(s3c2440_FIQ_Branch, SIZEOF_FIQ_JUMP);
267 +static int sc32440_fiq_suspend(struct platform_device *pdev, pm_message_t state)
269 + /* nothing makes FIQ any more */
270 + __raw_writel(0, S3C2410_INTMOD);
271 + local_fiq_disable();
276 +static int sc32440_fiq_resume(struct platform_device *pdev)
278 + fiq_set_vector_and_regs();
279 + fiq_init_irq_source(_fiq_irq);
283 +#define sc32440_fiq_suspend NULL
284 +#define sc32440_fiq_resume NULL
287 +static struct platform_driver sc32440_fiq_driver = {
289 + .name = "sc32440_fiq",
290 + .owner = THIS_MODULE,
293 + .probe = sc32440_fiq_probe,
294 + .remove = __devexit_p(sc32440_fiq_remove),
295 + .suspend = sc32440_fiq_suspend,
296 + .resume = sc32440_fiq_resume,
299 +static int __init sc32440_fiq_init(void)
301 + fiq_set_vector_and_regs();
303 + return platform_driver_register(&sc32440_fiq_driver);
306 +static void __exit sc32440_fiq_exit(void)
308 + fiq_disable_irq_source();
311 +MODULE_AUTHOR("Andy Green <andy@openmoko.com>");
312 +MODULE_LICENSE("GPL");
314 +module_init(sc32440_fiq_init);
315 +module_exit(sc32440_fiq_exit);
316 diff --git a/arch/arm/mach-s3c2440/fiq_c_isr.h b/arch/arm/mach-s3c2440/fiq_c_isr.h
318 index 0000000..f08740e
320 +++ b/arch/arm/mach-s3c2440/fiq_c_isr.h
322 +#ifndef _LINUX_FIQ_C_ISR_H
323 +#define _LINUX_FIQ_C_ISR_H
325 +#include <asm/arch-s3c2410/regs-irq.h>
327 +extern unsigned long _fiq_count_fiqs;
328 +extern u32 _fiq_ack_mask;
330 +/* This CANNOT be implemented in a module -- it has to be used in code
331 + * included in the monolithic kernel
334 +#define FIQ_HANDLER_START() \
335 +void __attribute__ ((naked)) s3c2440_fiq_isr(void) \
338 + * you can declare local vars here, take care to set the frame size\
339 + * below accordingly if there are more than a few dozen bytes of them\
342 +/* stick your locals here :-)
343 + * Do NOT initialize them here! define them and initialize them after
344 + * FIQ_HANDLER_ENTRY() is done.
347 +#define FIQ_HANDLER_ENTRY(LOCALS, FRAME) \
348 + const int _FIQ_FRAME_SIZE = FRAME; \
349 + /* entry takes care to store registers we will be treading on here */\
350 + asm __volatile__ (\
352 + /* stash FIQ and r0-r8 normal regs */\
353 + "stmdb sp!, {r0-r12, lr};"\
354 + /* allow SP to get some space */\
355 + "sub sp, sp, %1 ;"\
356 + /* !! THIS SETS THE FRAME, adjust to > sizeof locals */\
357 + "sub fp, sp, %0 ;"\
359 + : "rI" (LOCALS), "rI" (FRAME)\
363 +/* stick your ISR code here and then end with... */
365 +#define FIQ_HANDLER_END() \
366 + _fiq_count_fiqs++;\
367 + __raw_writel(_fiq_ack_mask, S3C2410_SRCPND);\
369 + /* exit back to normal mode restoring everything */\
370 + asm __volatile__ (\
371 + /* pop our allocation */\
372 + "add sp, sp, %0 ;"\
373 + /* return FIQ regs back to pristine state\
374 + * and get normal regs back\
376 + "ldmia sp!, {r0-r12, lr};"\
379 + "subs pc, lr, #4;"\
381 + : "rI" (_FIQ_FRAME_SIZE) \
385 +#endif /* _LINUX_FIQ_C_ISR_H */
386 diff --git a/arch/arm/mach-s3c2440/mach-gta02.c b/arch/arm/mach-s3c2440/mach-gta02.c
387 index 46acede..0bdd0e0 100644
388 --- a/arch/arm/mach-s3c2440/mach-gta02.c
389 +++ b/arch/arm/mach-s3c2440/mach-gta02.c
392 #include <linux/glamofb.h>
394 +#include <asm/arch/fiq_ipc_gta02.h>
395 +#include "fiq_c_isr.h"
397 /* arbitrates which sensor IRQ owns the shared SPI bus */
398 static spinlock_t motion_irq_lock;
400 +/* define FIQ IPC struct */
402 + * contains stuff FIQ ISR modifies and normal kernel code can see and use
403 + * this is defined in <asm/arch/fiq_ipc_gta02.h>, you should customize
404 + * the definition in there and include the same definition in your kernel
405 + * module that wants to interoperate with your FIQ code.
407 +struct fiq_ipc fiq_ipc;
408 +EXPORT_SYMBOL(fiq_ipc);
410 +/* define FIQ ISR */
413 +/* define your locals here -- no initializers though */
414 +FIQ_HANDLER_ENTRY(256, 512)
415 +/* Your ISR here :-) */
419 static struct map_desc gta02_iodesc[] __initdata = {
421 .virtual = 0xe0000000,
422 diff --git a/arch/arm/plat-s3c24xx/irq.c b/arch/arm/plat-s3c24xx/irq.c
423 index ae2c5d7..9887db1 100644
424 --- a/arch/arm/plat-s3c24xx/irq.c
425 +++ b/arch/arm/plat-s3c24xx/irq.c
426 @@ -133,12 +133,20 @@ static void
427 s3c_irq_mask(unsigned int irqno)
431 +#ifdef CONFIG_S3C2440_C_FIQ
432 + unsigned long flags;
436 +#ifdef CONFIG_S3C2440_C_FIQ
437 + local_save_flags(flags);
438 + local_fiq_disable();
440 mask = __raw_readl(S3C2410_INTMSK);
441 mask |= 1UL << irqno;
442 __raw_writel(mask, S3C2410_INTMSK);
443 +#ifdef CONFIG_S3C2440_C_FIQ
444 + local_irq_restore(flags);
449 @@ -155,9 +163,19 @@ s3c_irq_maskack(unsigned int irqno)
451 unsigned long bitval = 1UL << (irqno - IRQ_EINT0);
453 +#ifdef CONFIG_S3C2440_C_FIQ
454 + unsigned long flags;
457 +#ifdef CONFIG_S3C2440_C_FIQ
458 + local_save_flags(flags);
459 + local_fiq_disable();
461 mask = __raw_readl(S3C2410_INTMSK);
462 __raw_writel(mask|bitval, S3C2410_INTMSK);
463 +#ifdef CONFIG_S3C2440_C_FIQ
464 + local_irq_restore(flags);
467 __raw_writel(bitval, S3C2410_SRCPND);
468 __raw_writel(bitval, S3C2410_INTPND);
469 @@ -168,15 +186,25 @@ static void
470 s3c_irq_unmask(unsigned int irqno)
473 +#ifdef CONFIG_S3C2440_C_FIQ
474 + unsigned long flags;
477 if (irqno != IRQ_TIMER4 && irqno != IRQ_EINT8t23)
478 irqdbf2("s3c_irq_unmask %d\n", irqno);
482 +#ifdef CONFIG_S3C2440_C_FIQ
483 + local_save_flags(flags);
484 + local_fiq_disable();
486 mask = __raw_readl(S3C2410_INTMSK);
487 mask &= ~(1UL << irqno);
488 __raw_writel(mask, S3C2410_INTMSK);
489 +#ifdef CONFIG_S3C2440_C_FIQ
490 + local_irq_restore(flags);
494 struct irq_chip s3c_irq_level_chip = {
495 diff --git a/include/asm-arm/arch-s3c2410/fiq_ipc_gta02.h b/include/asm-arm/arch-s3c2410/fiq_ipc_gta02.h
497 index 0000000..341f2bb
499 +++ b/include/asm-arm/arch-s3c2410/fiq_ipc_gta02.h
501 +#ifndef _LINUX_FIQ_IPC_H
502 +#define _LINUX_FIQ_IPC_H
505 + * this defines the struct which is used to communicate between the FIQ
506 + * world and the normal linux kernel world. One of these structs is
507 + * statically defined for you in the monolithic kernel so the FIQ ISR code
508 + * can safely touch it any any time.
510 + * You also want to include this file in your kernel module that wants to
511 + * communicate with your FIQ code. Add any kinds of vars that are used by
512 + * the FIQ ISR and the module in here.
514 + * To get you started there is just an int that is incremented every FIQ
515 + * you can remove this when you are ready to customize, but it is useful
520 + u8 u8a[0]; /* placeholder */
523 +/* actual definition lives in arch/arm/mach-s3c2440/fiq_c_isr.c */
524 +extern struct fiq_ipc fiq_ipc;
526 +extern void fiq_kick(void); /* provoke a FIQ "immediately" */
528 +#endif /* _LINUX_FIQ_IPC_H */
529 diff --git a/include/asm-arm/plat-s3c24xx/irq.h b/include/asm-arm/plat-s3c24xx/irq.h
530 index 45746a9..bf15e1c 100644
531 --- a/include/asm-arm/plat-s3c24xx/irq.h
532 +++ b/include/asm-arm/plat-s3c24xx/irq.h
533 @@ -25,8 +25,15 @@ s3c_irqsub_mask(unsigned int irqno, unsigned int parentbit,
536 unsigned long submask;
537 +#ifdef CONFIG_S3C2440_C_FIQ
538 + unsigned long flags;
541 submask = __raw_readl(S3C2410_INTSUBMSK);
542 +#ifdef CONFIG_S3C2440_C_FIQ
543 + local_save_flags(flags);
544 + local_fiq_disable();
546 mask = __raw_readl(S3C2410_INTMSK);
548 submask |= (1UL << (irqno - IRQ_S3CUART_RX0));
549 @@ -39,6 +46,9 @@ s3c_irqsub_mask(unsigned int irqno, unsigned int parentbit,
551 /* write back masks */
552 __raw_writel(submask, S3C2410_INTSUBMSK);
553 +#ifdef CONFIG_S3C2440_C_FIQ
554 + local_irq_restore(flags);
559 @@ -47,8 +57,15 @@ s3c_irqsub_unmask(unsigned int irqno, unsigned int parentbit)
562 unsigned long submask;
563 +#ifdef CONFIG_S3C2440_C_FIQ
564 + unsigned long flags;
567 submask = __raw_readl(S3C2410_INTSUBMSK);
568 +#ifdef CONFIG_S3C2440_C_FIQ
569 + local_save_flags(flags);
570 + local_fiq_disable();
572 mask = __raw_readl(S3C2410_INTMSK);
574 submask &= ~(1UL << (irqno - IRQ_S3CUART_RX0));
575 @@ -57,6 +74,9 @@ s3c_irqsub_unmask(unsigned int irqno, unsigned int parentbit)
576 /* write back masks */
577 __raw_writel(submask, S3C2410_INTSUBMSK);
578 __raw_writel(mask, S3C2410_INTMSK);
579 +#ifdef CONFIG_S3C2440_C_FIQ
580 + local_irq_restore(flags);