1 /* $Id: imx23_icoll.c,v 1.4 2022/06/25 12:41:56 jmcneill Exp $ */
2 
3 /*
4  * Copyright (c) 2012 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Petri Laakso.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  * POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 #include <sys/param.h>
33 #include <sys/bus.h>
34 #include <sys/cpu.h>
35 #include <sys/device.h>
36 #include <sys/errno.h>
37 #include <sys/systm.h>
38 
39 #include <arm/cpufunc.h>
40 
41 #define _INTR_PRIVATE
42 #include <arm/pic/picvar.h>
43 
44 #include <arm/imx/imx23_icollreg.h>
45 #include <arm/imx/imx23var.h>
46 
47 #define ICOLL_SOFT_RST_LOOP 455                   /* At least 1 us ... */
48 #define ICOLL_READ(sc, reg)                                                     \
49           bus_space_read_4(sc->sc_iot, sc->sc_hdl, (reg))
50 #define ICOLL_WRITE(sc, reg, val)                                               \
51           bus_space_write_4(sc->sc_iot, sc->sc_hdl, (reg), (val))
52 
53 #define ICOLL_IRQ_REG_SIZE 0x10
54 #define ICOLL_CLR_IRQ(sc, irq)                                                            \
55           ICOLL_WRITE(sc, HW_ICOLL_INTERRUPT0_CLR +                             \
56                               (irq) * ICOLL_IRQ_REG_SIZE,                       \
57                               HW_ICOLL_INTERRUPT_ENABLE)
58 #define ICOLL_SET_IRQ(sc, irq)                                                            \
59           ICOLL_WRITE(sc, HW_ICOLL_INTERRUPT0_SET +                             \
60                               (irq) * ICOLL_IRQ_REG_SIZE,                       \
61                               HW_ICOLL_INTERRUPT_ENABLE)
62 #define ICOLL_GET_PRIO(sc, irq)                                                           \
63           __SHIFTOUT(ICOLL_READ(sc, HW_ICOLL_INTERRUPT0 +                       \
64                               (irq) * ICOLL_IRQ_REG_SIZE),                      \
65                               HW_ICOLL_INTERRUPT_PRIORITY)
66 
67 #define PICTOSOFTC(pic)                                                                   \
68           ((struct icoll_softc *)((char *)(pic) -                               \
69                     offsetof(struct icoll_softc, sc_pic)))
70 
71 /*
72  * pic callbacks.
73  */
74 static void         icoll_unblock_irqs(struct pic_softc *, size_t, uint32_t);
75 static void         icoll_block_irqs(struct pic_softc *, size_t, uint32_t);
76 static int          icoll_find_pending_irqs(struct pic_softc *);
77 static void         icoll_establish_irq(struct pic_softc *, struct intrsource *);
78 static void         icoll_source_name(struct pic_softc *, int, char *, size_t);
79 static void         icoll_set_priority(struct pic_softc *, int);
80 
81 /*
82  * autoconf(9) callbacks.
83  */
84 static int          icoll_match(device_t, cfdata_t, void *);
85 static void         icoll_attach(device_t, device_t, void *);
86 static int          icoll_activate(device_t, enum devact);
87 
88 /*
89  * ARM interrupt handler.
90  */
91 void imx23_intr_dispatch(struct clockframe *);
92 
93 const static struct pic_ops icoll_pic_ops = {
94           .pic_unblock_irqs = icoll_unblock_irqs,
95           .pic_block_irqs = icoll_block_irqs,
96           .pic_find_pending_irqs = icoll_find_pending_irqs,
97           .pic_establish_irq = icoll_establish_irq,
98           .pic_source_name = icoll_source_name,
99           .pic_set_priority = icoll_set_priority
100 };
101 
102 struct icoll_softc {
103           device_t sc_dev;
104           struct pic_softc sc_pic;
105           bus_space_tag_t sc_iot;
106           bus_space_handle_t sc_hdl;
107 };
108 
109 /* For IRQ handler. */
110 static struct icoll_softc *icoll_sc;
111 
112 /*
113  * Private to driver.
114  */
115 static void         icoll_reset(struct icoll_softc *);
116 
117 CFATTACH_DECL3_NEW(icoll,
118           sizeof(struct icoll_softc),
119           icoll_match,
120           icoll_attach,
121           NULL,
122           icoll_activate,
123           NULL,
124           NULL,
125           0);
126 
127 /*
128  * ARM interrupt handler.
129  */
130 void
imx23_intr_dispatch(struct clockframe * frame)131 imx23_intr_dispatch(struct clockframe *frame)
132 {
133           struct cpu_info * const ci = curcpu();
134           struct pic_softc *pic_sc;
135           int saved_spl;
136           uint8_t irq;
137           uint8_t prio;
138 
139           pic_sc = &icoll_sc->sc_pic;
140 
141           ci->ci_data.cpu_nintr++;
142 
143           /* Save current spl. */
144           saved_spl = curcpl();
145 
146           /* IRQ to be handled. */
147           irq = __SHIFTOUT(ICOLL_READ(icoll_sc, HW_ICOLL_STAT),
148               HW_ICOLL_STAT_VECTOR_NUMBER);
149 
150           /* Save IRQ's priority. Acknowledge it later. */
151           prio = ICOLL_GET_PRIO(icoll_sc, irq);
152 
153           /*
154            * Notify ICOLL to deassert IRQ before re-enabling the IRQ's.
155            * This is done by writing anything to HW_ICOLL_VECTOR.
156            */
157           ICOLL_WRITE(icoll_sc, HW_ICOLL_VECTOR,
158               __SHIFTIN(0x3fffffff, HW_ICOLL_VECTOR_IRQVECTOR));
159 
160           /* Bogus IRQ. */
161           if (irq == 0x7f) {
162                     cpsie(I32_bit);
163                     ICOLL_WRITE(icoll_sc, HW_ICOLL_LEVELACK, (1<<prio));
164                     cpsid(I32_bit);
165                     return;
166           }
167 
168           /* Raise the spl to the level of the IRQ. */
169           if (pic_sc->pic_sources[irq]->is_ipl > ci->ci_cpl)
170                     saved_spl = _splraise(pic_sc->pic_sources[irq]->is_ipl);
171 
172           /* Call the handler registered for the IRQ. */
173           cpsie(I32_bit);
174           pic_dispatch(pic_sc->pic_sources[irq], frame);
175 
176           /*
177            * Acknowledge the IRQ by writing its priority to HW_ICOLL_LEVELACK.
178            * Interrupts should be enabled.
179            */
180           ICOLL_WRITE(icoll_sc, HW_ICOLL_LEVELACK, (1<<prio));
181           cpsid(I32_bit);
182 
183           /* Restore the saved spl. */
184           splx(saved_spl);
185 
186           return;
187 }
188 
189 /*
190  * pic callbacks.
191  */
192 static void
icoll_unblock_irqs(struct pic_softc * pic,size_t irq_base,uint32_t irq_mask)193 icoll_unblock_irqs(struct pic_softc *pic, size_t irq_base, uint32_t irq_mask)
194 {
195           struct icoll_softc *sc = PICTOSOFTC(pic);
196           uint8_t b;
197 
198           for (;;) {
199                     b = ffs(irq_mask);
200                     if (b == 0) break;
201                     b--;      /* Zero based index. */
202                     ICOLL_SET_IRQ(sc, irq_base + b);
203                     irq_mask &= ~(1<<b);
204           }
205 
206           return;
207 }
208 
209 static void
icoll_block_irqs(struct pic_softc * pic,size_t irq_base,uint32_t irq_mask)210 icoll_block_irqs(struct pic_softc *pic, size_t irq_base, uint32_t irq_mask)
211 {
212           struct icoll_softc *sc = PICTOSOFTC(pic);
213           uint8_t b;
214 
215           for (;;) {
216                     b = ffs(irq_mask);
217                     if (b == 0) break;
218                     b--;      /* Zero based index. */
219                     ICOLL_CLR_IRQ(sc, irq_base + b);
220                     irq_mask &= ~(1<<b);
221           }
222 
223           return;
224 }
225 
226 static int
icoll_find_pending_irqs(struct pic_softc * pic)227 icoll_find_pending_irqs(struct pic_softc *pic)
228 {
229           return 0; /* ICOLL HW doesn't provide list of pending interrupts. */
230 }
231 
232 static void
icoll_establish_irq(struct pic_softc * pic,struct intrsource * is)233 icoll_establish_irq(struct pic_softc *pic, struct intrsource *is)
234 {
235           return; /* Nothing to establish. */
236 }
237 
238 static void
icoll_source_name(struct pic_softc * pic,int irq,char * is_source,size_t size)239 icoll_source_name(struct pic_softc *pic, int irq, char *is_source, size_t size)
240 {
241           snprintf(is_source, size, "irq %d", irq);
242 }
243 
244 /*
245  * Set new interrupt priority level by enabling or disabling IRQ's.
246  */
247 static void
icoll_set_priority(struct pic_softc * pic,int newipl)248 icoll_set_priority(struct pic_softc *pic, int newipl)
249 {
250           struct icoll_softc *sc = PICTOSOFTC(pic);
251           struct intrsource *is;
252           int i;
253 
254           register_t psw = DISABLE_INTERRUPT_SAVE();
255 
256           for (i = 0; i < pic->pic_maxsources; i++) {
257                     is = pic->pic_sources[i];
258                     if (is == NULL)
259                               continue;
260                     if (is->is_ipl > newipl)
261                               ICOLL_SET_IRQ(sc, pic->pic_irqbase + is->is_irq);
262                     else
263                               ICOLL_CLR_IRQ(sc, pic->pic_irqbase + is->is_irq);
264           }
265 
266           curcpu()->ci_cpl = newipl;
267 
268           if ((psw & I32_bit) == 0) {
269                     ENABLE_INTERRUPT();
270           }
271 }
272 
273 /*
274  * autoconf(9) callbacks.
275  */
276 static int
icoll_match(device_t parent,cfdata_t match,void * aux)277 icoll_match(device_t parent, cfdata_t match, void *aux)
278 {
279           struct apb_attach_args *aa = aux;
280 
281           if ((aa->aa_addr == HW_ICOLL_BASE) && (aa->aa_size == HW_ICOLL_SIZE))
282                     return 1;
283 
284           return 0;
285 }
286 
287 static void
icoll_attach(device_t parent,device_t self,void * aux)288 icoll_attach(device_t parent, device_t self, void *aux)
289 {
290           static int icoll_attached = 0;
291           struct icoll_softc *sc = device_private(self);
292           struct apb_attach_args *aa = aux;
293 
294           if (icoll_attached)
295                     return;
296 
297           icoll_sc = sc;
298 
299           sc->sc_dev = self;
300           sc->sc_iot = aa->aa_iot;
301 
302           sc->sc_pic.pic_maxsources = IRQ_LAST + 1;
303           sc->sc_pic.pic_ops = &icoll_pic_ops;
304           strlcpy(sc->sc_pic.pic_name, device_xname(self),
305               sizeof(sc->sc_pic.pic_name));
306 
307           if (bus_space_map(sc->sc_iot,
308               aa->aa_addr, aa->aa_size, 0, &(sc->sc_hdl))) {
309                     aprint_error_dev(sc->sc_dev, "unable to map bus space\n");
310                     return;
311           }
312 
313           icoll_reset(sc);
314           pic_add(&sc->sc_pic, 0);
315           aprint_normal("\n");
316           icoll_attached = 1;
317 
318           return;
319 }
320 
321 static int
icoll_activate(device_t self,enum devact act)322 icoll_activate(device_t self, enum devact act)
323 {
324           return EOPNOTSUPP;
325 }
326 
327 /*
328  * Reset the ICOLL block.
329  *
330  * Inspired by i.MX23 RM "39.3.10 Correct Way to Soft Reset a Block"
331  */
332 static void
icoll_reset(struct icoll_softc * sc)333 icoll_reset(struct icoll_softc *sc)
334 {
335           unsigned int loop;
336 
337           /*
338            * Prepare for soft-reset by making sure that SFTRST is not currently
339            * asserted. Also clear CLKGATE so we can wait for its assertion below.
340            */
341           ICOLL_WRITE(sc, HW_ICOLL_CTRL_CLR, HW_ICOLL_CTRL_SFTRST);
342 
343           /* Wait at least a microsecond for SFTRST to deassert. */
344           loop = 0;
345           while ((ICOLL_READ(sc, HW_ICOLL_CTRL) & HW_ICOLL_CTRL_SFTRST) ||
346               (loop < ICOLL_SOFT_RST_LOOP)) {
347                     loop++;
348           }
349 
350           /* Clear CLKGATE so we can wait for its assertion below. */
351           ICOLL_WRITE(sc, HW_ICOLL_CTRL_CLR, HW_ICOLL_CTRL_CLKGATE);
352 
353           /* Soft-reset the block. */
354           ICOLL_WRITE(sc, HW_ICOLL_CTRL_SET, HW_ICOLL_CTRL_SFTRST);
355 
356           /* Wait until clock is in the gated state. */
357           while (!(ICOLL_READ(sc, HW_ICOLL_CTRL) & HW_ICOLL_CTRL_CLKGATE));
358 
359           /* Bring block out of reset. */
360           ICOLL_WRITE(sc, HW_ICOLL_CTRL_CLR, HW_ICOLL_CTRL_SFTRST);
361 
362           loop = 0;
363           while ((ICOLL_READ(sc, HW_ICOLL_CTRL) & HW_ICOLL_CTRL_SFTRST) ||
364               (loop < ICOLL_SOFT_RST_LOOP)) {
365                     loop++;
366           }
367 
368           ICOLL_WRITE(sc, HW_ICOLL_CTRL_CLR, HW_ICOLL_CTRL_CLKGATE);
369 
370           /* Wait until clock is in the NON-gated state. */
371           while (ICOLL_READ(sc, HW_ICOLL_CTRL) & HW_ICOLL_CTRL_CLKGATE);
372 
373           return;
374 }
375