diff options
Diffstat (limited to 'drivers/isdn/hardware/mISDN/speedfax.c')
-rw-r--r-- | drivers/isdn/hardware/mISDN/speedfax.c | 526 |
1 files changed, 526 insertions, 0 deletions
diff --git a/drivers/isdn/hardware/mISDN/speedfax.c b/drivers/isdn/hardware/mISDN/speedfax.c new file mode 100644 index 00000000000..ff3a4e290da --- /dev/null +++ b/drivers/isdn/hardware/mISDN/speedfax.c | |||
@@ -0,0 +1,526 @@ | |||
1 | /* | ||
2 | * speedfax.c low level stuff for Sedlbauer Speedfax+ cards | ||
3 | * based on the ISAR DSP | ||
4 | * Thanks to Sedlbauer AG for informations and HW | ||
5 | * | ||
6 | * Author Karsten Keil <keil@isdn4linux.de> | ||
7 | * | ||
8 | * Copyright 2009 by Karsten Keil <keil@isdn4linux.de> | ||
9 | * | ||
10 | * This program is free software; you can redistribute it and/or modify | ||
11 | * it under the terms of the GNU General Public License version 2 as | ||
12 | * published by the Free Software Foundation. | ||
13 | * | ||
14 | * This program is distributed in the hope that it will be useful, | ||
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
17 | * GNU General Public License for more details. | ||
18 | * | ||
19 | * You should have received a copy of the GNU General Public License | ||
20 | * along with this program; if not, write to the Free Software | ||
21 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
22 | * | ||
23 | */ | ||
24 | |||
25 | #include <linux/module.h> | ||
26 | #include <linux/pci.h> | ||
27 | #include <linux/delay.h> | ||
28 | #include <linux/mISDNhw.h> | ||
29 | #include <linux/firmware.h> | ||
30 | #include "ipac.h" | ||
31 | #include "isar.h" | ||
32 | |||
33 | #define SPEEDFAX_REV "2.0" | ||
34 | |||
35 | #define PCI_SUBVENDOR_SPEEDFAX_PYRAMID 0x51 | ||
36 | #define PCI_SUBVENDOR_SPEEDFAX_PCI 0x54 | ||
37 | #define PCI_SUB_ID_SEDLBAUER 0x01 | ||
38 | |||
39 | #define SFAX_PCI_ADDR 0xc8 | ||
40 | #define SFAX_PCI_ISAC 0xd0 | ||
41 | #define SFAX_PCI_ISAR 0xe0 | ||
42 | |||
43 | /* TIGER 100 Registers */ | ||
44 | |||
45 | #define TIGER_RESET_ADDR 0x00 | ||
46 | #define TIGER_EXTERN_RESET_ON 0x01 | ||
47 | #define TIGER_EXTERN_RESET_OFF 0x00 | ||
48 | #define TIGER_AUX_CTRL 0x02 | ||
49 | #define TIGER_AUX_DATA 0x03 | ||
50 | #define TIGER_AUX_IRQMASK 0x05 | ||
51 | #define TIGER_AUX_STATUS 0x07 | ||
52 | |||
53 | /* Tiger AUX BITs */ | ||
54 | #define SFAX_AUX_IOMASK 0xdd /* 1 and 5 are inputs */ | ||
55 | #define SFAX_ISAR_RESET_BIT_OFF 0x00 | ||
56 | #define SFAX_ISAR_RESET_BIT_ON 0x01 | ||
57 | #define SFAX_TIGER_IRQ_BIT 0x02 | ||
58 | #define SFAX_LED1_BIT 0x08 | ||
59 | #define SFAX_LED2_BIT 0x10 | ||
60 | |||
61 | #define SFAX_PCI_RESET_ON (SFAX_ISAR_RESET_BIT_ON) | ||
62 | #define SFAX_PCI_RESET_OFF (SFAX_LED1_BIT | SFAX_LED2_BIT) | ||
63 | |||
64 | static int sfax_cnt; | ||
65 | static u32 debug; | ||
66 | static u32 irqloops = 4; | ||
67 | |||
68 | struct sfax_hw { | ||
69 | struct list_head list; | ||
70 | struct pci_dev *pdev; | ||
71 | char name[MISDN_MAX_IDLEN]; | ||
72 | u32 irq; | ||
73 | u32 irqcnt; | ||
74 | u32 cfg; | ||
75 | struct _ioport p_isac; | ||
76 | struct _ioport p_isar; | ||
77 | u8 aux_data; | ||
78 | spinlock_t lock; /* HW access lock */ | ||
79 | struct isac_hw isac; | ||
80 | struct isar_hw isar; | ||
81 | }; | ||
82 | |||
83 | static LIST_HEAD(Cards); | ||
84 | static DEFINE_RWLOCK(card_lock); /* protect Cards */ | ||
85 | |||
86 | static void | ||
87 | _set_debug(struct sfax_hw *card) | ||
88 | { | ||
89 | card->isac.dch.debug = debug; | ||
90 | card->isar.ch[0].bch.debug = debug; | ||
91 | card->isar.ch[1].bch.debug = debug; | ||
92 | } | ||
93 | |||
94 | static int | ||
95 | set_debug(const char *val, struct kernel_param *kp) | ||
96 | { | ||
97 | int ret; | ||
98 | struct sfax_hw *card; | ||
99 | |||
100 | ret = param_set_uint(val, kp); | ||
101 | if (!ret) { | ||
102 | read_lock(&card_lock); | ||
103 | list_for_each_entry(card, &Cards, list) | ||
104 | _set_debug(card); | ||
105 | read_unlock(&card_lock); | ||
106 | } | ||
107 | return ret; | ||
108 | } | ||
109 | |||
110 | MODULE_AUTHOR("Karsten Keil"); | ||
111 | MODULE_LICENSE("GPL v2"); | ||
112 | MODULE_VERSION(SPEEDFAX_REV); | ||
113 | module_param_call(debug, set_debug, param_get_uint, &debug, S_IRUGO | S_IWUSR); | ||
114 | MODULE_PARM_DESC(debug, "Speedfax debug mask"); | ||
115 | module_param(irqloops, uint, S_IRUGO | S_IWUSR); | ||
116 | MODULE_PARM_DESC(irqloops, "Speedfax maximal irqloops (default 4)"); | ||
117 | |||
118 | IOFUNC_IND(ISAC, sfax_hw, p_isac) | ||
119 | IOFUNC_IND(ISAR, sfax_hw, p_isar) | ||
120 | |||
121 | static irqreturn_t | ||
122 | speedfax_irq(int intno, void *dev_id) | ||
123 | { | ||
124 | struct sfax_hw *sf = dev_id; | ||
125 | u8 val; | ||
126 | int cnt = irqloops; | ||
127 | |||
128 | spin_lock(&sf->lock); | ||
129 | val = inb(sf->cfg + TIGER_AUX_STATUS); | ||
130 | if (val & SFAX_TIGER_IRQ_BIT) { /* for us or shared ? */ | ||
131 | spin_unlock(&sf->lock); | ||
132 | return IRQ_NONE; /* shared */ | ||
133 | } | ||
134 | sf->irqcnt++; | ||
135 | val = ReadISAR_IND(sf, ISAR_IRQBIT); | ||
136 | Start_ISAR: | ||
137 | if (val & ISAR_IRQSTA) | ||
138 | mISDNisar_irq(&sf->isar); | ||
139 | val = ReadISAC_IND(sf, ISAC_ISTA); | ||
140 | if (val) | ||
141 | mISDNisac_irq(&sf->isac, val); | ||
142 | val = ReadISAR_IND(sf, ISAR_IRQBIT); | ||
143 | if ((val & ISAR_IRQSTA) && cnt--) | ||
144 | goto Start_ISAR; | ||
145 | if (cnt < irqloops) | ||
146 | pr_debug("%s: %d irqloops cpu%d\n", sf->name, | ||
147 | irqloops - cnt, smp_processor_id()); | ||
148 | if (irqloops && !cnt) | ||
149 | pr_notice("%s: %d IRQ LOOP cpu%d\n", sf->name, | ||
150 | irqloops, smp_processor_id()); | ||
151 | spin_unlock(&sf->lock); | ||
152 | return IRQ_HANDLED; | ||
153 | } | ||
154 | |||
155 | static void | ||
156 | enable_hwirq(struct sfax_hw *sf) | ||
157 | { | ||
158 | WriteISAC_IND(sf, ISAC_MASK, 0); | ||
159 | WriteISAR_IND(sf, ISAR_IRQBIT, ISAR_IRQMSK); | ||
160 | outb(SFAX_TIGER_IRQ_BIT, sf->cfg + TIGER_AUX_IRQMASK); | ||
161 | } | ||
162 | |||
163 | static void | ||
164 | disable_hwirq(struct sfax_hw *sf) | ||
165 | { | ||
166 | WriteISAC_IND(sf, ISAC_MASK, 0xFF); | ||
167 | WriteISAR_IND(sf, ISAR_IRQBIT, 0); | ||
168 | outb(0, sf->cfg + TIGER_AUX_IRQMASK); | ||
169 | } | ||
170 | |||
171 | static void | ||
172 | reset_speedfax(struct sfax_hw *sf) | ||
173 | { | ||
174 | |||
175 | pr_debug("%s: resetting card\n", sf->name); | ||
176 | outb(TIGER_EXTERN_RESET_ON, sf->cfg + TIGER_RESET_ADDR); | ||
177 | outb(SFAX_PCI_RESET_ON, sf->cfg + TIGER_AUX_DATA); | ||
178 | mdelay(1); | ||
179 | outb(TIGER_EXTERN_RESET_OFF, sf->cfg + TIGER_RESET_ADDR); | ||
180 | sf->aux_data = SFAX_PCI_RESET_OFF; | ||
181 | outb(sf->aux_data, sf->cfg + TIGER_AUX_DATA); | ||
182 | mdelay(1); | ||
183 | } | ||
184 | |||
185 | static int | ||
186 | sfax_ctrl(struct sfax_hw *sf, u32 cmd, u_long arg) | ||
187 | { | ||
188 | int ret = 0; | ||
189 | |||
190 | switch (cmd) { | ||
191 | case HW_RESET_REQ: | ||
192 | reset_speedfax(sf); | ||
193 | break; | ||
194 | case HW_ACTIVATE_IND: | ||
195 | if (arg & 1) | ||
196 | sf->aux_data &= ~SFAX_LED1_BIT; | ||
197 | if (arg & 2) | ||
198 | sf->aux_data &= ~SFAX_LED2_BIT; | ||
199 | outb(sf->aux_data, sf->cfg + TIGER_AUX_DATA); | ||
200 | break; | ||
201 | case HW_DEACT_IND: | ||
202 | if (arg & 1) | ||
203 | sf->aux_data |= SFAX_LED1_BIT; | ||
204 | if (arg & 2) | ||
205 | sf->aux_data |= SFAX_LED2_BIT; | ||
206 | outb(sf->aux_data, sf->cfg + TIGER_AUX_DATA); | ||
207 | break; | ||
208 | default: | ||
209 | pr_info("%s: %s unknown command %x %lx\n", | ||
210 | sf->name, __func__, cmd, arg); | ||
211 | ret = -EINVAL; | ||
212 | break; | ||
213 | } | ||
214 | return ret; | ||
215 | } | ||
216 | |||
217 | static int | ||
218 | channel_ctrl(struct sfax_hw *sf, struct mISDN_ctrl_req *cq) | ||
219 | { | ||
220 | int ret = 0; | ||
221 | |||
222 | switch (cq->op) { | ||
223 | case MISDN_CTRL_GETOP: | ||
224 | cq->op = MISDN_CTRL_LOOP; | ||
225 | break; | ||
226 | case MISDN_CTRL_LOOP: | ||
227 | /* cq->channel: 0 disable, 1 B1 loop 2 B2 loop, 3 both */ | ||
228 | if (cq->channel < 0 || cq->channel > 3) { | ||
229 | ret = -EINVAL; | ||
230 | break; | ||
231 | } | ||
232 | ret = sf->isac.ctrl(&sf->isac, HW_TESTLOOP, cq->channel); | ||
233 | break; | ||
234 | default: | ||
235 | pr_info("%s: unknown Op %x\n", sf->name, cq->op); | ||
236 | ret = -EINVAL; | ||
237 | break; | ||
238 | } | ||
239 | return ret; | ||
240 | } | ||
241 | |||
242 | static int | ||
243 | sfax_dctrl(struct mISDNchannel *ch, u32 cmd, void *arg) | ||
244 | { | ||
245 | struct mISDNdevice *dev = container_of(ch, struct mISDNdevice, D); | ||
246 | struct dchannel *dch = container_of(dev, struct dchannel, dev); | ||
247 | struct sfax_hw *sf = dch->hw; | ||
248 | struct channel_req *rq; | ||
249 | int err = 0; | ||
250 | |||
251 | pr_debug("%s: cmd:%x %p\n", sf->name, cmd, arg); | ||
252 | switch (cmd) { | ||
253 | case OPEN_CHANNEL: | ||
254 | rq = arg; | ||
255 | if (rq->protocol == ISDN_P_TE_S0) | ||
256 | err = sf->isac.open(&sf->isac, rq); | ||
257 | else | ||
258 | err = sf->isar.open(&sf->isar, rq); | ||
259 | if (err) | ||
260 | break; | ||
261 | if (!try_module_get(THIS_MODULE)) | ||
262 | pr_info("%s: cannot get module\n", sf->name); | ||
263 | break; | ||
264 | case CLOSE_CHANNEL: | ||
265 | pr_debug("%s: dev(%d) close from %p\n", sf->name, | ||
266 | dch->dev.id, __builtin_return_address(0)); | ||
267 | module_put(THIS_MODULE); | ||
268 | break; | ||
269 | case CONTROL_CHANNEL: | ||
270 | err = channel_ctrl(sf, arg); | ||
271 | break; | ||
272 | default: | ||
273 | pr_debug("%s: unknown command %x\n", sf->name, cmd); | ||
274 | return -EINVAL; | ||
275 | } | ||
276 | return err; | ||
277 | } | ||
278 | |||
279 | static int __devinit | ||
280 | init_card(struct sfax_hw *sf) | ||
281 | { | ||
282 | int ret, cnt = 3; | ||
283 | u_long flags; | ||
284 | |||
285 | ret = request_irq(sf->irq, speedfax_irq, IRQF_SHARED, sf->name, sf); | ||
286 | if (ret) { | ||
287 | pr_info("%s: couldn't get interrupt %d\n", sf->name, sf->irq); | ||
288 | return ret; | ||
289 | } | ||
290 | while (cnt--) { | ||
291 | spin_lock_irqsave(&sf->lock, flags); | ||
292 | ret = sf->isac.init(&sf->isac); | ||
293 | if (ret) { | ||
294 | spin_unlock_irqrestore(&sf->lock, flags); | ||
295 | pr_info("%s: ISAC init failed with %d\n", | ||
296 | sf->name, ret); | ||
297 | break; | ||
298 | } | ||
299 | enable_hwirq(sf); | ||
300 | /* RESET Receiver and Transmitter */ | ||
301 | WriteISAC_IND(sf, ISAC_CMDR, 0x41); | ||
302 | spin_unlock_irqrestore(&sf->lock, flags); | ||
303 | msleep_interruptible(10); | ||
304 | if (debug & DEBUG_HW) | ||
305 | pr_notice("%s: IRQ %d count %d\n", sf->name, | ||
306 | sf->irq, sf->irqcnt); | ||
307 | if (!sf->irqcnt) { | ||
308 | pr_info("%s: IRQ(%d) got no requests during init %d\n", | ||
309 | sf->name, sf->irq, 3 - cnt); | ||
310 | } else | ||
311 | return 0; | ||
312 | } | ||
313 | free_irq(sf->irq, sf); | ||
314 | return -EIO; | ||
315 | } | ||
316 | |||
317 | |||
318 | static int __devinit | ||
319 | setup_speedfax(struct sfax_hw *sf) | ||
320 | { | ||
321 | u_long flags; | ||
322 | |||
323 | if (!request_region(sf->cfg, 256, sf->name)) { | ||
324 | pr_info("mISDN: %s config port %x-%x already in use\n", | ||
325 | sf->name, sf->cfg, sf->cfg + 255); | ||
326 | return -EIO; | ||
327 | } | ||
328 | outb(0xff, sf->cfg); | ||
329 | outb(0, sf->cfg); | ||
330 | outb(0xdd, sf->cfg + TIGER_AUX_CTRL); | ||
331 | outb(0, sf->cfg + TIGER_AUX_IRQMASK); | ||
332 | |||
333 | sf->isac.type = IPAC_TYPE_ISAC; | ||
334 | sf->p_isac.ale = sf->cfg + SFAX_PCI_ADDR; | ||
335 | sf->p_isac.port = sf->cfg + SFAX_PCI_ISAC; | ||
336 | sf->p_isar.ale = sf->cfg + SFAX_PCI_ADDR; | ||
337 | sf->p_isar.port = sf->cfg + SFAX_PCI_ISAR; | ||
338 | ASSIGN_FUNC(IND, ISAC, sf->isac); | ||
339 | ASSIGN_FUNC(IND, ISAR, sf->isar); | ||
340 | spin_lock_irqsave(&sf->lock, flags); | ||
341 | reset_speedfax(sf); | ||
342 | disable_hwirq(sf); | ||
343 | spin_unlock_irqrestore(&sf->lock, flags); | ||
344 | return 0; | ||
345 | } | ||
346 | |||
347 | static void | ||
348 | release_card(struct sfax_hw *card) { | ||
349 | u_long flags; | ||
350 | |||
351 | spin_lock_irqsave(&card->lock, flags); | ||
352 | disable_hwirq(card); | ||
353 | spin_unlock_irqrestore(&card->lock, flags); | ||
354 | card->isac.release(&card->isac); | ||
355 | free_irq(card->irq, card); | ||
356 | card->isar.release(&card->isar); | ||
357 | mISDN_unregister_device(&card->isac.dch.dev); | ||
358 | release_region(card->cfg, 256); | ||
359 | pci_disable_device(card->pdev); | ||
360 | pci_set_drvdata(card->pdev, NULL); | ||
361 | write_lock_irqsave(&card_lock, flags); | ||
362 | list_del(&card->list); | ||
363 | write_unlock_irqrestore(&card_lock, flags); | ||
364 | kfree(card); | ||
365 | sfax_cnt--; | ||
366 | } | ||
367 | |||
368 | static int __devinit | ||
369 | setup_instance(struct sfax_hw *card) | ||
370 | { | ||
371 | const struct firmware *firmware; | ||
372 | int i, err; | ||
373 | u_long flags; | ||
374 | |||
375 | snprintf(card->name, MISDN_MAX_IDLEN - 1, "Speedfax.%d", sfax_cnt + 1); | ||
376 | write_lock_irqsave(&card_lock, flags); | ||
377 | list_add_tail(&card->list, &Cards); | ||
378 | write_unlock_irqrestore(&card_lock, flags); | ||
379 | _set_debug(card); | ||
380 | spin_lock_init(&card->lock); | ||
381 | card->isac.hwlock = &card->lock; | ||
382 | card->isar.hwlock = &card->lock; | ||
383 | card->isar.ctrl = (void *)&sfax_ctrl; | ||
384 | card->isac.name = card->name; | ||
385 | card->isar.name = card->name; | ||
386 | card->isar.owner = THIS_MODULE; | ||
387 | |||
388 | err = request_firmware(&firmware, "isdn/ISAR.BIN", &card->pdev->dev); | ||
389 | if (err < 0) { | ||
390 | pr_info("%s: firmware request failed %d\n", | ||
391 | card->name, err); | ||
392 | goto error_fw; | ||
393 | } | ||
394 | if (debug & DEBUG_HW) | ||
395 | pr_notice("%s: got firmware %zu bytes\n", | ||
396 | card->name, firmware->size); | ||
397 | |||
398 | mISDNisac_init(&card->isac, card); | ||
399 | |||
400 | card->isac.dch.dev.D.ctrl = sfax_dctrl; | ||
401 | card->isac.dch.dev.Bprotocols = | ||
402 | mISDNisar_init(&card->isar, card); | ||
403 | for (i = 0; i < 2; i++) { | ||
404 | set_channelmap(i + 1, card->isac.dch.dev.channelmap); | ||
405 | list_add(&card->isar.ch[i].bch.ch.list, | ||
406 | &card->isac.dch.dev.bchannels); | ||
407 | } | ||
408 | |||
409 | err = setup_speedfax(card); | ||
410 | if (err) | ||
411 | goto error_setup; | ||
412 | err = card->isar.init(&card->isar); | ||
413 | if (err) | ||
414 | goto error; | ||
415 | err = mISDN_register_device(&card->isac.dch.dev, | ||
416 | &card->pdev->dev, card->name); | ||
417 | if (err) | ||
418 | goto error; | ||
419 | err = init_card(card); | ||
420 | if (err) | ||
421 | goto error_init; | ||
422 | err = card->isar.firmware(&card->isar, firmware->data, firmware->size); | ||
423 | if (!err) { | ||
424 | release_firmware(firmware); | ||
425 | sfax_cnt++; | ||
426 | pr_notice("SpeedFax %d cards installed\n", sfax_cnt); | ||
427 | return 0; | ||
428 | } | ||
429 | disable_hwirq(card); | ||
430 | free_irq(card->irq, card); | ||
431 | error_init: | ||
432 | mISDN_unregister_device(&card->isac.dch.dev); | ||
433 | error: | ||
434 | release_region(card->cfg, 256); | ||
435 | error_setup: | ||
436 | card->isac.release(&card->isac); | ||
437 | card->isar.release(&card->isar); | ||
438 | release_firmware(firmware); | ||
439 | error_fw: | ||
440 | pci_disable_device(card->pdev); | ||
441 | write_lock_irqsave(&card_lock, flags); | ||
442 | list_del(&card->list); | ||
443 | write_unlock_irqrestore(&card_lock, flags); | ||
444 | kfree(card); | ||
445 | return err; | ||
446 | } | ||
447 | |||
448 | static int __devinit | ||
449 | sfaxpci_probe(struct pci_dev *pdev, const struct pci_device_id *ent) | ||
450 | { | ||
451 | int err = -ENOMEM; | ||
452 | struct sfax_hw *card = kzalloc(sizeof(struct sfax_hw), GFP_KERNEL); | ||
453 | |||
454 | if (!card) { | ||
455 | pr_info("No memory for Speedfax+ PCI\n"); | ||
456 | return err; | ||
457 | } | ||
458 | card->pdev = pdev; | ||
459 | err = pci_enable_device(pdev); | ||
460 | if (err) { | ||
461 | kfree(card); | ||
462 | return err; | ||
463 | } | ||
464 | |||
465 | pr_notice("mISDN: Speedfax found adapter %s at %s\n", | ||
466 | (char *)ent->driver_data, pci_name(pdev)); | ||
467 | |||
468 | card->cfg = pci_resource_start(pdev, 0); | ||
469 | card->irq = pdev->irq; | ||
470 | pci_set_drvdata(pdev, card); | ||
471 | err = setup_instance(card); | ||
472 | if (err) | ||
473 | pci_set_drvdata(pdev, NULL); | ||
474 | return err; | ||
475 | } | ||
476 | |||
477 | static void __devexit | ||
478 | sfax_remove_pci(struct pci_dev *pdev) | ||
479 | { | ||
480 | struct sfax_hw *card = pci_get_drvdata(pdev); | ||
481 | |||
482 | if (card) | ||
483 | release_card(card); | ||
484 | else | ||
485 | pr_debug("%s: drvdata allready removed\n", __func__); | ||
486 | } | ||
487 | |||
488 | static struct pci_device_id sfaxpci_ids[] __devinitdata = { | ||
489 | { PCI_VENDOR_ID_TIGERJET, PCI_DEVICE_ID_TIGERJET_100, | ||
490 | PCI_SUBVENDOR_SPEEDFAX_PYRAMID, PCI_SUB_ID_SEDLBAUER, | ||
491 | 0, 0, (unsigned long) "Pyramid Speedfax + PCI" | ||
492 | }, | ||
493 | { PCI_VENDOR_ID_TIGERJET, PCI_DEVICE_ID_TIGERJET_100, | ||
494 | PCI_SUBVENDOR_SPEEDFAX_PCI, PCI_SUB_ID_SEDLBAUER, | ||
495 | 0, 0, (unsigned long) "Sedlbauer Speedfax + PCI" | ||
496 | }, | ||
497 | { } | ||
498 | }; | ||
499 | MODULE_DEVICE_TABLE(pci, sfaxpci_ids); | ||
500 | |||
501 | static struct pci_driver sfaxpci_driver = { | ||
502 | .name = "speedfax+ pci", | ||
503 | .probe = sfaxpci_probe, | ||
504 | .remove = __devexit_p(sfax_remove_pci), | ||
505 | .id_table = sfaxpci_ids, | ||
506 | }; | ||
507 | |||
508 | static int __init | ||
509 | Speedfax_init(void) | ||
510 | { | ||
511 | int err; | ||
512 | |||
513 | pr_notice("Sedlbauer Speedfax+ Driver Rev. %s\n", | ||
514 | SPEEDFAX_REV); | ||
515 | err = pci_register_driver(&sfaxpci_driver); | ||
516 | return err; | ||
517 | } | ||
518 | |||
519 | static void __exit | ||
520 | Speedfax_cleanup(void) | ||
521 | { | ||
522 | pci_unregister_driver(&sfaxpci_driver); | ||
523 | } | ||
524 | |||
525 | module_init(Speedfax_init); | ||
526 | module_exit(Speedfax_cleanup); | ||