diff options
Diffstat (limited to 'arch/xtensa/variants/s6000/gpio.c')
-rw-r--r-- | arch/xtensa/variants/s6000/gpio.c | 163 |
1 files changed, 162 insertions, 1 deletions
diff --git a/arch/xtensa/variants/s6000/gpio.c b/arch/xtensa/variants/s6000/gpio.c index 79317fdcf14c..380a70fff756 100644 --- a/arch/xtensa/variants/s6000/gpio.c +++ b/arch/xtensa/variants/s6000/gpio.c | |||
@@ -4,15 +4,20 @@ | |||
4 | * Copyright (c) 2009 emlix GmbH | 4 | * Copyright (c) 2009 emlix GmbH |
5 | * Authors: Oskar Schirmer <os@emlix.com> | 5 | * Authors: Oskar Schirmer <os@emlix.com> |
6 | * Johannes Weiner <jw@emlix.com> | 6 | * Johannes Weiner <jw@emlix.com> |
7 | * Daniel Gloeckner <dg@emlix.com> | ||
7 | */ | 8 | */ |
9 | #include <linux/bitops.h> | ||
8 | #include <linux/kernel.h> | 10 | #include <linux/kernel.h> |
9 | #include <linux/module.h> | 11 | #include <linux/module.h> |
10 | #include <linux/init.h> | 12 | #include <linux/init.h> |
11 | #include <linux/io.h> | 13 | #include <linux/io.h> |
14 | #include <linux/irq.h> | ||
12 | #include <linux/gpio.h> | 15 | #include <linux/gpio.h> |
13 | 16 | ||
14 | #include <variant/hardware.h> | 17 | #include <variant/hardware.h> |
15 | 18 | ||
19 | #define IRQ_BASE XTENSA_NR_IRQS | ||
20 | |||
16 | #define S6_GPIO_DATA 0x000 | 21 | #define S6_GPIO_DATA 0x000 |
17 | #define S6_GPIO_IS 0x404 | 22 | #define S6_GPIO_IS 0x404 |
18 | #define S6_GPIO_IBE 0x408 | 23 | #define S6_GPIO_IBE 0x408 |
@@ -52,19 +57,175 @@ static void set(struct gpio_chip *chip, unsigned int off, int val) | |||
52 | writeb(val ? ~0 : 0, S6_REG_GPIO + S6_GPIO_DATA + S6_GPIO_OFFSET(off)); | 57 | writeb(val ? ~0 : 0, S6_REG_GPIO + S6_GPIO_DATA + S6_GPIO_OFFSET(off)); |
53 | } | 58 | } |
54 | 59 | ||
60 | static int to_irq(struct gpio_chip *chip, unsigned offset) | ||
61 | { | ||
62 | if (offset < 8) | ||
63 | return offset + IRQ_BASE; | ||
64 | return -EINVAL; | ||
65 | } | ||
66 | |||
55 | static struct gpio_chip gpiochip = { | 67 | static struct gpio_chip gpiochip = { |
56 | .owner = THIS_MODULE, | 68 | .owner = THIS_MODULE, |
57 | .direction_input = direction_input, | 69 | .direction_input = direction_input, |
58 | .get = get, | 70 | .get = get, |
59 | .direction_output = direction_output, | 71 | .direction_output = direction_output, |
60 | .set = set, | 72 | .set = set, |
73 | .to_irq = to_irq, | ||
61 | .base = 0, | 74 | .base = 0, |
62 | .ngpio = 24, | 75 | .ngpio = 24, |
63 | .can_sleep = 0, /* no blocking io needed */ | 76 | .can_sleep = 0, /* no blocking io needed */ |
64 | .exported = 0, /* no exporting to userspace */ | 77 | .exported = 0, /* no exporting to userspace */ |
65 | }; | 78 | }; |
66 | 79 | ||
67 | int s6_gpio_init(void) | 80 | int s6_gpio_init(u32 afsel) |
68 | { | 81 | { |
82 | writeb(afsel, S6_REG_GPIO + S6_GPIO_BANK(0) + S6_GPIO_AFSEL); | ||
83 | writeb(afsel >> 8, S6_REG_GPIO + S6_GPIO_BANK(1) + S6_GPIO_AFSEL); | ||
84 | writeb(afsel >> 16, S6_REG_GPIO + S6_GPIO_BANK(2) + S6_GPIO_AFSEL); | ||
69 | return gpiochip_add(&gpiochip); | 85 | return gpiochip_add(&gpiochip); |
70 | } | 86 | } |
87 | |||
88 | static void ack(unsigned int irq) | ||
89 | { | ||
90 | writeb(1 << (irq - IRQ_BASE), S6_REG_GPIO + S6_GPIO_IC); | ||
91 | } | ||
92 | |||
93 | static void mask(unsigned int irq) | ||
94 | { | ||
95 | u8 r = readb(S6_REG_GPIO + S6_GPIO_IE); | ||
96 | r &= ~(1 << (irq - IRQ_BASE)); | ||
97 | writeb(r, S6_REG_GPIO + S6_GPIO_IE); | ||
98 | } | ||
99 | |||
100 | static void unmask(unsigned int irq) | ||
101 | { | ||
102 | u8 m = readb(S6_REG_GPIO + S6_GPIO_IE); | ||
103 | m |= 1 << (irq - IRQ_BASE); | ||
104 | writeb(m, S6_REG_GPIO + S6_GPIO_IE); | ||
105 | } | ||
106 | |||
107 | static int set_type(unsigned int irq, unsigned int type) | ||
108 | { | ||
109 | const u8 m = 1 << (irq - IRQ_BASE); | ||
110 | irq_flow_handler_t handler; | ||
111 | struct irq_desc *desc; | ||
112 | u8 reg; | ||
113 | |||
114 | if (type == IRQ_TYPE_PROBE) { | ||
115 | if ((readb(S6_REG_GPIO + S6_GPIO_BANK(0) + S6_GPIO_AFSEL) & m) | ||
116 | || (readb(S6_REG_GPIO + S6_GPIO_BANK(0) + S6_GPIO_IE) & m) | ||
117 | || readb(S6_REG_GPIO + S6_GPIO_BANK(0) + S6_GPIO_DIR | ||
118 | + S6_GPIO_MASK(irq - IRQ_BASE))) | ||
119 | return 0; | ||
120 | type = IRQ_TYPE_EDGE_BOTH; | ||
121 | } | ||
122 | |||
123 | reg = readb(S6_REG_GPIO + S6_GPIO_BANK(0) + S6_GPIO_IS); | ||
124 | if (type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH)) { | ||
125 | reg |= m; | ||
126 | handler = handle_level_irq; | ||
127 | } else { | ||
128 | reg &= ~m; | ||
129 | handler = handle_edge_irq; | ||
130 | } | ||
131 | writeb(reg, S6_REG_GPIO + S6_GPIO_BANK(0) + S6_GPIO_IS); | ||
132 | desc = irq_to_desc(irq); | ||
133 | desc->handle_irq = handler; | ||
134 | |||
135 | reg = readb(S6_REG_GPIO + S6_GPIO_BANK(0) + S6_GPIO_IEV); | ||
136 | if (type & (IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_EDGE_RISING)) | ||
137 | reg |= m; | ||
138 | else | ||
139 | reg &= ~m; | ||
140 | writeb(reg, S6_REG_GPIO + S6_GPIO_BANK(0) + S6_GPIO_IEV); | ||
141 | |||
142 | reg = readb(S6_REG_GPIO + S6_GPIO_BANK(0) + S6_GPIO_IBE); | ||
143 | if ((type & IRQ_TYPE_EDGE_BOTH) == IRQ_TYPE_EDGE_BOTH) | ||
144 | reg |= m; | ||
145 | else | ||
146 | reg &= ~m; | ||
147 | writeb(reg, S6_REG_GPIO + S6_GPIO_BANK(0) + S6_GPIO_IBE); | ||
148 | return 0; | ||
149 | } | ||
150 | |||
151 | static struct irq_chip gpioirqs = { | ||
152 | .name = "GPIO", | ||
153 | .ack = ack, | ||
154 | .mask = mask, | ||
155 | .unmask = unmask, | ||
156 | .set_type = set_type, | ||
157 | }; | ||
158 | |||
159 | static u8 demux_masks[4]; | ||
160 | |||
161 | static void demux_irqs(unsigned int irq, struct irq_desc *desc) | ||
162 | { | ||
163 | u8 *mask = get_irq_desc_data(desc); | ||
164 | u8 pending; | ||
165 | int cirq; | ||
166 | |||
167 | desc->chip->mask(irq); | ||
168 | desc->chip->ack(irq); | ||
169 | pending = readb(S6_REG_GPIO + S6_GPIO_BANK(0) + S6_GPIO_MIS) & *mask; | ||
170 | cirq = IRQ_BASE - 1; | ||
171 | while (pending) { | ||
172 | int n = ffs(pending); | ||
173 | cirq += n; | ||
174 | pending >>= n; | ||
175 | generic_handle_irq(cirq); | ||
176 | } | ||
177 | desc->chip->unmask(irq); | ||
178 | } | ||
179 | |||
180 | extern const signed char *platform_irq_mappings[XTENSA_NR_IRQS]; | ||
181 | |||
182 | void __init variant_init_irq(void) | ||
183 | { | ||
184 | int irq, n; | ||
185 | writeb(0, S6_REG_GPIO + S6_GPIO_BANK(0) + S6_GPIO_IE); | ||
186 | for (irq = n = 0; irq < XTENSA_NR_IRQS; irq++) { | ||
187 | const signed char *mapping = platform_irq_mappings[irq]; | ||
188 | int alone = 1; | ||
189 | u8 mask; | ||
190 | if (!mapping) | ||
191 | continue; | ||
192 | for(mask = 0; *mapping != -1; mapping++) | ||
193 | switch (*mapping) { | ||
194 | case S6_INTC_GPIO(0): | ||
195 | mask |= 1 << 0; | ||
196 | break; | ||
197 | case S6_INTC_GPIO(1): | ||
198 | mask |= 1 << 1; | ||
199 | break; | ||
200 | case S6_INTC_GPIO(2): | ||
201 | mask |= 1 << 2; | ||
202 | break; | ||
203 | case S6_INTC_GPIO(3): | ||
204 | mask |= 0x1f << 3; | ||
205 | break; | ||
206 | default: | ||
207 | alone = 0; | ||
208 | } | ||
209 | if (mask) { | ||
210 | int cirq, i; | ||
211 | if (!alone) { | ||
212 | printk(KERN_ERR "chained irq chips can't share" | ||
213 | " parent irq %i\n", irq); | ||
214 | continue; | ||
215 | } | ||
216 | demux_masks[n] = mask; | ||
217 | cirq = IRQ_BASE - 1; | ||
218 | do { | ||
219 | i = ffs(mask); | ||
220 | cirq += i; | ||
221 | mask >>= i; | ||
222 | set_irq_chip(cirq, &gpioirqs); | ||
223 | set_irq_type(irq, IRQ_TYPE_LEVEL_LOW); | ||
224 | } while (mask); | ||
225 | set_irq_data(irq, demux_masks + n); | ||
226 | set_irq_chained_handler(irq, demux_irqs); | ||
227 | if (++n == ARRAY_SIZE(demux_masks)) | ||
228 | break; | ||
229 | } | ||
230 | } | ||
231 | } | ||