diff options
Diffstat (limited to 'drivers/input/serio/hp_sdc.c')
-rw-r--r-- | drivers/input/serio/hp_sdc.c | 1054 |
1 files changed, 1054 insertions, 0 deletions
diff --git a/drivers/input/serio/hp_sdc.c b/drivers/input/serio/hp_sdc.c new file mode 100644 index 000000000000..7629452dd64b --- /dev/null +++ b/drivers/input/serio/hp_sdc.c | |||
@@ -0,0 +1,1054 @@ | |||
1 | /* | ||
2 | * HP i8042-based System Device Controller driver. | ||
3 | * | ||
4 | * Copyright (c) 2001 Brian S. Julin | ||
5 | * All rights reserved. | ||
6 | * | ||
7 | * Redistribution and use in source and binary forms, with or without | ||
8 | * modification, are permitted provided that the following conditions | ||
9 | * are met: | ||
10 | * 1. Redistributions of source code must retain the above copyright | ||
11 | * notice, this list of conditions, and the following disclaimer, | ||
12 | * without modification. | ||
13 | * 2. The name of the author may not be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * Alternatively, this software may be distributed under the terms of the | ||
17 | * GNU General Public License ("GPL"). | ||
18 | * | ||
19 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND | ||
20 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | ||
22 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR | ||
23 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | ||
25 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | ||
26 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | ||
27 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | ||
28 | * | ||
29 | * References: | ||
30 | * System Device Controller Microprocessor Firmware Theory of Operation | ||
31 | * for Part Number 1820-4784 Revision B. Dwg No. A-1820-4784-2 | ||
32 | * Helge Deller's original hilkbd.c port for PA-RISC. | ||
33 | * | ||
34 | * | ||
35 | * Driver theory of operation: | ||
36 | * | ||
37 | * hp_sdc_put does all writing to the SDC. ISR can run on a different | ||
38 | * CPU than hp_sdc_put, but only one CPU runs hp_sdc_put at a time | ||
39 | * (it cannot really benefit from SMP anyway.) A tasket fit this perfectly. | ||
40 | * | ||
41 | * All data coming back from the SDC is sent via interrupt and can be read | ||
42 | * fully in the ISR, so there are no latency/throughput problems there. | ||
43 | * The problem is with output, due to the slow clock speed of the SDC | ||
44 | * compared to the CPU. This should not be too horrible most of the time, | ||
45 | * but if used with HIL devices that support the multibyte transfer command, | ||
46 | * keeping outbound throughput flowing at the 6500KBps that the HIL is | ||
47 | * capable of is more than can be done at HZ=100. | ||
48 | * | ||
49 | * Busy polling for IBF clear wastes CPU cycles and bus cycles. hp_sdc.ibf | ||
50 | * is set to 0 when the IBF flag in the status register has cleared. ISR | ||
51 | * may do this, and may also access the parts of queued transactions related | ||
52 | * to reading data back from the SDC, but otherwise will not touch the | ||
53 | * hp_sdc state. Whenever a register is written hp_sdc.ibf is set to 1. | ||
54 | * | ||
55 | * The i8042 write index and the values in the 4-byte input buffer | ||
56 | * starting at 0x70 are kept track of in hp_sdc.wi, and .r7[], respectively, | ||
57 | * to minimize the amount of IO needed to the SDC. However these values | ||
58 | * do not need to be locked since they are only ever accessed by hp_sdc_put. | ||
59 | * | ||
60 | * A timer task schedules the tasklet once per second just to make | ||
61 | * sure it doesn't freeze up and to allow for bad reads to time out. | ||
62 | */ | ||
63 | |||
64 | #include <linux/hp_sdc.h> | ||
65 | #include <linux/sched.h> | ||
66 | #include <linux/errno.h> | ||
67 | #include <linux/init.h> | ||
68 | #include <linux/module.h> | ||
69 | #include <linux/ioport.h> | ||
70 | #include <linux/time.h> | ||
71 | #include <linux/slab.h> | ||
72 | #include <linux/hil.h> | ||
73 | #include <asm/io.h> | ||
74 | #include <asm/system.h> | ||
75 | |||
76 | /* Machine-specific abstraction */ | ||
77 | |||
78 | #if defined(__hppa__) | ||
79 | # include <asm/parisc-device.h> | ||
80 | # define sdc_readb(p) gsc_readb(p) | ||
81 | # define sdc_writeb(v,p) gsc_writeb((v),(p)) | ||
82 | #elif defined(__mc68000__) | ||
83 | # include <asm/uaccess.h> | ||
84 | # define sdc_readb(p) in_8(p) | ||
85 | # define sdc_writeb(v,p) out_8((p),(v)) | ||
86 | #else | ||
87 | # error "HIL is not supported on this platform" | ||
88 | #endif | ||
89 | |||
90 | #define PREFIX "HP SDC: " | ||
91 | |||
92 | MODULE_AUTHOR("Brian S. Julin <bri@calyx.com>"); | ||
93 | MODULE_DESCRIPTION("HP i8042-based SDC Driver"); | ||
94 | MODULE_LICENSE("Dual BSD/GPL"); | ||
95 | |||
96 | EXPORT_SYMBOL(hp_sdc_request_timer_irq); | ||
97 | EXPORT_SYMBOL(hp_sdc_request_hil_irq); | ||
98 | EXPORT_SYMBOL(hp_sdc_request_cooked_irq); | ||
99 | |||
100 | EXPORT_SYMBOL(hp_sdc_release_timer_irq); | ||
101 | EXPORT_SYMBOL(hp_sdc_release_hil_irq); | ||
102 | EXPORT_SYMBOL(hp_sdc_release_cooked_irq); | ||
103 | |||
104 | EXPORT_SYMBOL(hp_sdc_enqueue_transaction); | ||
105 | EXPORT_SYMBOL(hp_sdc_dequeue_transaction); | ||
106 | |||
107 | static hp_i8042_sdc hp_sdc; /* All driver state is kept in here. */ | ||
108 | |||
109 | /*************** primitives for use in any context *********************/ | ||
110 | static inline uint8_t hp_sdc_status_in8 (void) { | ||
111 | uint8_t status; | ||
112 | unsigned long flags; | ||
113 | |||
114 | write_lock_irqsave(&hp_sdc.ibf_lock, flags); | ||
115 | status = sdc_readb(hp_sdc.status_io); | ||
116 | if (!(status & HP_SDC_STATUS_IBF)) hp_sdc.ibf = 0; | ||
117 | write_unlock_irqrestore(&hp_sdc.ibf_lock, flags); | ||
118 | |||
119 | return status; | ||
120 | } | ||
121 | |||
122 | static inline uint8_t hp_sdc_data_in8 (void) { | ||
123 | return sdc_readb(hp_sdc.data_io); | ||
124 | } | ||
125 | |||
126 | static inline void hp_sdc_status_out8 (uint8_t val) { | ||
127 | unsigned long flags; | ||
128 | |||
129 | write_lock_irqsave(&hp_sdc.ibf_lock, flags); | ||
130 | hp_sdc.ibf = 1; | ||
131 | if ((val & 0xf0) == 0xe0) hp_sdc.wi = 0xff; | ||
132 | sdc_writeb(val, hp_sdc.status_io); | ||
133 | write_unlock_irqrestore(&hp_sdc.ibf_lock, flags); | ||
134 | } | ||
135 | |||
136 | static inline void hp_sdc_data_out8 (uint8_t val) { | ||
137 | unsigned long flags; | ||
138 | |||
139 | write_lock_irqsave(&hp_sdc.ibf_lock, flags); | ||
140 | hp_sdc.ibf = 1; | ||
141 | sdc_writeb(val, hp_sdc.data_io); | ||
142 | write_unlock_irqrestore(&hp_sdc.ibf_lock, flags); | ||
143 | } | ||
144 | |||
145 | /* Care must be taken to only invoke hp_sdc_spin_ibf when | ||
146 | * absolutely needed, or in rarely invoked subroutines. | ||
147 | * Not only does it waste CPU cycles, it also wastes bus cycles. | ||
148 | */ | ||
149 | static inline void hp_sdc_spin_ibf(void) { | ||
150 | unsigned long flags; | ||
151 | rwlock_t *lock; | ||
152 | |||
153 | lock = &hp_sdc.ibf_lock; | ||
154 | |||
155 | read_lock_irqsave(lock, flags); | ||
156 | if (!hp_sdc.ibf) { | ||
157 | read_unlock_irqrestore(lock, flags); | ||
158 | return; | ||
159 | } | ||
160 | read_unlock(lock); | ||
161 | write_lock(lock); | ||
162 | while (sdc_readb(hp_sdc.status_io) & HP_SDC_STATUS_IBF) {}; | ||
163 | hp_sdc.ibf = 0; | ||
164 | write_unlock_irqrestore(lock, flags); | ||
165 | } | ||
166 | |||
167 | |||
168 | /************************ Interrupt context functions ************************/ | ||
169 | static void hp_sdc_take (int irq, void *dev_id, uint8_t status, uint8_t data) { | ||
170 | hp_sdc_transaction *curr; | ||
171 | |||
172 | read_lock(&hp_sdc.rtq_lock); | ||
173 | if (hp_sdc.rcurr < 0) { | ||
174 | read_unlock(&hp_sdc.rtq_lock); | ||
175 | return; | ||
176 | } | ||
177 | curr = hp_sdc.tq[hp_sdc.rcurr]; | ||
178 | read_unlock(&hp_sdc.rtq_lock); | ||
179 | |||
180 | curr->seq[curr->idx++] = status; | ||
181 | curr->seq[curr->idx++] = data; | ||
182 | hp_sdc.rqty -= 2; | ||
183 | do_gettimeofday(&hp_sdc.rtv); | ||
184 | |||
185 | if (hp_sdc.rqty <= 0) { | ||
186 | /* All data has been gathered. */ | ||
187 | if(curr->seq[curr->actidx] & HP_SDC_ACT_SEMAPHORE) { | ||
188 | if (curr->act.semaphore) up(curr->act.semaphore); | ||
189 | } | ||
190 | if(curr->seq[curr->actidx] & HP_SDC_ACT_CALLBACK) { | ||
191 | if (curr->act.irqhook) | ||
192 | curr->act.irqhook(irq, dev_id, status, data); | ||
193 | } | ||
194 | curr->actidx = curr->idx; | ||
195 | curr->idx++; | ||
196 | /* Return control of this transaction */ | ||
197 | write_lock(&hp_sdc.rtq_lock); | ||
198 | hp_sdc.rcurr = -1; | ||
199 | hp_sdc.rqty = 0; | ||
200 | write_unlock(&hp_sdc.rtq_lock); | ||
201 | tasklet_schedule(&hp_sdc.task); | ||
202 | } | ||
203 | } | ||
204 | |||
205 | static irqreturn_t hp_sdc_isr(int irq, void *dev_id, struct pt_regs * regs) { | ||
206 | uint8_t status, data; | ||
207 | |||
208 | status = hp_sdc_status_in8(); | ||
209 | /* Read data unconditionally to advance i8042. */ | ||
210 | data = hp_sdc_data_in8(); | ||
211 | |||
212 | /* For now we are ignoring these until we get the SDC to behave. */ | ||
213 | if (((status & 0xf1) == 0x51) && data == 0x82) { | ||
214 | return IRQ_HANDLED; | ||
215 | } | ||
216 | |||
217 | switch(status & HP_SDC_STATUS_IRQMASK) { | ||
218 | case 0: /* This case is not documented. */ | ||
219 | break; | ||
220 | case HP_SDC_STATUS_USERTIMER: | ||
221 | case HP_SDC_STATUS_PERIODIC: | ||
222 | case HP_SDC_STATUS_TIMER: | ||
223 | read_lock(&hp_sdc.hook_lock); | ||
224 | if (hp_sdc.timer != NULL) | ||
225 | hp_sdc.timer(irq, dev_id, status, data); | ||
226 | read_unlock(&hp_sdc.hook_lock); | ||
227 | break; | ||
228 | case HP_SDC_STATUS_REG: | ||
229 | hp_sdc_take(irq, dev_id, status, data); | ||
230 | break; | ||
231 | case HP_SDC_STATUS_HILCMD: | ||
232 | case HP_SDC_STATUS_HILDATA: | ||
233 | read_lock(&hp_sdc.hook_lock); | ||
234 | if (hp_sdc.hil != NULL) | ||
235 | hp_sdc.hil(irq, dev_id, status, data); | ||
236 | read_unlock(&hp_sdc.hook_lock); | ||
237 | break; | ||
238 | case HP_SDC_STATUS_PUP: | ||
239 | read_lock(&hp_sdc.hook_lock); | ||
240 | if (hp_sdc.pup != NULL) | ||
241 | hp_sdc.pup(irq, dev_id, status, data); | ||
242 | else printk(KERN_INFO PREFIX "HP SDC reports successful PUP.\n"); | ||
243 | read_unlock(&hp_sdc.hook_lock); | ||
244 | break; | ||
245 | default: | ||
246 | read_lock(&hp_sdc.hook_lock); | ||
247 | if (hp_sdc.cooked != NULL) | ||
248 | hp_sdc.cooked(irq, dev_id, status, data); | ||
249 | read_unlock(&hp_sdc.hook_lock); | ||
250 | break; | ||
251 | } | ||
252 | return IRQ_HANDLED; | ||
253 | } | ||
254 | |||
255 | |||
256 | static irqreturn_t hp_sdc_nmisr(int irq, void *dev_id, struct pt_regs * regs) { | ||
257 | int status; | ||
258 | |||
259 | status = hp_sdc_status_in8(); | ||
260 | printk(KERN_WARNING PREFIX "NMI !\n"); | ||
261 | |||
262 | #if 0 | ||
263 | if (status & HP_SDC_NMISTATUS_FHS) { | ||
264 | read_lock(&hp_sdc.hook_lock); | ||
265 | if (hp_sdc.timer != NULL) | ||
266 | hp_sdc.timer(irq, dev_id, status, 0); | ||
267 | read_unlock(&hp_sdc.hook_lock); | ||
268 | } | ||
269 | else { | ||
270 | /* TODO: pass this on to the HIL handler, or do SAK here? */ | ||
271 | printk(KERN_WARNING PREFIX "HIL NMI\n"); | ||
272 | } | ||
273 | #endif | ||
274 | return IRQ_HANDLED; | ||
275 | } | ||
276 | |||
277 | |||
278 | /***************** Kernel (tasklet) context functions ****************/ | ||
279 | |||
280 | unsigned long hp_sdc_put(void); | ||
281 | |||
282 | static void hp_sdc_tasklet(unsigned long foo) { | ||
283 | |||
284 | write_lock_irq(&hp_sdc.rtq_lock); | ||
285 | if (hp_sdc.rcurr >= 0) { | ||
286 | struct timeval tv; | ||
287 | do_gettimeofday(&tv); | ||
288 | if (tv.tv_sec > hp_sdc.rtv.tv_sec) tv.tv_usec += 1000000; | ||
289 | if (tv.tv_usec - hp_sdc.rtv.tv_usec > HP_SDC_MAX_REG_DELAY) { | ||
290 | hp_sdc_transaction *curr; | ||
291 | uint8_t tmp; | ||
292 | |||
293 | curr = hp_sdc.tq[hp_sdc.rcurr]; | ||
294 | /* If this turns out to be a normal failure mode | ||
295 | * we'll need to figure out a way to communicate | ||
296 | * it back to the application. and be less verbose. | ||
297 | */ | ||
298 | printk(KERN_WARNING PREFIX "read timeout (%ius)!\n", | ||
299 | tv.tv_usec - hp_sdc.rtv.tv_usec); | ||
300 | curr->idx += hp_sdc.rqty; | ||
301 | hp_sdc.rqty = 0; | ||
302 | tmp = curr->seq[curr->actidx]; | ||
303 | curr->seq[curr->actidx] |= HP_SDC_ACT_DEAD; | ||
304 | if(tmp & HP_SDC_ACT_SEMAPHORE) { | ||
305 | if (curr->act.semaphore) | ||
306 | up(curr->act.semaphore); | ||
307 | } | ||
308 | if(tmp & HP_SDC_ACT_CALLBACK) { | ||
309 | /* Note this means that irqhooks may be called | ||
310 | * in tasklet/bh context. | ||
311 | */ | ||
312 | if (curr->act.irqhook) | ||
313 | curr->act.irqhook(0, 0, 0, 0); | ||
314 | } | ||
315 | curr->actidx = curr->idx; | ||
316 | curr->idx++; | ||
317 | hp_sdc.rcurr = -1; | ||
318 | } | ||
319 | } | ||
320 | write_unlock_irq(&hp_sdc.rtq_lock); | ||
321 | hp_sdc_put(); | ||
322 | } | ||
323 | |||
324 | unsigned long hp_sdc_put(void) { | ||
325 | hp_sdc_transaction *curr; | ||
326 | uint8_t act; | ||
327 | int idx, curridx; | ||
328 | |||
329 | int limit = 0; | ||
330 | |||
331 | write_lock(&hp_sdc.lock); | ||
332 | |||
333 | /* If i8042 buffers are full, we cannot do anything that | ||
334 | requires output, so we skip to the administrativa. */ | ||
335 | if (hp_sdc.ibf) { | ||
336 | hp_sdc_status_in8(); | ||
337 | if (hp_sdc.ibf) goto finish; | ||
338 | } | ||
339 | |||
340 | anew: | ||
341 | /* See if we are in the middle of a sequence. */ | ||
342 | if (hp_sdc.wcurr < 0) hp_sdc.wcurr = 0; | ||
343 | read_lock_irq(&hp_sdc.rtq_lock); | ||
344 | if (hp_sdc.rcurr == hp_sdc.wcurr) hp_sdc.wcurr++; | ||
345 | read_unlock_irq(&hp_sdc.rtq_lock); | ||
346 | if (hp_sdc.wcurr >= HP_SDC_QUEUE_LEN) hp_sdc.wcurr = 0; | ||
347 | curridx = hp_sdc.wcurr; | ||
348 | |||
349 | if (hp_sdc.tq[curridx] != NULL) goto start; | ||
350 | |||
351 | while (++curridx != hp_sdc.wcurr) { | ||
352 | if (curridx >= HP_SDC_QUEUE_LEN) { | ||
353 | curridx = -1; /* Wrap to top */ | ||
354 | continue; | ||
355 | } | ||
356 | read_lock_irq(&hp_sdc.rtq_lock); | ||
357 | if (hp_sdc.rcurr == curridx) { | ||
358 | read_unlock_irq(&hp_sdc.rtq_lock); | ||
359 | continue; | ||
360 | } | ||
361 | read_unlock_irq(&hp_sdc.rtq_lock); | ||
362 | if (hp_sdc.tq[curridx] != NULL) break; /* Found one. */ | ||
363 | } | ||
364 | if (curridx == hp_sdc.wcurr) { /* There's nothing queued to do. */ | ||
365 | curridx = -1; | ||
366 | } | ||
367 | hp_sdc.wcurr = curridx; | ||
368 | |||
369 | start: | ||
370 | |||
371 | /* Check to see if the interrupt mask needs to be set. */ | ||
372 | if (hp_sdc.set_im) { | ||
373 | hp_sdc_status_out8(hp_sdc.im | HP_SDC_CMD_SET_IM); | ||
374 | hp_sdc.set_im = 0; | ||
375 | goto finish; | ||
376 | } | ||
377 | |||
378 | if (hp_sdc.wcurr == -1) goto done; | ||
379 | |||
380 | curr = hp_sdc.tq[curridx]; | ||
381 | idx = curr->actidx; | ||
382 | |||
383 | if (curr->actidx >= curr->endidx) { | ||
384 | hp_sdc.tq[curridx] = NULL; | ||
385 | /* Interleave outbound data between the transactions. */ | ||
386 | hp_sdc.wcurr++; | ||
387 | if (hp_sdc.wcurr >= HP_SDC_QUEUE_LEN) hp_sdc.wcurr = 0; | ||
388 | goto finish; | ||
389 | } | ||
390 | |||
391 | act = curr->seq[idx]; | ||
392 | idx++; | ||
393 | |||
394 | if (curr->idx >= curr->endidx) { | ||
395 | if (act & HP_SDC_ACT_DEALLOC) kfree(curr); | ||
396 | hp_sdc.tq[curridx] = NULL; | ||
397 | /* Interleave outbound data between the transactions. */ | ||
398 | hp_sdc.wcurr++; | ||
399 | if (hp_sdc.wcurr >= HP_SDC_QUEUE_LEN) hp_sdc.wcurr = 0; | ||
400 | goto finish; | ||
401 | } | ||
402 | |||
403 | while (act & HP_SDC_ACT_PRECMD) { | ||
404 | if (curr->idx != idx) { | ||
405 | idx++; | ||
406 | act &= ~HP_SDC_ACT_PRECMD; | ||
407 | break; | ||
408 | } | ||
409 | hp_sdc_status_out8(curr->seq[idx]); | ||
410 | curr->idx++; | ||
411 | /* act finished? */ | ||
412 | if ((act & HP_SDC_ACT_DURING) == HP_SDC_ACT_PRECMD) | ||
413 | goto actdone; | ||
414 | /* skip quantity field if data-out sequence follows. */ | ||
415 | if (act & HP_SDC_ACT_DATAOUT) curr->idx++; | ||
416 | goto finish; | ||
417 | } | ||
418 | if (act & HP_SDC_ACT_DATAOUT) { | ||
419 | int qty; | ||
420 | |||
421 | qty = curr->seq[idx]; | ||
422 | idx++; | ||
423 | if (curr->idx - idx < qty) { | ||
424 | hp_sdc_data_out8(curr->seq[curr->idx]); | ||
425 | curr->idx++; | ||
426 | /* act finished? */ | ||
427 | if ((curr->idx - idx >= qty) && | ||
428 | ((act & HP_SDC_ACT_DURING) == HP_SDC_ACT_DATAOUT)) | ||
429 | goto actdone; | ||
430 | goto finish; | ||
431 | } | ||
432 | idx += qty; | ||
433 | act &= ~HP_SDC_ACT_DATAOUT; | ||
434 | } | ||
435 | else while (act & HP_SDC_ACT_DATAREG) { | ||
436 | int mask; | ||
437 | uint8_t w7[4]; | ||
438 | |||
439 | mask = curr->seq[idx]; | ||
440 | if (idx != curr->idx) { | ||
441 | idx++; | ||
442 | idx += !!(mask & 1); | ||
443 | idx += !!(mask & 2); | ||
444 | idx += !!(mask & 4); | ||
445 | idx += !!(mask & 8); | ||
446 | act &= ~HP_SDC_ACT_DATAREG; | ||
447 | break; | ||
448 | } | ||
449 | |||
450 | w7[0] = (mask & 1) ? curr->seq[++idx] : hp_sdc.r7[0]; | ||
451 | w7[1] = (mask & 2) ? curr->seq[++idx] : hp_sdc.r7[1]; | ||
452 | w7[2] = (mask & 4) ? curr->seq[++idx] : hp_sdc.r7[2]; | ||
453 | w7[3] = (mask & 8) ? curr->seq[++idx] : hp_sdc.r7[3]; | ||
454 | |||
455 | if (hp_sdc.wi > 0x73 || hp_sdc.wi < 0x70 || | ||
456 | w7[hp_sdc.wi-0x70] == hp_sdc.r7[hp_sdc.wi-0x70]) { | ||
457 | int i = 0; | ||
458 | |||
459 | /* Need to point the write index register */ | ||
460 | while ((i < 4) && w7[i] == hp_sdc.r7[i]) i++; | ||
461 | if (i < 4) { | ||
462 | hp_sdc_status_out8(HP_SDC_CMD_SET_D0 + i); | ||
463 | hp_sdc.wi = 0x70 + i; | ||
464 | goto finish; | ||
465 | } | ||
466 | idx++; | ||
467 | if ((act & HP_SDC_ACT_DURING) == HP_SDC_ACT_DATAREG) | ||
468 | goto actdone; | ||
469 | curr->idx = idx; | ||
470 | act &= ~HP_SDC_ACT_DATAREG; | ||
471 | break; | ||
472 | } | ||
473 | |||
474 | hp_sdc_data_out8(w7[hp_sdc.wi - 0x70]); | ||
475 | hp_sdc.r7[hp_sdc.wi - 0x70] = w7[hp_sdc.wi - 0x70]; | ||
476 | hp_sdc.wi++; /* write index register autoincrements */ | ||
477 | { | ||
478 | int i = 0; | ||
479 | |||
480 | while ((i < 4) && w7[i] == hp_sdc.r7[i]) i++; | ||
481 | if (i >= 4) { | ||
482 | curr->idx = idx + 1; | ||
483 | if ((act & HP_SDC_ACT_DURING) == | ||
484 | HP_SDC_ACT_DATAREG) | ||
485 | goto actdone; | ||
486 | } | ||
487 | } | ||
488 | goto finish; | ||
489 | } | ||
490 | /* We don't go any further in the command if there is a pending read, | ||
491 | because we don't want interleaved results. */ | ||
492 | read_lock_irq(&hp_sdc.rtq_lock); | ||
493 | if (hp_sdc.rcurr >= 0) { | ||
494 | read_unlock_irq(&hp_sdc.rtq_lock); | ||
495 | goto finish; | ||
496 | } | ||
497 | read_unlock_irq(&hp_sdc.rtq_lock); | ||
498 | |||
499 | |||
500 | if (act & HP_SDC_ACT_POSTCMD) { | ||
501 | uint8_t postcmd; | ||
502 | |||
503 | /* curr->idx should == idx at this point. */ | ||
504 | postcmd = curr->seq[idx]; | ||
505 | curr->idx++; | ||
506 | if (act & HP_SDC_ACT_DATAIN) { | ||
507 | |||
508 | /* Start a new read */ | ||
509 | hp_sdc.rqty = curr->seq[curr->idx]; | ||
510 | do_gettimeofday(&hp_sdc.rtv); | ||
511 | curr->idx++; | ||
512 | /* Still need to lock here in case of spurious irq. */ | ||
513 | write_lock_irq(&hp_sdc.rtq_lock); | ||
514 | hp_sdc.rcurr = curridx; | ||
515 | write_unlock_irq(&hp_sdc.rtq_lock); | ||
516 | hp_sdc_status_out8(postcmd); | ||
517 | goto finish; | ||
518 | } | ||
519 | hp_sdc_status_out8(postcmd); | ||
520 | goto actdone; | ||
521 | } | ||
522 | |||
523 | actdone: | ||
524 | if (act & HP_SDC_ACT_SEMAPHORE) { | ||
525 | up(curr->act.semaphore); | ||
526 | } | ||
527 | else if (act & HP_SDC_ACT_CALLBACK) { | ||
528 | curr->act.irqhook(0,0,0,0); | ||
529 | } | ||
530 | if (curr->idx >= curr->endidx) { /* This transaction is over. */ | ||
531 | if (act & HP_SDC_ACT_DEALLOC) kfree(curr); | ||
532 | hp_sdc.tq[curridx] = NULL; | ||
533 | } | ||
534 | else { | ||
535 | curr->actidx = idx + 1; | ||
536 | curr->idx = idx + 2; | ||
537 | } | ||
538 | /* Interleave outbound data between the transactions. */ | ||
539 | hp_sdc.wcurr++; | ||
540 | if (hp_sdc.wcurr >= HP_SDC_QUEUE_LEN) hp_sdc.wcurr = 0; | ||
541 | |||
542 | finish: | ||
543 | /* If by some quirk IBF has cleared and our ISR has run to | ||
544 | see that that has happened, do it all again. */ | ||
545 | if (!hp_sdc.ibf && limit++ < 20) goto anew; | ||
546 | |||
547 | done: | ||
548 | if (hp_sdc.wcurr >= 0) tasklet_schedule(&hp_sdc.task); | ||
549 | write_unlock(&hp_sdc.lock); | ||
550 | return 0; | ||
551 | } | ||
552 | |||
553 | /******* Functions called in either user or kernel context ****/ | ||
554 | int hp_sdc_enqueue_transaction(hp_sdc_transaction *this) { | ||
555 | unsigned long flags; | ||
556 | int i; | ||
557 | |||
558 | if (this == NULL) { | ||
559 | tasklet_schedule(&hp_sdc.task); | ||
560 | return -EINVAL; | ||
561 | }; | ||
562 | |||
563 | write_lock_irqsave(&hp_sdc.lock, flags); | ||
564 | |||
565 | /* Can't have same transaction on queue twice */ | ||
566 | for (i=0; i < HP_SDC_QUEUE_LEN; i++) | ||
567 | if (hp_sdc.tq[i] == this) goto fail; | ||
568 | |||
569 | this->actidx = 0; | ||
570 | this->idx = 1; | ||
571 | |||
572 | /* Search for empty slot */ | ||
573 | for (i=0; i < HP_SDC_QUEUE_LEN; i++) { | ||
574 | if (hp_sdc.tq[i] == NULL) { | ||
575 | hp_sdc.tq[i] = this; | ||
576 | write_unlock_irqrestore(&hp_sdc.lock, flags); | ||
577 | tasklet_schedule(&hp_sdc.task); | ||
578 | return 0; | ||
579 | } | ||
580 | } | ||
581 | write_unlock_irqrestore(&hp_sdc.lock, flags); | ||
582 | printk(KERN_WARNING PREFIX "No free slot to add transaction.\n"); | ||
583 | return -EBUSY; | ||
584 | |||
585 | fail: | ||
586 | write_unlock_irqrestore(&hp_sdc.lock,flags); | ||
587 | printk(KERN_WARNING PREFIX "Transaction add failed: transaction already queued?\n"); | ||
588 | return -EINVAL; | ||
589 | } | ||
590 | |||
591 | int hp_sdc_dequeue_transaction(hp_sdc_transaction *this) { | ||
592 | unsigned long flags; | ||
593 | int i; | ||
594 | |||
595 | write_lock_irqsave(&hp_sdc.lock, flags); | ||
596 | |||
597 | /* TODO: don't remove it if it's not done. */ | ||
598 | |||
599 | for (i=0; i < HP_SDC_QUEUE_LEN; i++) | ||
600 | if (hp_sdc.tq[i] == this) hp_sdc.tq[i] = NULL; | ||
601 | |||
602 | write_unlock_irqrestore(&hp_sdc.lock, flags); | ||
603 | return 0; | ||
604 | } | ||
605 | |||
606 | |||
607 | |||
608 | /********************** User context functions **************************/ | ||
609 | int hp_sdc_request_timer_irq(hp_sdc_irqhook *callback) { | ||
610 | |||
611 | if (callback == NULL || hp_sdc.dev == NULL) { | ||
612 | return -EINVAL; | ||
613 | } | ||
614 | write_lock_irq(&hp_sdc.hook_lock); | ||
615 | if (hp_sdc.timer != NULL) { | ||
616 | write_unlock_irq(&hp_sdc.hook_lock); | ||
617 | return -EBUSY; | ||
618 | } | ||
619 | |||
620 | hp_sdc.timer = callback; | ||
621 | /* Enable interrupts from the timers */ | ||
622 | hp_sdc.im &= ~HP_SDC_IM_FH; | ||
623 | hp_sdc.im &= ~HP_SDC_IM_PT; | ||
624 | hp_sdc.im &= ~HP_SDC_IM_TIMERS; | ||
625 | hp_sdc.set_im = 1; | ||
626 | write_unlock_irq(&hp_sdc.hook_lock); | ||
627 | |||
628 | tasklet_schedule(&hp_sdc.task); | ||
629 | |||
630 | return 0; | ||
631 | } | ||
632 | |||
633 | int hp_sdc_request_hil_irq(hp_sdc_irqhook *callback) { | ||
634 | |||
635 | if (callback == NULL || hp_sdc.dev == NULL) { | ||
636 | return -EINVAL; | ||
637 | } | ||
638 | write_lock_irq(&hp_sdc.hook_lock); | ||
639 | if (hp_sdc.hil != NULL) { | ||
640 | write_unlock_irq(&hp_sdc.hook_lock); | ||
641 | return -EBUSY; | ||
642 | } | ||
643 | |||
644 | hp_sdc.hil = callback; | ||
645 | hp_sdc.im &= ~(HP_SDC_IM_HIL | HP_SDC_IM_RESET); | ||
646 | hp_sdc.set_im = 1; | ||
647 | write_unlock_irq(&hp_sdc.hook_lock); | ||
648 | |||
649 | tasklet_schedule(&hp_sdc.task); | ||
650 | |||
651 | return 0; | ||
652 | } | ||
653 | |||
654 | int hp_sdc_request_cooked_irq(hp_sdc_irqhook *callback) { | ||
655 | |||
656 | if (callback == NULL || hp_sdc.dev == NULL) { | ||
657 | return -EINVAL; | ||
658 | } | ||
659 | write_lock_irq(&hp_sdc.hook_lock); | ||
660 | if (hp_sdc.cooked != NULL) { | ||
661 | write_unlock_irq(&hp_sdc.hook_lock); | ||
662 | return -EBUSY; | ||
663 | } | ||
664 | |||
665 | /* Enable interrupts from the HIL MLC */ | ||
666 | hp_sdc.cooked = callback; | ||
667 | hp_sdc.im &= ~(HP_SDC_IM_HIL | HP_SDC_IM_RESET); | ||
668 | hp_sdc.set_im = 1; | ||
669 | write_unlock_irq(&hp_sdc.hook_lock); | ||
670 | |||
671 | tasklet_schedule(&hp_sdc.task); | ||
672 | |||
673 | return 0; | ||
674 | } | ||
675 | |||
676 | int hp_sdc_release_timer_irq(hp_sdc_irqhook *callback) { | ||
677 | |||
678 | |||
679 | write_lock_irq(&hp_sdc.hook_lock); | ||
680 | if ((callback != hp_sdc.timer) || | ||
681 | (hp_sdc.timer == NULL)) { | ||
682 | write_unlock_irq(&hp_sdc.hook_lock); | ||
683 | return -EINVAL; | ||
684 | } | ||
685 | |||
686 | /* Disable interrupts from the timers */ | ||
687 | hp_sdc.timer = NULL; | ||
688 | hp_sdc.im |= HP_SDC_IM_TIMERS; | ||
689 | hp_sdc.im |= HP_SDC_IM_FH; | ||
690 | hp_sdc.im |= HP_SDC_IM_PT; | ||
691 | hp_sdc.set_im = 1; | ||
692 | write_unlock_irq(&hp_sdc.hook_lock); | ||
693 | tasklet_schedule(&hp_sdc.task); | ||
694 | |||
695 | return 0; | ||
696 | } | ||
697 | |||
698 | int hp_sdc_release_hil_irq(hp_sdc_irqhook *callback) { | ||
699 | |||
700 | write_lock_irq(&hp_sdc.hook_lock); | ||
701 | if ((callback != hp_sdc.hil) || | ||
702 | (hp_sdc.hil == NULL)) { | ||
703 | write_unlock_irq(&hp_sdc.hook_lock); | ||
704 | return -EINVAL; | ||
705 | } | ||
706 | |||
707 | hp_sdc.hil = NULL; | ||
708 | /* Disable interrupts from HIL only if there is no cooked driver. */ | ||
709 | if(hp_sdc.cooked == NULL) { | ||
710 | hp_sdc.im |= (HP_SDC_IM_HIL | HP_SDC_IM_RESET); | ||
711 | hp_sdc.set_im = 1; | ||
712 | } | ||
713 | write_unlock_irq(&hp_sdc.hook_lock); | ||
714 | tasklet_schedule(&hp_sdc.task); | ||
715 | |||
716 | return 0; | ||
717 | } | ||
718 | |||
719 | int hp_sdc_release_cooked_irq(hp_sdc_irqhook *callback) { | ||
720 | |||
721 | write_lock_irq(&hp_sdc.hook_lock); | ||
722 | if ((callback != hp_sdc.cooked) || | ||
723 | (hp_sdc.cooked == NULL)) { | ||
724 | write_unlock_irq(&hp_sdc.hook_lock); | ||
725 | return -EINVAL; | ||
726 | } | ||
727 | |||
728 | hp_sdc.cooked = NULL; | ||
729 | /* Disable interrupts from HIL only if there is no raw HIL driver. */ | ||
730 | if(hp_sdc.hil == NULL) { | ||
731 | hp_sdc.im |= (HP_SDC_IM_HIL | HP_SDC_IM_RESET); | ||
732 | hp_sdc.set_im = 1; | ||
733 | } | ||
734 | write_unlock_irq(&hp_sdc.hook_lock); | ||
735 | tasklet_schedule(&hp_sdc.task); | ||
736 | |||
737 | return 0; | ||
738 | } | ||
739 | |||
740 | /************************* Keepalive timer task *********************/ | ||
741 | |||
742 | void hp_sdc_kicker (unsigned long data) { | ||
743 | tasklet_schedule(&hp_sdc.task); | ||
744 | /* Re-insert the periodic task. */ | ||
745 | mod_timer(&hp_sdc.kicker, jiffies + HZ); | ||
746 | } | ||
747 | |||
748 | /************************** Module Initialization ***************************/ | ||
749 | |||
750 | #if defined(__hppa__) | ||
751 | |||
752 | static struct parisc_device_id hp_sdc_tbl[] = { | ||
753 | { | ||
754 | .hw_type = HPHW_FIO, | ||
755 | .hversion_rev = HVERSION_REV_ANY_ID, | ||
756 | .hversion = HVERSION_ANY_ID, | ||
757 | .sversion = 0x73, | ||
758 | }, | ||
759 | { 0, } | ||
760 | }; | ||
761 | |||
762 | MODULE_DEVICE_TABLE(parisc, hp_sdc_tbl); | ||
763 | |||
764 | static int __init hp_sdc_init_hppa(struct parisc_device *d); | ||
765 | |||
766 | static struct parisc_driver hp_sdc_driver = { | ||
767 | .name = "HP SDC", | ||
768 | .id_table = hp_sdc_tbl, | ||
769 | .probe = hp_sdc_init_hppa, | ||
770 | }; | ||
771 | |||
772 | #endif /* __hppa__ */ | ||
773 | |||
774 | static int __init hp_sdc_init(void) | ||
775 | { | ||
776 | int i; | ||
777 | char *errstr; | ||
778 | hp_sdc_transaction t_sync; | ||
779 | uint8_t ts_sync[6]; | ||
780 | struct semaphore s_sync; | ||
781 | |||
782 | rwlock_init(&hp_sdc.lock); | ||
783 | rwlock_init(&hp_sdc.ibf_lock); | ||
784 | rwlock_init(&hp_sdc.rtq_lock); | ||
785 | rwlock_init(&hp_sdc.hook_lock); | ||
786 | |||
787 | hp_sdc.timer = NULL; | ||
788 | hp_sdc.hil = NULL; | ||
789 | hp_sdc.pup = NULL; | ||
790 | hp_sdc.cooked = NULL; | ||
791 | hp_sdc.im = HP_SDC_IM_MASK; /* Mask maskable irqs */ | ||
792 | hp_sdc.set_im = 1; | ||
793 | hp_sdc.wi = 0xff; | ||
794 | hp_sdc.r7[0] = 0xff; | ||
795 | hp_sdc.r7[1] = 0xff; | ||
796 | hp_sdc.r7[2] = 0xff; | ||
797 | hp_sdc.r7[3] = 0xff; | ||
798 | hp_sdc.ibf = 1; | ||
799 | |||
800 | for (i = 0; i < HP_SDC_QUEUE_LEN; i++) hp_sdc.tq[i] = NULL; | ||
801 | hp_sdc.wcurr = -1; | ||
802 | hp_sdc.rcurr = -1; | ||
803 | hp_sdc.rqty = 0; | ||
804 | |||
805 | hp_sdc.dev_err = -ENODEV; | ||
806 | |||
807 | errstr = "IO not found for"; | ||
808 | if (!hp_sdc.base_io) goto err0; | ||
809 | |||
810 | errstr = "IRQ not found for"; | ||
811 | if (!hp_sdc.irq) goto err0; | ||
812 | |||
813 | hp_sdc.dev_err = -EBUSY; | ||
814 | |||
815 | #if defined(__hppa__) | ||
816 | errstr = "IO not available for"; | ||
817 | if (request_region(hp_sdc.data_io, 2, hp_sdc_driver.name)) goto err0; | ||
818 | #endif | ||
819 | |||
820 | errstr = "IRQ not available for"; | ||
821 | if(request_irq(hp_sdc.irq, &hp_sdc_isr, 0, "HP SDC", | ||
822 | (void *) hp_sdc.base_io)) goto err1; | ||
823 | |||
824 | errstr = "NMI not available for"; | ||
825 | if (request_irq(hp_sdc.nmi, &hp_sdc_nmisr, 0, "HP SDC NMI", | ||
826 | (void *) hp_sdc.base_io)) goto err2; | ||
827 | |||
828 | printk(KERN_INFO PREFIX "HP SDC at 0x%p, IRQ %d (NMI IRQ %d)\n", | ||
829 | (void *)hp_sdc.base_io, hp_sdc.irq, hp_sdc.nmi); | ||
830 | |||
831 | hp_sdc_status_in8(); | ||
832 | hp_sdc_data_in8(); | ||
833 | |||
834 | tasklet_init(&hp_sdc.task, hp_sdc_tasklet, 0); | ||
835 | |||
836 | /* Sync the output buffer registers, thus scheduling hp_sdc_tasklet. */ | ||
837 | t_sync.actidx = 0; | ||
838 | t_sync.idx = 1; | ||
839 | t_sync.endidx = 6; | ||
840 | t_sync.seq = ts_sync; | ||
841 | ts_sync[0] = HP_SDC_ACT_DATAREG | HP_SDC_ACT_SEMAPHORE; | ||
842 | ts_sync[1] = 0x0f; | ||
843 | ts_sync[2] = ts_sync[3] = ts_sync[4] = ts_sync[5] = 0; | ||
844 | t_sync.act.semaphore = &s_sync; | ||
845 | init_MUTEX_LOCKED(&s_sync); | ||
846 | hp_sdc_enqueue_transaction(&t_sync); | ||
847 | down(&s_sync); /* Wait for t_sync to complete */ | ||
848 | |||
849 | /* Create the keepalive task */ | ||
850 | init_timer(&hp_sdc.kicker); | ||
851 | hp_sdc.kicker.expires = jiffies + HZ; | ||
852 | hp_sdc.kicker.function = &hp_sdc_kicker; | ||
853 | add_timer(&hp_sdc.kicker); | ||
854 | |||
855 | hp_sdc.dev_err = 0; | ||
856 | return 0; | ||
857 | err2: | ||
858 | free_irq(hp_sdc.irq, NULL); | ||
859 | err1: | ||
860 | release_region(hp_sdc.data_io, 2); | ||
861 | err0: | ||
862 | printk(KERN_WARNING PREFIX ": %s SDC IO=0x%p IRQ=0x%x NMI=0x%x\n", | ||
863 | errstr, (void *)hp_sdc.base_io, hp_sdc.irq, hp_sdc.nmi); | ||
864 | hp_sdc.dev = NULL; | ||
865 | return hp_sdc.dev_err; | ||
866 | } | ||
867 | |||
868 | #if defined(__hppa__) | ||
869 | |||
870 | static int __init hp_sdc_init_hppa(struct parisc_device *d) | ||
871 | { | ||
872 | if (!d) return 1; | ||
873 | if (hp_sdc.dev != NULL) return 1; /* We only expect one SDC */ | ||
874 | |||
875 | hp_sdc.dev = d; | ||
876 | hp_sdc.irq = d->irq; | ||
877 | hp_sdc.nmi = d->aux_irq; | ||
878 | hp_sdc.base_io = d->hpa; | ||
879 | hp_sdc.data_io = d->hpa + 0x800; | ||
880 | hp_sdc.status_io = d->hpa + 0x801; | ||
881 | |||
882 | return hp_sdc_init(); | ||
883 | } | ||
884 | |||
885 | #endif /* __hppa__ */ | ||
886 | |||
887 | #if !defined(__mc68000__) /* Link error on m68k! */ | ||
888 | static void __exit hp_sdc_exit(void) | ||
889 | #else | ||
890 | static void hp_sdc_exit(void) | ||
891 | #endif | ||
892 | { | ||
893 | write_lock_irq(&hp_sdc.lock); | ||
894 | |||
895 | /* Turn off all maskable "sub-function" irq's. */ | ||
896 | hp_sdc_spin_ibf(); | ||
897 | sdc_writeb(HP_SDC_CMD_SET_IM | HP_SDC_IM_MASK, hp_sdc.status_io); | ||
898 | |||
899 | /* Wait until we know this has been processed by the i8042 */ | ||
900 | hp_sdc_spin_ibf(); | ||
901 | |||
902 | free_irq(hp_sdc.nmi, NULL); | ||
903 | free_irq(hp_sdc.irq, NULL); | ||
904 | write_unlock_irq(&hp_sdc.lock); | ||
905 | |||
906 | del_timer(&hp_sdc.kicker); | ||
907 | |||
908 | tasklet_kill(&hp_sdc.task); | ||
909 | |||
910 | /* release_region(hp_sdc.data_io, 2); */ | ||
911 | |||
912 | #if defined(__hppa__) | ||
913 | if (unregister_parisc_driver(&hp_sdc_driver)) | ||
914 | printk(KERN_WARNING PREFIX "Error unregistering HP SDC"); | ||
915 | #endif | ||
916 | } | ||
917 | |||
918 | static int __init hp_sdc_register(void) | ||
919 | { | ||
920 | hp_sdc_transaction tq_init; | ||
921 | uint8_t tq_init_seq[5]; | ||
922 | struct semaphore tq_init_sem; | ||
923 | #if defined(__mc68000__) | ||
924 | mm_segment_t fs; | ||
925 | unsigned char i; | ||
926 | #endif | ||
927 | |||
928 | hp_sdc.dev = NULL; | ||
929 | hp_sdc.dev_err = 0; | ||
930 | #if defined(__hppa__) | ||
931 | if (register_parisc_driver(&hp_sdc_driver)) { | ||
932 | printk(KERN_WARNING PREFIX "Error registering SDC with system bus tree.\n"); | ||
933 | return -ENODEV; | ||
934 | } | ||
935 | #elif defined(__mc68000__) | ||
936 | if (!MACH_IS_HP300) | ||
937 | return -ENODEV; | ||
938 | |||
939 | hp_sdc.irq = 1; | ||
940 | hp_sdc.nmi = 7; | ||
941 | hp_sdc.base_io = (unsigned long) 0xf0428000; | ||
942 | hp_sdc.data_io = (unsigned long) hp_sdc.base_io + 1; | ||
943 | hp_sdc.status_io = (unsigned long) hp_sdc.base_io + 3; | ||
944 | fs = get_fs(); | ||
945 | set_fs(KERNEL_DS); | ||
946 | if (!get_user(i, (unsigned char *)hp_sdc.data_io)) | ||
947 | hp_sdc.dev = (void *)1; | ||
948 | set_fs(fs); | ||
949 | hp_sdc.dev_err = hp_sdc_init(); | ||
950 | #endif | ||
951 | if (hp_sdc.dev == NULL) { | ||
952 | printk(KERN_WARNING PREFIX "No SDC found.\n"); | ||
953 | return hp_sdc.dev_err; | ||
954 | } | ||
955 | |||
956 | init_MUTEX_LOCKED(&tq_init_sem); | ||
957 | |||
958 | tq_init.actidx = 0; | ||
959 | tq_init.idx = 1; | ||
960 | tq_init.endidx = 5; | ||
961 | tq_init.seq = tq_init_seq; | ||
962 | tq_init.act.semaphore = &tq_init_sem; | ||
963 | |||
964 | tq_init_seq[0] = | ||
965 | HP_SDC_ACT_POSTCMD | HP_SDC_ACT_DATAIN | HP_SDC_ACT_SEMAPHORE; | ||
966 | tq_init_seq[1] = HP_SDC_CMD_READ_KCC; | ||
967 | tq_init_seq[2] = 1; | ||
968 | tq_init_seq[3] = 0; | ||
969 | tq_init_seq[4] = 0; | ||
970 | |||
971 | hp_sdc_enqueue_transaction(&tq_init); | ||
972 | |||
973 | down(&tq_init_sem); | ||
974 | up(&tq_init_sem); | ||
975 | |||
976 | if ((tq_init_seq[0] & HP_SDC_ACT_DEAD) == HP_SDC_ACT_DEAD) { | ||
977 | printk(KERN_WARNING PREFIX "Error reading config byte.\n"); | ||
978 | hp_sdc_exit(); | ||
979 | return -ENODEV; | ||
980 | } | ||
981 | hp_sdc.r11 = tq_init_seq[4]; | ||
982 | if (hp_sdc.r11 & HP_SDC_CFG_NEW) { | ||
983 | char *str; | ||
984 | printk(KERN_INFO PREFIX "New style SDC\n"); | ||
985 | tq_init_seq[1] = HP_SDC_CMD_READ_XTD; | ||
986 | tq_init.actidx = 0; | ||
987 | tq_init.idx = 1; | ||
988 | down(&tq_init_sem); | ||
989 | hp_sdc_enqueue_transaction(&tq_init); | ||
990 | down(&tq_init_sem); | ||
991 | up(&tq_init_sem); | ||
992 | if ((tq_init_seq[0] & HP_SDC_ACT_DEAD) == HP_SDC_ACT_DEAD) { | ||
993 | printk(KERN_WARNING PREFIX "Error reading extended config byte.\n"); | ||
994 | return -ENODEV; | ||
995 | } | ||
996 | hp_sdc.r7e = tq_init_seq[4]; | ||
997 | HP_SDC_XTD_REV_STRINGS(hp_sdc.r7e & HP_SDC_XTD_REV, str) | ||
998 | printk(KERN_INFO PREFIX "Revision: %s\n", str); | ||
999 | if (hp_sdc.r7e & HP_SDC_XTD_BEEPER) { | ||
1000 | printk(KERN_INFO PREFIX "TI SN76494 beeper present\n"); | ||
1001 | } | ||
1002 | if (hp_sdc.r7e & HP_SDC_XTD_BBRTC) { | ||
1003 | printk(KERN_INFO PREFIX "OKI MSM-58321 BBRTC present\n"); | ||
1004 | } | ||
1005 | printk(KERN_INFO PREFIX "Spunking the self test register to force PUP " | ||
1006 | "on next firmware reset.\n"); | ||
1007 | tq_init_seq[0] = HP_SDC_ACT_PRECMD | | ||
1008 | HP_SDC_ACT_DATAOUT | HP_SDC_ACT_SEMAPHORE; | ||
1009 | tq_init_seq[1] = HP_SDC_CMD_SET_STR; | ||
1010 | tq_init_seq[2] = 1; | ||
1011 | tq_init_seq[3] = 0; | ||
1012 | tq_init.actidx = 0; | ||
1013 | tq_init.idx = 1; | ||
1014 | tq_init.endidx = 4; | ||
1015 | down(&tq_init_sem); | ||
1016 | hp_sdc_enqueue_transaction(&tq_init); | ||
1017 | down(&tq_init_sem); | ||
1018 | up(&tq_init_sem); | ||
1019 | } | ||
1020 | else { | ||
1021 | printk(KERN_INFO PREFIX "Old style SDC (1820-%s).\n", | ||
1022 | (hp_sdc.r11 & HP_SDC_CFG_REV) ? "3300" : "2564/3087"); | ||
1023 | } | ||
1024 | |||
1025 | return 0; | ||
1026 | } | ||
1027 | |||
1028 | module_init(hp_sdc_register); | ||
1029 | module_exit(hp_sdc_exit); | ||
1030 | |||
1031 | /* Timing notes: These measurements taken on my 64MHz 7100-LC (715/64) | ||
1032 | * cycles cycles-adj time | ||
1033 | * between two consecutive mfctl(16)'s: 4 n/a 63ns | ||
1034 | * hp_sdc_spin_ibf when idle: 119 115 1.7us | ||
1035 | * gsc_writeb status register: 83 79 1.2us | ||
1036 | * IBF to clear after sending SET_IM: 6204 6006 93us | ||
1037 | * IBF to clear after sending LOAD_RT: 4467 4352 68us | ||
1038 | * IBF to clear after sending two LOAD_RTs: 18974 18859 295us | ||
1039 | * READ_T1, read status/data, IRQ, call handler: 35564 n/a 556us | ||
1040 | * cmd to ~IBF READ_T1 2nd time right after: 5158403 n/a 81ms | ||
1041 | * between IRQ received and ~IBF for above: 2578877 n/a 40ms | ||
1042 | * | ||
1043 | * Performance stats after a run of this module configuring HIL and | ||
1044 | * receiving a few mouse events: | ||
1045 | * | ||
1046 | * status in8 282508 cycles 7128 calls | ||
1047 | * status out8 8404 cycles 341 calls | ||
1048 | * data out8 1734 cycles 78 calls | ||
1049 | * isr 174324 cycles 617 calls (includes take) | ||
1050 | * take 1241 cycles 2 calls | ||
1051 | * put 1411504 cycles 6937 calls | ||
1052 | * task 1655209 cycles 6937 calls (includes put) | ||
1053 | * | ||
1054 | */ | ||