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