]>
Commit | Line | Data |
---|---|---|
fef96086 PM |
1 | /* |
2 | * arch/sh/boards/renesas/x3proto/ilsel.c | |
3 | * | |
4 | * Helper routines for SH-X3 proto board ILSEL. | |
5 | * | |
6 | * Copyright (C) 2007 Paul Mundt | |
7 | * | |
8 | * This file is subject to the terms and conditions of the GNU General Public | |
9 | * License. See the file "COPYING" in the main directory of this archive | |
10 | * for more details. | |
11 | */ | |
12 | #include <linux/init.h> | |
13 | #include <linux/kernel.h> | |
14 | #include <linux/module.h> | |
15 | #include <linux/bitmap.h> | |
16 | #include <linux/io.h> | |
17 | #include <asm/ilsel.h> | |
18 | ||
19 | /* | |
20 | * ILSEL is split across: | |
21 | * | |
22 | * ILSEL0 - 0xb8100004 [ Levels 1 - 4 ] | |
23 | * ILSEL1 - 0xb8100006 [ Levels 5 - 8 ] | |
24 | * ILSEL2 - 0xb8100008 [ Levels 9 - 12 ] | |
25 | * ILSEL3 - 0xb810000a [ Levels 13 - 15 ] | |
26 | * | |
27 | * With each level being relative to an ilsel_source_t. | |
28 | */ | |
29 | #define ILSEL_BASE 0xb8100004 | |
30 | #define ILSEL_LEVELS 15 | |
31 | ||
32 | /* | |
33 | * ILSEL level map, in descending order from the highest level down. | |
34 | * | |
35 | * Supported levels are 1 - 15 spread across ILSEL0 - ILSEL4, mapping | |
36 | * directly to IRLs. As the IRQs are numbered in reverse order relative | |
37 | * to the interrupt level, the level map is carefully managed to ensure a | |
38 | * 1:1 mapping between the bit position and the IRQ number. | |
39 | * | |
40 | * This careful constructions allows ilsel_enable*() to be referenced | |
41 | * directly for hooking up an ILSEL set and getting back an IRQ which can | |
42 | * subsequently be used for internal accounting in the (optional) disable | |
43 | * path. | |
44 | */ | |
45 | static unsigned long ilsel_level_map; | |
46 | ||
47 | static inline unsigned int ilsel_offset(unsigned int bit) | |
48 | { | |
49 | return ILSEL_LEVELS - bit - 1; | |
50 | } | |
51 | ||
52 | static inline unsigned long mk_ilsel_addr(unsigned int bit) | |
53 | { | |
54 | return ILSEL_BASE + ((ilsel_offset(bit) >> 1) & ~0x1); | |
55 | } | |
56 | ||
57 | static inline unsigned int mk_ilsel_shift(unsigned int bit) | |
58 | { | |
59 | return (ilsel_offset(bit) & 0x3) << 2; | |
60 | } | |
61 | ||
62 | static void __ilsel_enable(ilsel_source_t set, unsigned int bit) | |
63 | { | |
64 | unsigned int tmp, shift; | |
65 | unsigned long addr; | |
66 | ||
67 | addr = mk_ilsel_addr(bit); | |
68 | shift = mk_ilsel_shift(bit); | |
69 | ||
70 | pr_debug("%s: bit#%d: addr - 0x%08lx (shift %d, set %d)\n", | |
866e6b9e | 71 | __func__, bit, addr, shift, set); |
fef96086 | 72 | |
9d56dd3b | 73 | tmp = __raw_readw(addr); |
fef96086 PM |
74 | tmp &= ~(0xf << shift); |
75 | tmp |= set << shift; | |
9d56dd3b | 76 | __raw_writew(tmp, addr); |
fef96086 PM |
77 | } |
78 | ||
79 | /** | |
80 | * ilsel_enable - Enable an ILSEL set. | |
81 | * @set: ILSEL source (see ilsel_source_t enum in include/asm-sh/ilsel.h). | |
82 | * | |
83 | * Enables a given non-aliased ILSEL source (<= ILSEL_KEY) at the highest | |
84 | * available interrupt level. Callers should take care to order callsites | |
85 | * noting descending interrupt levels. Aliasing FPGA and external board | |
86 | * IRQs need to use ilsel_enable_fixed(). | |
87 | * | |
88 | * The return value is an IRQ number that can later be taken down with | |
89 | * ilsel_disable(). | |
90 | */ | |
91 | int ilsel_enable(ilsel_source_t set) | |
92 | { | |
93 | unsigned int bit; | |
94 | ||
95 | /* Aliased sources must use ilsel_enable_fixed() */ | |
96 | BUG_ON(set > ILSEL_KEY); | |
97 | ||
98 | do { | |
99 | bit = find_first_zero_bit(&ilsel_level_map, ILSEL_LEVELS); | |
100 | } while (test_and_set_bit(bit, &ilsel_level_map)); | |
101 | ||
102 | __ilsel_enable(set, bit); | |
103 | ||
104 | return bit; | |
105 | } | |
106 | EXPORT_SYMBOL_GPL(ilsel_enable); | |
107 | ||
108 | /** | |
109 | * ilsel_enable_fixed - Enable an ILSEL set at a fixed interrupt level | |
110 | * @set: ILSEL source (see ilsel_source_t enum in include/asm-sh/ilsel.h). | |
111 | * @level: Interrupt level (1 - 15) | |
112 | * | |
113 | * Enables a given ILSEL source at a fixed interrupt level. Necessary | |
114 | * both for level reservation as well as for aliased sources that only | |
115 | * exist on special ILSEL#s. | |
116 | * | |
117 | * Returns an IRQ number (as ilsel_enable()). | |
118 | */ | |
119 | int ilsel_enable_fixed(ilsel_source_t set, unsigned int level) | |
120 | { | |
121 | unsigned int bit = ilsel_offset(level - 1); | |
122 | ||
123 | if (test_and_set_bit(bit, &ilsel_level_map)) | |
124 | return -EBUSY; | |
125 | ||
126 | __ilsel_enable(set, bit); | |
127 | ||
128 | return bit; | |
129 | } | |
130 | EXPORT_SYMBOL_GPL(ilsel_enable_fixed); | |
131 | ||
132 | /** | |
133 | * ilsel_disable - Disable an ILSEL set | |
134 | * @irq: Bit position for ILSEL set value (retval from enable routines) | |
135 | * | |
136 | * Disable a previously enabled ILSEL set. | |
137 | */ | |
138 | void ilsel_disable(unsigned int irq) | |
139 | { | |
140 | unsigned long addr; | |
141 | unsigned int tmp; | |
142 | ||
af24fdc1 | 143 | addr = mk_ilsel_addr(irq); |
fef96086 | 144 | |
9d56dd3b | 145 | tmp = __raw_readw(addr); |
af24fdc1 | 146 | tmp &= ~(0xf << mk_ilsel_shift(irq)); |
9d56dd3b | 147 | __raw_writew(tmp, addr); |
fef96086 | 148 | |
af24fdc1 | 149 | clear_bit(irq, &ilsel_level_map); |
fef96086 PM |
150 | } |
151 | EXPORT_SYMBOL_GPL(ilsel_disable); |