Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* $Id: boardergo.c,v 1.5.6.7 2001/11/06 21:58:19 kai Exp $ |
2 | * | |
3 | * Linux driver for HYSDN cards, specific routines for ergo type boards. | |
4 | * | |
5 | * Author Werner Cornelius (werner@titro.de) for Hypercope GmbH | |
6 | * Copyright 1999 by Werner Cornelius (werner@titro.de) | |
7 | * | |
8 | * This software may be used and distributed according to the terms | |
9 | * of the GNU General Public License, incorporated herein by reference. | |
10 | * | |
11 | * As all Linux supported cards Champ2, Ergo and Metro2/4 use the same | |
12 | * DPRAM interface and layout with only minor differences all related | |
13 | * stuff is done here, not in separate modules. | |
14 | * | |
15 | */ | |
16 | ||
1da177e4 LT |
17 | #include <linux/signal.h> |
18 | #include <linux/kernel.h> | |
19 | #include <linux/ioport.h> | |
20 | #include <linux/interrupt.h> | |
21 | #include <linux/vmalloc.h> | |
22 | #include <linux/delay.h> | |
23 | #include <asm/io.h> | |
24 | ||
25 | #include "hysdn_defs.h" | |
26 | #include "boardergo.h" | |
27 | ||
28 | #define byteout(addr,val) outb(val,addr) | |
29 | #define bytein(addr) inb(addr) | |
30 | ||
31 | /***************************************************/ | |
32 | /* The cards interrupt handler. Called from system */ | |
33 | /***************************************************/ | |
34 | static irqreturn_t | |
7d12e780 | 35 | ergo_interrupt(int intno, void *dev_id) |
1da177e4 LT |
36 | { |
37 | hysdn_card *card = dev_id; /* parameter from irq */ | |
38 | tErgDpram *dpr; | |
c721bcce AM |
39 | unsigned long flags; |
40 | unsigned char volatile b; | |
1da177e4 LT |
41 | |
42 | if (!card) | |
43 | return IRQ_NONE; /* error -> spurious interrupt */ | |
44 | if (!card->irq_enabled) | |
45 | return IRQ_NONE; /* other device interrupting or irq switched off */ | |
46 | ||
0d9ba869 | 47 | spin_lock_irqsave(&card->hysdn_lock, flags); /* no further irqs allowed */ |
1da177e4 LT |
48 | |
49 | if (!(bytein(card->iobase + PCI9050_INTR_REG) & PCI9050_INTR_REG_STAT1)) { | |
0d9ba869 | 50 | spin_unlock_irqrestore(&card->hysdn_lock, flags); /* restore old state */ |
1da177e4 LT |
51 | return IRQ_NONE; /* no interrupt requested by E1 */ |
52 | } | |
53 | /* clear any pending ints on the board */ | |
54 | dpr = card->dpram; | |
55 | b = dpr->ToPcInt; /* clear for ergo */ | |
56 | b |= dpr->ToPcIntMetro; /* same for metro */ | |
57 | b |= dpr->ToHyInt; /* and for champ */ | |
58 | ||
59 | /* start kernel task immediately after leaving all interrupts */ | |
60 | if (!card->hw_lock) | |
61 | schedule_work(&card->irq_queue); | |
0d9ba869 | 62 | spin_unlock_irqrestore(&card->hysdn_lock, flags); |
1da177e4 LT |
63 | return IRQ_HANDLED; |
64 | } /* ergo_interrupt */ | |
65 | ||
66 | /******************************************************************************/ | |
67 | /* ergo_irq_bh is the function called by the immediate kernel task list after */ | |
68 | /* being activated with queue_task and no interrupts active. This task is the */ | |
69 | /* only one handling data transfer from or to the card after booting. The task */ | |
70 | /* may be queued from everywhere (interrupts included). */ | |
71 | /******************************************************************************/ | |
72 | static void | |
3e577a80 | 73 | ergo_irq_bh(struct work_struct *ugli_api) |
1da177e4 | 74 | { |
3e577a80 | 75 | hysdn_card * card = container_of(ugli_api, hysdn_card, irq_queue); |
1da177e4 LT |
76 | tErgDpram *dpr; |
77 | int again; | |
c721bcce | 78 | unsigned long flags; |
1da177e4 LT |
79 | |
80 | if (card->state != CARD_STATE_RUN) | |
81 | return; /* invalid call */ | |
82 | ||
83 | dpr = card->dpram; /* point to DPRAM */ | |
84 | ||
0d9ba869 | 85 | spin_lock_irqsave(&card->hysdn_lock, flags); |
1da177e4 | 86 | if (card->hw_lock) { |
0d9ba869 | 87 | spin_unlock_irqrestore(&card->hysdn_lock, flags); /* hardware currently unavailable */ |
1da177e4 LT |
88 | return; |
89 | } | |
90 | card->hw_lock = 1; /* we now lock the hardware */ | |
91 | ||
92 | do { | |
93 | sti(); /* reenable other ints */ | |
94 | again = 0; /* assume loop not to be repeated */ | |
95 | ||
96 | if (!dpr->ToHyFlag) { | |
97 | /* we are able to send a buffer */ | |
98 | ||
99 | if (hysdn_sched_tx(card, dpr->ToHyBuf, &dpr->ToHySize, &dpr->ToHyChannel, | |
100 | ERG_TO_HY_BUF_SIZE)) { | |
101 | dpr->ToHyFlag = 1; /* enable tx */ | |
102 | again = 1; /* restart loop */ | |
103 | } | |
104 | } /* we are able to send a buffer */ | |
105 | if (dpr->ToPcFlag) { | |
106 | /* a message has arrived for us, handle it */ | |
107 | ||
108 | if (hysdn_sched_rx(card, dpr->ToPcBuf, dpr->ToPcSize, dpr->ToPcChannel)) { | |
109 | dpr->ToPcFlag = 0; /* we worked the data */ | |
110 | again = 1; /* restart loop */ | |
111 | } | |
112 | } /* a message has arrived for us */ | |
113 | cli(); /* no further ints */ | |
114 | if (again) { | |
115 | dpr->ToHyInt = 1; | |
116 | dpr->ToPcInt = 1; /* interrupt to E1 for all cards */ | |
117 | } else | |
118 | card->hw_lock = 0; /* free hardware again */ | |
119 | } while (again); /* until nothing more to do */ | |
120 | ||
0d9ba869 | 121 | spin_unlock_irqrestore(&card->hysdn_lock, flags); |
1da177e4 LT |
122 | } /* ergo_irq_bh */ |
123 | ||
124 | ||
125 | /*********************************************************/ | |
126 | /* stop the card (hardware reset) and disable interrupts */ | |
127 | /*********************************************************/ | |
128 | static void | |
129 | ergo_stopcard(hysdn_card * card) | |
130 | { | |
c721bcce AM |
131 | unsigned long flags; |
132 | unsigned char val; | |
1da177e4 LT |
133 | |
134 | hysdn_net_release(card); /* first release the net device if existing */ | |
135 | #ifdef CONFIG_HYSDN_CAPI | |
136 | hycapi_capi_stop(card); | |
137 | #endif /* CONFIG_HYSDN_CAPI */ | |
0d9ba869 | 138 | spin_lock_irqsave(&card->hysdn_lock, flags); |
1da177e4 LT |
139 | val = bytein(card->iobase + PCI9050_INTR_REG); /* get actual value */ |
140 | val &= ~(PCI9050_INTR_REG_ENPCI | PCI9050_INTR_REG_EN1); /* mask irq */ | |
141 | byteout(card->iobase + PCI9050_INTR_REG, val); | |
142 | card->irq_enabled = 0; | |
143 | byteout(card->iobase + PCI9050_USER_IO, PCI9050_E1_RESET); /* reset E1 processor */ | |
144 | card->state = CARD_STATE_UNUSED; | |
145 | card->err_log_state = ERRLOG_STATE_OFF; /* currently no log active */ | |
146 | ||
0d9ba869 | 147 | spin_unlock_irqrestore(&card->hysdn_lock, flags); |
1da177e4 LT |
148 | } /* ergo_stopcard */ |
149 | ||
150 | /**************************************************************************/ | |
151 | /* enable or disable the cards error log. The event is queued if possible */ | |
152 | /**************************************************************************/ | |
153 | static void | |
154 | ergo_set_errlog_state(hysdn_card * card, int on) | |
155 | { | |
c721bcce | 156 | unsigned long flags; |
1da177e4 LT |
157 | |
158 | if (card->state != CARD_STATE_RUN) { | |
159 | card->err_log_state = ERRLOG_STATE_OFF; /* must be off */ | |
160 | return; | |
161 | } | |
0d9ba869 | 162 | spin_lock_irqsave(&card->hysdn_lock, flags); |
1da177e4 LT |
163 | |
164 | if (((card->err_log_state == ERRLOG_STATE_OFF) && !on) || | |
165 | ((card->err_log_state == ERRLOG_STATE_ON) && on)) { | |
0d9ba869 | 166 | spin_unlock_irqrestore(&card->hysdn_lock, flags); |
1da177e4 LT |
167 | return; /* nothing to do */ |
168 | } | |
169 | if (on) | |
170 | card->err_log_state = ERRLOG_STATE_START; /* request start */ | |
171 | else | |
172 | card->err_log_state = ERRLOG_STATE_STOP; /* request stop */ | |
173 | ||
0d9ba869 | 174 | spin_unlock_irqrestore(&card->hysdn_lock, flags); |
1da177e4 LT |
175 | schedule_work(&card->irq_queue); |
176 | } /* ergo_set_errlog_state */ | |
177 | ||
178 | /******************************************/ | |
179 | /* test the cards RAM and return 0 if ok. */ | |
180 | /******************************************/ | |
181 | static const char TestText[36] = "This Message is filler, why read it"; | |
182 | ||
183 | static int | |
184 | ergo_testram(hysdn_card * card) | |
185 | { | |
186 | tErgDpram *dpr = card->dpram; | |
187 | ||
188 | memset(dpr->TrapTable, 0, sizeof(dpr->TrapTable)); /* clear all Traps */ | |
189 | dpr->ToHyInt = 1; /* E1 INTR state forced */ | |
190 | ||
191 | memcpy(&dpr->ToHyBuf[ERG_TO_HY_BUF_SIZE - sizeof(TestText)], TestText, | |
192 | sizeof(TestText)); | |
193 | if (memcmp(&dpr->ToHyBuf[ERG_TO_HY_BUF_SIZE - sizeof(TestText)], TestText, | |
194 | sizeof(TestText))) | |
195 | return (-1); | |
196 | ||
197 | memcpy(&dpr->ToPcBuf[ERG_TO_PC_BUF_SIZE - sizeof(TestText)], TestText, | |
198 | sizeof(TestText)); | |
199 | if (memcmp(&dpr->ToPcBuf[ERG_TO_PC_BUF_SIZE - sizeof(TestText)], TestText, | |
200 | sizeof(TestText))) | |
201 | return (-1); | |
202 | ||
203 | return (0); | |
204 | } /* ergo_testram */ | |
205 | ||
206 | /*****************************************************************************/ | |
207 | /* this function is intended to write stage 1 boot image to the cards buffer */ | |
208 | /* this is done in two steps. First the 1024 hi-words are written (offs=0), */ | |
209 | /* then the 1024 lo-bytes are written. The remaining DPRAM is cleared, the */ | |
210 | /* PCI-write-buffers flushed and the card is taken out of reset. */ | |
211 | /* The function then waits for a reaction of the E1 processor or a timeout. */ | |
212 | /* Negative return values are interpreted as errors. */ | |
213 | /*****************************************************************************/ | |
214 | static int | |
c721bcce AM |
215 | ergo_writebootimg(struct HYSDN_CARD *card, unsigned char *buf, |
216 | unsigned long offs) | |
1da177e4 | 217 | { |
c721bcce | 218 | unsigned char *dst; |
1da177e4 LT |
219 | tErgDpram *dpram; |
220 | int cnt = (BOOT_IMG_SIZE >> 2); /* number of words to move and swap (byte order!) */ | |
221 | ||
222 | if (card->debug_flags & LOG_POF_CARD) | |
223 | hysdn_addlog(card, "ERGO: write bootldr offs=0x%lx ", offs); | |
224 | ||
225 | dst = card->dpram; /* pointer to start of DPRAM */ | |
226 | dst += (offs + ERG_DPRAM_FILL_SIZE); /* offset in the DPRAM */ | |
227 | while (cnt--) { | |
228 | *dst++ = *(buf + 1); /* high byte */ | |
229 | *dst++ = *buf; /* low byte */ | |
230 | dst += 2; /* point to next longword */ | |
231 | buf += 2; /* buffer only filled with words */ | |
232 | } | |
233 | ||
234 | /* if low words (offs = 2) have been written, clear the rest of the DPRAM, */ | |
235 | /* flush the PCI-write-buffer and take the E1 out of reset */ | |
236 | if (offs) { | |
237 | memset(card->dpram, 0, ERG_DPRAM_FILL_SIZE); /* fill the DPRAM still not cleared */ | |
238 | dpram = card->dpram; /* get pointer to dpram structure */ | |
239 | dpram->ToHyNoDpramErrLog = 0xFF; /* write a dpram register */ | |
240 | while (!dpram->ToHyNoDpramErrLog); /* reread volatile register to flush PCI */ | |
241 | ||
242 | byteout(card->iobase + PCI9050_USER_IO, PCI9050_E1_RUN); /* start E1 processor */ | |
243 | /* the interrupts are still masked */ | |
244 | ||
245 | sti(); | |
246 | msleep_interruptible(20); /* Timeout 20ms */ | |
247 | ||
248 | if (((tDpramBootSpooler *) card->dpram)->Len != DPRAM_SPOOLER_DATA_SIZE) { | |
249 | if (card->debug_flags & LOG_POF_CARD) | |
250 | hysdn_addlog(card, "ERGO: write bootldr no answer"); | |
251 | return (-ERR_BOOTIMG_FAIL); | |
252 | } | |
253 | } /* start_boot_img */ | |
254 | return (0); /* successful */ | |
255 | } /* ergo_writebootimg */ | |
256 | ||
257 | /********************************************************************************/ | |
258 | /* ergo_writebootseq writes the buffer containing len bytes to the E1 processor */ | |
259 | /* using the boot spool mechanism. If everything works fine 0 is returned. In */ | |
260 | /* case of errors a negative error value is returned. */ | |
261 | /********************************************************************************/ | |
262 | static int | |
c721bcce | 263 | ergo_writebootseq(struct HYSDN_CARD *card, unsigned char *buf, int len) |
1da177e4 LT |
264 | { |
265 | tDpramBootSpooler *sp = (tDpramBootSpooler *) card->dpram; | |
c721bcce AM |
266 | unsigned char *dst; |
267 | unsigned char buflen; | |
1da177e4 | 268 | int nr_write; |
c721bcce AM |
269 | unsigned char tmp_rdptr; |
270 | unsigned char wr_mirror; | |
1da177e4 LT |
271 | int i; |
272 | ||
273 | if (card->debug_flags & LOG_POF_CARD) | |
274 | hysdn_addlog(card, "ERGO: write boot seq len=%d ", len); | |
275 | ||
276 | dst = sp->Data; /* point to data in spool structure */ | |
277 | buflen = sp->Len; /* maximum len of spooled data */ | |
278 | wr_mirror = sp->WrPtr; /* only once read */ | |
279 | sti(); | |
280 | ||
281 | /* try until all bytes written or error */ | |
282 | i = 0x1000; /* timeout value */ | |
283 | while (len) { | |
284 | ||
285 | /* first determine the number of bytes that may be buffered */ | |
286 | do { | |
287 | tmp_rdptr = sp->RdPtr; /* first read the pointer */ | |
288 | i--; /* decrement timeout */ | |
289 | } while (i && (tmp_rdptr != sp->RdPtr)); /* wait for stable pointer */ | |
290 | ||
291 | if (!i) { | |
292 | if (card->debug_flags & LOG_POF_CARD) | |
293 | hysdn_addlog(card, "ERGO: write boot seq timeout"); | |
294 | return (-ERR_BOOTSEQ_FAIL); /* value not stable -> timeout */ | |
295 | } | |
296 | if ((nr_write = tmp_rdptr - wr_mirror - 1) < 0) | |
297 | nr_write += buflen; /* now we got number of free bytes - 1 in buffer */ | |
298 | ||
299 | if (!nr_write) | |
300 | continue; /* no free bytes in buffer */ | |
301 | ||
302 | if (nr_write > len) | |
303 | nr_write = len; /* limit if last few bytes */ | |
304 | i = 0x1000; /* reset timeout value */ | |
305 | ||
306 | /* now we know how much bytes we may put in the puffer */ | |
307 | len -= nr_write; /* we savely could adjust len before output */ | |
308 | while (nr_write--) { | |
309 | *(dst + wr_mirror) = *buf++; /* output one byte */ | |
310 | if (++wr_mirror >= buflen) | |
311 | wr_mirror = 0; | |
312 | sp->WrPtr = wr_mirror; /* announce the next byte to E1 */ | |
313 | } /* while (nr_write) */ | |
314 | ||
315 | } /* while (len) */ | |
316 | return (0); | |
317 | } /* ergo_writebootseq */ | |
318 | ||
319 | /***********************************************************************************/ | |
320 | /* ergo_waitpofready waits for a maximum of 10 seconds for the completition of the */ | |
321 | /* boot process. If the process has been successful 0 is returned otherwise a */ | |
322 | /* negative error code is returned. */ | |
323 | /***********************************************************************************/ | |
324 | static int | |
325 | ergo_waitpofready(struct HYSDN_CARD *card) | |
326 | { | |
327 | tErgDpram *dpr = card->dpram; /* pointer to DPRAM structure */ | |
328 | int timecnt = 10000 / 50; /* timeout is 10 secs max. */ | |
c721bcce | 329 | unsigned long flags; |
1da177e4 LT |
330 | int msg_size; |
331 | int i; | |
332 | ||
333 | if (card->debug_flags & LOG_POF_CARD) | |
334 | hysdn_addlog(card, "ERGO: waiting for pof ready"); | |
335 | while (timecnt--) { | |
336 | /* wait until timeout */ | |
337 | ||
338 | if (dpr->ToPcFlag) { | |
339 | /* data has arrived */ | |
340 | ||
341 | if ((dpr->ToPcChannel != CHAN_SYSTEM) || | |
342 | (dpr->ToPcSize < MIN_RDY_MSG_SIZE) || | |
343 | (dpr->ToPcSize > MAX_RDY_MSG_SIZE) || | |
c721bcce | 344 | ((*(unsigned long *) dpr->ToPcBuf) != RDY_MAGIC)) |
1da177e4 LT |
345 | break; /* an error occurred */ |
346 | ||
347 | /* Check for additional data delivered during SysReady */ | |
348 | msg_size = dpr->ToPcSize - RDY_MAGIC_SIZE; | |
349 | if (msg_size > 0) | |
350 | if (EvalSysrTokData(card, dpr->ToPcBuf + RDY_MAGIC_SIZE, msg_size)) | |
351 | break; | |
352 | ||
353 | if (card->debug_flags & LOG_POF_RECORD) | |
354 | hysdn_addlog(card, "ERGO: pof boot success"); | |
0d9ba869 | 355 | spin_lock_irqsave(&card->hysdn_lock, flags); |
1da177e4 LT |
356 | |
357 | card->state = CARD_STATE_RUN; /* now card is running */ | |
358 | /* enable the cards interrupt */ | |
359 | byteout(card->iobase + PCI9050_INTR_REG, | |
360 | bytein(card->iobase + PCI9050_INTR_REG) | | |
361 | (PCI9050_INTR_REG_ENPCI | PCI9050_INTR_REG_EN1)); | |
362 | card->irq_enabled = 1; /* we are ready to receive interrupts */ | |
363 | ||
364 | dpr->ToPcFlag = 0; /* reset data indicator */ | |
365 | dpr->ToHyInt = 1; | |
366 | dpr->ToPcInt = 1; /* interrupt to E1 for all cards */ | |
367 | ||
0d9ba869 | 368 | spin_unlock_irqrestore(&card->hysdn_lock, flags); |
1da177e4 LT |
369 | if ((hynet_enable & (1 << card->myid)) |
370 | && (i = hysdn_net_create(card))) | |
371 | { | |
372 | ergo_stopcard(card); | |
373 | card->state = CARD_STATE_BOOTERR; | |
374 | return (i); | |
375 | } | |
376 | #ifdef CONFIG_HYSDN_CAPI | |
377 | if((i = hycapi_capi_create(card))) { | |
378 | printk(KERN_WARNING "HYSDN: failed to create capi-interface.\n"); | |
379 | } | |
380 | #endif /* CONFIG_HYSDN_CAPI */ | |
381 | return (0); /* success */ | |
382 | } /* data has arrived */ | |
383 | sti(); | |
384 | msleep_interruptible(50); /* Timeout 50ms */ | |
385 | } /* wait until timeout */ | |
386 | ||
387 | if (card->debug_flags & LOG_POF_CARD) | |
388 | hysdn_addlog(card, "ERGO: pof boot ready timeout"); | |
389 | return (-ERR_POF_TIMEOUT); | |
390 | } /* ergo_waitpofready */ | |
391 | ||
392 | ||
393 | ||
394 | /************************************************************************************/ | |
395 | /* release the cards hardware. Before releasing do a interrupt disable and hardware */ | |
396 | /* reset. Also unmap dpram. */ | |
397 | /* Use only during module release. */ | |
398 | /************************************************************************************/ | |
399 | static void | |
400 | ergo_releasehardware(hysdn_card * card) | |
401 | { | |
402 | ergo_stopcard(card); /* first stop the card if not already done */ | |
403 | free_irq(card->irq, card); /* release interrupt */ | |
404 | release_region(card->iobase + PCI9050_INTR_REG, 1); /* release all io ports */ | |
405 | release_region(card->iobase + PCI9050_USER_IO, 1); | |
fca4edb4 | 406 | iounmap(card->dpram); |
1da177e4 LT |
407 | card->dpram = NULL; /* release shared mem */ |
408 | } /* ergo_releasehardware */ | |
409 | ||
410 | ||
411 | /*********************************************************************************/ | |
412 | /* acquire the needed hardware ports and map dpram. If an error occurs a nonzero */ | |
413 | /* value is returned. */ | |
414 | /* Use only during module init. */ | |
415 | /*********************************************************************************/ | |
416 | int | |
417 | ergo_inithardware(hysdn_card * card) | |
418 | { | |
419 | if (!request_region(card->iobase + PCI9050_INTR_REG, 1, "HYSDN")) | |
420 | return (-1); | |
421 | if (!request_region(card->iobase + PCI9050_USER_IO, 1, "HYSDN")) { | |
422 | release_region(card->iobase + PCI9050_INTR_REG, 1); | |
423 | return (-1); /* ports already in use */ | |
424 | } | |
425 | card->memend = card->membase + ERG_DPRAM_PAGE_SIZE - 1; | |
426 | if (!(card->dpram = ioremap(card->membase, ERG_DPRAM_PAGE_SIZE))) { | |
427 | release_region(card->iobase + PCI9050_INTR_REG, 1); | |
428 | release_region(card->iobase + PCI9050_USER_IO, 1); | |
429 | return (-1); | |
430 | } | |
431 | ||
432 | ergo_stopcard(card); /* disable interrupts */ | |
9ba02bec | 433 | if (request_irq(card->irq, ergo_interrupt, IRQF_SHARED, "HYSDN", card)) { |
1da177e4 LT |
434 | ergo_releasehardware(card); /* return the acquired hardware */ |
435 | return (-1); | |
436 | } | |
437 | /* success, now setup the function pointers */ | |
438 | card->stopcard = ergo_stopcard; | |
439 | card->releasehardware = ergo_releasehardware; | |
440 | card->testram = ergo_testram; | |
441 | card->writebootimg = ergo_writebootimg; | |
442 | card->writebootseq = ergo_writebootseq; | |
443 | card->waitpofready = ergo_waitpofready; | |
444 | card->set_errlog_state = ergo_set_errlog_state; | |
3e577a80 | 445 | INIT_WORK(&card->irq_queue, ergo_irq_bh); |
0d9ba869 | 446 | card->hysdn_lock = SPIN_LOCK_UNLOCKED; |
1da177e4 LT |
447 | |
448 | return (0); | |
449 | } /* ergo_inithardware */ |