1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18#include "crystalhd.h"
19
20#include <linux/mutex.h>
21#include <linux/slab.h>
22
23
24static DEFINE_MUTEX(chd_dec_mutex);
25static struct class *crystalhd_class;
26
27static struct crystalhd_adp *g_adp_info;
28
29static irqreturn_t chd_dec_isr(int irq, void *arg)
30{
31 struct crystalhd_adp *adp = (struct crystalhd_adp *) arg;
32 int rc = 0;
33 if (adp)
34 rc = crystalhd_cmd_interrupt(&adp->cmds);
35
36 return IRQ_RETVAL(rc);
37}
38
39static int chd_dec_enable_int(struct crystalhd_adp *adp)
40{
41 int rc = 0;
42
43 if (!adp || !adp->pdev) {
44 BCMLOG_ERR("Invalid arg!!\n");
45 return -EINVAL;
46 }
47
48 if (adp->pdev->msi_enabled)
49 adp->msi = 1;
50 else
51 adp->msi = pci_enable_msi(adp->pdev);
52
53 rc = request_irq(adp->pdev->irq, chd_dec_isr, IRQF_SHARED,
54 adp->name, (void *)adp);
55 if (rc) {
56 BCMLOG_ERR("Interrupt request failed..\n");
57 pci_disable_msi(adp->pdev);
58 }
59
60 return rc;
61}
62
63static int chd_dec_disable_int(struct crystalhd_adp *adp)
64{
65 if (!adp || !adp->pdev) {
66 BCMLOG_ERR("Invalid arg!!\n");
67 return -EINVAL;
68 }
69
70 free_irq(adp->pdev->irq, adp);
71
72 if (adp->msi)
73 pci_disable_msi(adp->pdev);
74
75 return 0;
76}
77
78struct crystalhd_ioctl_data *chd_dec_alloc_iodata(struct crystalhd_adp *adp, bool isr)
79{
80 unsigned long flags = 0;
81 struct crystalhd_ioctl_data *temp;
82
83 if (!adp)
84 return NULL;
85
86 spin_lock_irqsave(&adp->lock, flags);
87
88 temp = adp->idata_free_head;
89 if (temp) {
90 adp->idata_free_head = adp->idata_free_head->next;
91 memset(temp, 0, sizeof(*temp));
92 }
93
94 spin_unlock_irqrestore(&adp->lock, flags);
95 return temp;
96}
97
98void chd_dec_free_iodata(struct crystalhd_adp *adp, struct crystalhd_ioctl_data *iodata,
99 bool isr)
100{
101 unsigned long flags = 0;
102
103 if (!adp || !iodata)
104 return;
105
106 spin_lock_irqsave(&adp->lock, flags);
107 iodata->next = adp->idata_free_head;
108 adp->idata_free_head = iodata;
109 spin_unlock_irqrestore(&adp->lock, flags);
110}
111
112static inline int crystalhd_user_data(unsigned long ud, void *dr, int size, int set)
113{
114 int rc;
115
116 if (!ud || !dr) {
117 BCMLOG_ERR("Invalid arg\n");
118 return -EINVAL;
119 }
120
121 if (set)
122 rc = copy_to_user((void *)ud, dr, size);
123 else
124 rc = copy_from_user(dr, (void *)ud, size);
125
126 if (rc) {
127 BCMLOG_ERR("Invalid args for command\n");
128 rc = -EFAULT;
129 }
130
131 return rc;
132}
133
134static int chd_dec_fetch_cdata(struct crystalhd_adp *adp, struct crystalhd_ioctl_data *io,
135 uint32_t m_sz, unsigned long ua)
136{
137 unsigned long ua_off;
138 int rc = 0;
139
140 if (!adp || !io || !ua || !m_sz) {
141 BCMLOG_ERR("Invalid Arg!!\n");
142 return -EINVAL;
143 }
144
145 io->add_cdata = vmalloc(m_sz);
146 if (!io->add_cdata) {
147 BCMLOG_ERR("kalloc fail for sz:%x\n", m_sz);
148 return -ENOMEM;
149 }
150
151 io->add_cdata_sz = m_sz;
152 ua_off = ua + sizeof(io->udata);
153 rc = crystalhd_user_data(ua_off, io->add_cdata, io->add_cdata_sz, 0);
154 if (rc) {
155 BCMLOG_ERR("failed to pull add_cdata sz:%x ua_off:%x\n",
156 io->add_cdata_sz, (unsigned int)ua_off);
157 kfree(io->add_cdata);
158 io->add_cdata = NULL;
159 return -ENODATA;
160 }
161
162 return rc;
163}
164
165static int chd_dec_release_cdata(struct crystalhd_adp *adp,
166 struct crystalhd_ioctl_data *io, unsigned long ua)
167{
168 unsigned long ua_off;
169 int rc;
170
171 if (!adp || !io || !ua) {
172 BCMLOG_ERR("Invalid Arg!!\n");
173 return -EINVAL;
174 }
175
176 if (io->cmd != BCM_IOC_FW_DOWNLOAD) {
177 ua_off = ua + sizeof(io->udata);
178 rc = crystalhd_user_data(ua_off, io->add_cdata,
179 io->add_cdata_sz, 1);
180 if (rc) {
181 BCMLOG_ERR("failed to push add_cdata sz:%x ua_off:%x\n",
182 io->add_cdata_sz, (unsigned int)ua_off);
183 return -ENODATA;
184 }
185 }
186
187 if (io->add_cdata) {
188 vfree(io->add_cdata);
189 io->add_cdata = NULL;
190 }
191
192 return 0;
193}
194
195static int chd_dec_proc_user_data(struct crystalhd_adp *adp,
196 struct crystalhd_ioctl_data *io,
197 unsigned long ua, int set)
198{
199 int rc;
200 uint32_t m_sz = 0;
201
202 if (!adp || !io || !ua) {
203 BCMLOG_ERR("Invalid Arg!!\n");
204 return -EINVAL;
205 }
206
207 rc = crystalhd_user_data(ua, &io->udata, sizeof(io->udata), set);
208 if (rc) {
209 BCMLOG_ERR("failed to %s iodata\n", (set ? "set" : "get"));
210 return rc;
211 }
212
213 switch (io->cmd) {
214 case BCM_IOC_MEM_RD:
215 case BCM_IOC_MEM_WR:
216 case BCM_IOC_FW_DOWNLOAD:
217 m_sz = io->udata.u.devMem.NumDwords * 4;
218 if (set)
219 rc = chd_dec_release_cdata(adp, io, ua);
220 else
221 rc = chd_dec_fetch_cdata(adp, io, m_sz, ua);
222 break;
223 default:
224 break;
225 }
226
227 return rc;
228}
229
230static int chd_dec_api_cmd(struct crystalhd_adp *adp, unsigned long ua,
231 uint32_t uid, uint32_t cmd, crystalhd_cmd_proc func)
232{
233 int rc;
234 struct crystalhd_ioctl_data *temp;
235 enum BC_STATUS sts = BC_STS_SUCCESS;
236
237 temp = chd_dec_alloc_iodata(adp, 0);
238 if (!temp) {
239 BCMLOG_ERR("Failed to get iodata..\n");
240 return -EINVAL;
241 }
242
243 temp->u_id = uid;
244 temp->cmd = cmd;
245
246 rc = chd_dec_proc_user_data(adp, temp, ua, 0);
247 if (!rc) {
248 sts = func(&adp->cmds, temp);
249 if (sts == BC_STS_PENDING)
250 sts = BC_STS_NOT_IMPL;
251 temp->udata.RetSts = sts;
252 rc = chd_dec_proc_user_data(adp, temp, ua, 1);
253 }
254
255 if (temp) {
256 chd_dec_free_iodata(adp, temp, 0);
257 temp = NULL;
258 }
259
260 return rc;
261}
262
263
264static long chd_dec_ioctl(struct file *fd, unsigned int cmd, unsigned long ua)
265{
266 struct crystalhd_adp *adp = chd_get_adp();
267 crystalhd_cmd_proc cproc;
268 struct crystalhd_user *uc;
269 int ret;
270
271 if (!adp || !fd) {
272 BCMLOG_ERR("Invalid adp\n");
273 return -EINVAL;
274 }
275
276 uc = fd->private_data;
277 if (!uc) {
278 BCMLOG_ERR("Failed to get uc\n");
279 return -ENODATA;
280 }
281
282 mutex_lock(&chd_dec_mutex);
283 cproc = crystalhd_get_cmd_proc(&adp->cmds, cmd, uc);
284 if (!cproc) {
285 BCMLOG_ERR("Unhandled command: %d\n", cmd);
286 mutex_unlock(&chd_dec_mutex);
287 return -EINVAL;
288 }
289
290 ret = chd_dec_api_cmd(adp, ua, uc->uid, cmd, cproc);
291 mutex_unlock(&chd_dec_mutex);
292 return ret;
293}
294
295static int chd_dec_open(struct inode *in, struct file *fd)
296{
297 struct crystalhd_adp *adp = chd_get_adp();
298 int rc = 0;
299 enum BC_STATUS sts = BC_STS_SUCCESS;
300 struct crystalhd_user *uc = NULL;
301
302 if (!adp) {
303 BCMLOG_ERR("Invalid adp\n");
304 return -EINVAL;
305 }
306
307 if (adp->cfg_users >= BC_LINK_MAX_OPENS) {
308 BCMLOG(BCMLOG_INFO, "Already in use.%d\n", adp->cfg_users);
309 return -EBUSY;
310 }
311
312 sts = crystalhd_user_open(&adp->cmds, &uc);
313 if (sts != BC_STS_SUCCESS) {
314 BCMLOG_ERR("cmd_user_open - %d\n", sts);
315 rc = -EBUSY;
316 }
317
318 adp->cfg_users++;
319
320 fd->private_data = uc;
321
322 return rc;
323}
324
325static int chd_dec_close(struct inode *in, struct file *fd)
326{
327 struct crystalhd_adp *adp = chd_get_adp();
328 struct crystalhd_user *uc;
329
330 if (!adp) {
331 BCMLOG_ERR("Invalid adp\n");
332 return -EINVAL;
333 }
334
335 uc = fd->private_data;
336 if (!uc) {
337 BCMLOG_ERR("Failed to get uc\n");
338 return -ENODATA;
339 }
340
341 crystalhd_user_close(&adp->cmds, uc);
342
343 adp->cfg_users--;
344
345 return 0;
346}
347
348static const struct file_operations chd_dec_fops = {
349 .owner = THIS_MODULE,
350 .unlocked_ioctl = chd_dec_ioctl,
351 .open = chd_dec_open,
352 .release = chd_dec_close,
353 .llseek = noop_llseek,
354};
355
356static int __devinit chd_dec_init_chdev(struct crystalhd_adp *adp)
357{
358 struct crystalhd_ioctl_data *temp;
359 struct device *dev;
360 int rc = -ENODEV, i = 0;
361
362 if (!adp)
363 goto fail;
364
365 adp->chd_dec_major = register_chrdev(0, CRYSTALHD_API_NAME,
366 &chd_dec_fops);
367 if (adp->chd_dec_major < 0) {
368 BCMLOG_ERR("Failed to create config dev\n");
369 rc = adp->chd_dec_major;
370 goto fail;
371 }
372
373
374 crystalhd_class = class_create(THIS_MODULE, "crystalhd");
375 if (IS_ERR(crystalhd_class)) {
376 BCMLOG_ERR("failed to create class\n");
377 goto fail;
378 }
379
380 dev = device_create(crystalhd_class, NULL, MKDEV(adp->chd_dec_major, 0),
381 NULL, "crystalhd");
382 if (IS_ERR(dev)) {
383 BCMLOG_ERR("failed to create device\n");
384 goto device_create_fail;
385 }
386
387 rc = crystalhd_create_elem_pool(adp, BC_LINK_ELEM_POOL_SZ);
388 if (rc) {
389 BCMLOG_ERR("failed to create device\n");
390 goto elem_pool_fail;
391 }
392
393
394 for (i = 0; i < CHD_IODATA_POOL_SZ; i++) {
395 temp = kzalloc(sizeof(struct crystalhd_ioctl_data), GFP_KERNEL);
396 if (!temp) {
397 BCMLOG_ERR("ioctl data pool kzalloc failed\n");
398 rc = -ENOMEM;
399 goto kzalloc_fail;
400 }
401
402 chd_dec_free_iodata(adp, temp, 0);
403 }
404
405 return 0;
406
407kzalloc_fail:
408 crystalhd_delete_elem_pool(adp);
409elem_pool_fail:
410 device_destroy(crystalhd_class, MKDEV(adp->chd_dec_major, 0));
411device_create_fail:
412 class_destroy(crystalhd_class);
413fail:
414 return rc;
415}
416
417static void __devexit chd_dec_release_chdev(struct crystalhd_adp *adp)
418{
419 struct crystalhd_ioctl_data *temp = NULL;
420 if (!adp)
421 return;
422
423 if (adp->chd_dec_major > 0) {
424
425 device_destroy(crystalhd_class, MKDEV(adp->chd_dec_major, 0));
426 unregister_chrdev(adp->chd_dec_major, CRYSTALHD_API_NAME);
427 BCMLOG(BCMLOG_INFO, "released api device - %d\n",
428 adp->chd_dec_major);
429 class_destroy(crystalhd_class);
430 }
431 adp->chd_dec_major = 0;
432
433
434 do {
435 temp = chd_dec_alloc_iodata(adp, 0);
436 kfree(temp);
437 } while (temp);
438
439 crystalhd_delete_elem_pool(adp);
440}
441
442static int __devinit chd_pci_reserve_mem(struct crystalhd_adp *pinfo)
443{
444 int rc;
445 unsigned long bar2 = pci_resource_start(pinfo->pdev, 2);
446 uint32_t mem_len = pci_resource_len(pinfo->pdev, 2);
447 unsigned long bar0 = pci_resource_start(pinfo->pdev, 0);
448 uint32_t i2o_len = pci_resource_len(pinfo->pdev, 0);
449
450 BCMLOG(BCMLOG_SSTEP, "bar2:0x%lx-0x%08x bar0:0x%lx-0x%08x\n",
451 bar2, mem_len, bar0, i2o_len);
452
453 rc = check_mem_region(bar2, mem_len);
454 if (rc) {
455 BCMLOG_ERR("No valid mem region...\n");
456 return -ENOMEM;
457 }
458
459 pinfo->addr = ioremap_nocache(bar2, mem_len);
460 if (!pinfo->addr) {
461 BCMLOG_ERR("Failed to remap mem region...\n");
462 return -ENOMEM;
463 }
464
465 pinfo->pci_mem_start = bar2;
466 pinfo->pci_mem_len = mem_len;
467
468 rc = check_mem_region(bar0, i2o_len);
469 if (rc) {
470 BCMLOG_ERR("No valid mem region...\n");
471 return -ENOMEM;
472 }
473
474 pinfo->i2o_addr = ioremap_nocache(bar0, i2o_len);
475 if (!pinfo->i2o_addr) {
476 BCMLOG_ERR("Failed to remap mem region...\n");
477 return -ENOMEM;
478 }
479
480 pinfo->pci_i2o_start = bar0;
481 pinfo->pci_i2o_len = i2o_len;
482
483 rc = pci_request_regions(pinfo->pdev, pinfo->name);
484 if (rc < 0) {
485 BCMLOG_ERR("Region request failed: %d\n", rc);
486 return rc;
487 }
488
489 BCMLOG(BCMLOG_SSTEP, "Mapped addr:0x%08lx i2o_addr:0x%08lx\n",
490 (unsigned long)pinfo->addr, (unsigned long)pinfo->i2o_addr);
491
492 return 0;
493}
494
495static void __devexit chd_pci_release_mem(struct crystalhd_adp *pinfo)
496{
497 if (!pinfo)
498 return;
499
500 if (pinfo->addr)
501 iounmap(pinfo->addr);
502
503 if (pinfo->i2o_addr)
504 iounmap(pinfo->i2o_addr);
505
506 pci_release_regions(pinfo->pdev);
507}
508
509
510static void __devexit chd_dec_pci_remove(struct pci_dev *pdev)
511{
512 struct crystalhd_adp *pinfo;
513 enum BC_STATUS sts = BC_STS_SUCCESS;
514
515 pinfo = pci_get_drvdata(pdev);
516 if (!pinfo) {
517 BCMLOG_ERR("could not get adp\n");
518 return;
519 }
520
521 sts = crystalhd_delete_cmd_context(&pinfo->cmds);
522 if (sts != BC_STS_SUCCESS)
523 BCMLOG_ERR("cmd delete :%d\n", sts);
524
525 chd_dec_release_chdev(pinfo);
526
527 chd_dec_disable_int(pinfo);
528
529 chd_pci_release_mem(pinfo);
530 pci_disable_device(pinfo->pdev);
531
532 kfree(pinfo);
533 g_adp_info = NULL;
534}
535
536static int __devinit chd_dec_pci_probe(struct pci_dev *pdev,
537 const struct pci_device_id *entry)
538{
539 struct crystalhd_adp *pinfo;
540 int rc;
541 enum BC_STATUS sts = BC_STS_SUCCESS;
542
543 BCMLOG(BCMLOG_DBG, "PCI_INFO: Vendor:0x%04x Device:0x%04x "
544 "s_vendor:0x%04x s_device: 0x%04x\n",
545 pdev->vendor, pdev->device, pdev->subsystem_vendor,
546 pdev->subsystem_device);
547
548 pinfo = kzalloc(sizeof(struct crystalhd_adp), GFP_KERNEL);
549 if (!pinfo) {
550 BCMLOG_ERR("Failed to allocate memory\n");
551 return -ENOMEM;
552 }
553
554 pinfo->pdev = pdev;
555
556 rc = pci_enable_device(pdev);
557 if (rc) {
558 BCMLOG_ERR("Failed to enable PCI device\n");
559 goto err;
560 }
561
562 snprintf(pinfo->name, sizeof(pinfo->name), "crystalhd_pci_e:%d:%d:%d",
563 pdev->bus->number, PCI_SLOT(pdev->devfn),
564 PCI_FUNC(pdev->devfn));
565
566 rc = chd_pci_reserve_mem(pinfo);
567 if (rc) {
568 BCMLOG_ERR("Failed to setup memory regions.\n");
569 pci_disable_device(pdev);
570 rc = -ENOMEM;
571 goto err;
572 }
573
574 pinfo->present = 1;
575 pinfo->drv_data = entry->driver_data;
576
577
578 spin_lock_init(&pinfo->lock);
579
580
581 chd_dec_init_chdev(pinfo);
582 rc = chd_dec_enable_int(pinfo);
583 if (rc) {
584 BCMLOG_ERR("_enable_int err:%d\n", rc);
585 pci_disable_device(pdev);
586 rc = -ENODEV;
587 goto err;
588 }
589
590
591 if (!pci_set_dma_mask(pdev, DMA_BIT_MASK(64))) {
592 pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(64));
593 pinfo->dmabits = 64;
594 } else if (!pci_set_dma_mask(pdev, DMA_BIT_MASK(32))) {
595 pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32));
596 pinfo->dmabits = 32;
597 } else {
598 BCMLOG_ERR("Unabled to setup DMA %d\n", rc);
599 pci_disable_device(pdev);
600 rc = -ENODEV;
601 goto err;
602 }
603
604 sts = crystalhd_setup_cmd_context(&pinfo->cmds, pinfo);
605 if (sts != BC_STS_SUCCESS) {
606 BCMLOG_ERR("cmd setup :%d\n", sts);
607 pci_disable_device(pdev);
608 rc = -ENODEV;
609 goto err;
610 }
611
612 pci_set_master(pdev);
613
614 pci_set_drvdata(pdev, pinfo);
615
616 g_adp_info = pinfo;
617
618 return 0;
619
620err:
621 kfree(pinfo);
622 return rc;
623}
624
625#ifdef CONFIG_PM
626int chd_dec_pci_suspend(struct pci_dev *pdev, pm_message_t state)
627{
628 struct crystalhd_adp *adp;
629 struct crystalhd_ioctl_data *temp;
630 enum BC_STATUS sts = BC_STS_SUCCESS;
631
632 adp = pci_get_drvdata(pdev);
633 if (!adp) {
634 BCMLOG_ERR("could not get adp\n");
635 return -ENODEV;
636 }
637
638 temp = chd_dec_alloc_iodata(adp, false);
639 if (!temp) {
640 BCMLOG_ERR("could not get ioctl data\n");
641 return -ENODEV;
642 }
643
644 sts = crystalhd_suspend(&adp->cmds, temp);
645 if (sts != BC_STS_SUCCESS) {
646 BCMLOG_ERR("BCM70012 Suspend %d\n", sts);
647 return -ENODEV;
648 }
649
650 chd_dec_free_iodata(adp, temp, false);
651 chd_dec_disable_int(adp);
652 pci_save_state(pdev);
653
654
655 pci_disable_device(pdev);
656 pci_set_power_state(pdev, pci_choose_state(pdev, state));
657 return 0;
658}
659
660int chd_dec_pci_resume(struct pci_dev *pdev)
661{
662 struct crystalhd_adp *adp;
663 enum BC_STATUS sts = BC_STS_SUCCESS;
664 int rc;
665
666 adp = pci_get_drvdata(pdev);
667 if (!adp) {
668 BCMLOG_ERR("could not get adp\n");
669 return -ENODEV;
670 }
671
672 pci_set_power_state(pdev, PCI_D0);
673 pci_restore_state(pdev);
674
675
676 if (pci_enable_device(pdev)) {
677 BCMLOG_ERR("Failed to enable PCI device\n");
678 return 1;
679 }
680
681 pci_set_master(pdev);
682
683 rc = chd_dec_enable_int(adp);
684 if (rc) {
685 BCMLOG_ERR("_enable_int err:%d\n", rc);
686 pci_disable_device(pdev);
687 return -ENODEV;
688 }
689
690 sts = crystalhd_resume(&adp->cmds);
691 if (sts != BC_STS_SUCCESS) {
692 BCMLOG_ERR("BCM70012 Resume %d\n", sts);
693 pci_disable_device(pdev);
694 return -ENODEV;
695 }
696
697 return 0;
698}
699#endif
700
701static DEFINE_PCI_DEVICE_TABLE(chd_dec_pci_id_table) = {
702 { PCI_VDEVICE(BROADCOM, 0x1612), 8 },
703 { 0, },
704};
705MODULE_DEVICE_TABLE(pci, chd_dec_pci_id_table);
706
707static struct pci_driver bc_chd_70012_driver = {
708 .name = "Broadcom 70012 Decoder",
709 .probe = chd_dec_pci_probe,
710 .remove = __devexit_p(chd_dec_pci_remove),
711 .id_table = chd_dec_pci_id_table,
712#ifdef CONFIG_PM
713 .suspend = chd_dec_pci_suspend,
714 .resume = chd_dec_pci_resume
715#endif
716};
717
718void chd_set_log_level(struct crystalhd_adp *adp, char *arg)
719{
720 if ((!arg) || (strlen(arg) < 3))
721 g_linklog_level = BCMLOG_ERROR | BCMLOG_DATA;
722 else if (!strncmp(arg, "sstep", 5))
723 g_linklog_level = BCMLOG_INFO | BCMLOG_DATA | BCMLOG_DBG |
724 BCMLOG_SSTEP | BCMLOG_ERROR;
725 else if (!strncmp(arg, "info", 4))
726 g_linklog_level = BCMLOG_ERROR | BCMLOG_DATA | BCMLOG_INFO;
727 else if (!strncmp(arg, "debug", 5))
728 g_linklog_level = BCMLOG_ERROR | BCMLOG_DATA | BCMLOG_INFO |
729 BCMLOG_DBG;
730 else if (!strncmp(arg, "pball", 5))
731 g_linklog_level = 0xFFFFFFFF & ~(BCMLOG_SPINLOCK);
732 else if (!strncmp(arg, "silent", 6))
733 g_linklog_level = 0;
734 else
735 g_linklog_level = 0;
736}
737
738struct crystalhd_adp *chd_get_adp(void)
739{
740 return g_adp_info;
741}
742
743static int __init chd_dec_module_init(void)
744{
745 int rc;
746
747 chd_set_log_level(NULL, "debug");
748 BCMLOG(BCMLOG_DATA, "Loading crystalhd %d.%d.%d\n",
749 crystalhd_kmod_major, crystalhd_kmod_minor, crystalhd_kmod_rev);
750
751 rc = pci_register_driver(&bc_chd_70012_driver);
752
753 if (rc < 0)
754 BCMLOG_ERR("Could not find any devices. err:%d\n", rc);
755
756 return rc;
757}
758module_init(chd_dec_module_init);
759
760static void __exit chd_dec_module_cleanup(void)
761{
762 BCMLOG(BCMLOG_DATA, "unloading crystalhd %d.%d.%d\n",
763 crystalhd_kmod_major, crystalhd_kmod_minor, crystalhd_kmod_rev);
764
765 pci_unregister_driver(&bc_chd_70012_driver);
766}
767module_exit(chd_dec_module_cleanup);
768
769MODULE_AUTHOR("Naren Sankar <nsankar@broadcom.com>");
770MODULE_AUTHOR("Prasad Bolisetty <prasadb@broadcom.com>");
771MODULE_DESCRIPTION(CRYSTAL_HD_NAME);
772MODULE_LICENSE("GPL");
773MODULE_ALIAS("bcm70012");
774