generic: ar8216: add support for the AR8327 chip
[openwrt.git] / target / linux / generic / files / drivers / net / phy / adm6996.c
1 /*
2 * ADM6996 switch driver
3 *
4 * swconfig interface based on ar8216.c
5 *
6 * Copyright (c) 2008 Felix Fietkau <nbd@openwrt.org>
7 * VLAN support Copyright (c) 2010, 2011 Peter Lebbing <peter@digitalbrains.com>
8 *
9 * This program is free software; you can redistribute it and/or modify it
10 * under the terms of the GNU General Public License v2 as published by the
11 * Free Software Foundation
12 */
13
14 /*#define DEBUG 1*/
15 #include <linux/kernel.h>
16 #include <linux/string.h>
17 #include <linux/errno.h>
18 #include <linux/unistd.h>
19 #include <linux/slab.h>
20 #include <linux/interrupt.h>
21 #include <linux/init.h>
22 #include <linux/delay.h>
23 #include <linux/netdevice.h>
24 #include <linux/etherdevice.h>
25 #include <linux/skbuff.h>
26 #include <linux/spinlock.h>
27 #include <linux/mm.h>
28 #include <linux/module.h>
29 #include <linux/mii.h>
30 #include <linux/ethtool.h>
31 #include <linux/phy.h>
32 #include <linux/switch.h>
33
34 #include <asm/io.h>
35 #include <asm/irq.h>
36 #include <asm/uaccess.h>
37 #include "adm6996.h"
38
39 MODULE_DESCRIPTION("Infineon ADM6996 Switch");
40 MODULE_AUTHOR("Felix Fietkau, Peter Lebbing <peter@digitalbrains.com>");
41 MODULE_LICENSE("GPL");
42
43 enum adm6996_model {
44 ADM6996FC,
45 ADM6996M
46 };
47
48 static const char * const adm6996_model_name[] =
49 {
50 "ADM6996FC",
51 "ADM6996M"
52 };
53
54 struct adm6996_priv {
55 struct switch_dev dev;
56 struct phy_device *phydev;
57
58 enum adm6996_model model;
59
60 bool enable_vlan;
61 bool vlan_enabled; /* Current hardware state */
62
63 #ifdef DEBUG
64 u16 addr; /* Debugging: register address to operate on */
65 #endif
66
67 u16 pvid[ADM_NUM_PORTS]; /* Primary VLAN ID */
68
69 u16 vlan_id[ADM_NUM_VLANS];
70 u8 vlan_table[ADM_NUM_VLANS]; /* bitmap, 1 = port is member */
71 u8 vlan_tagged[ADM_NUM_VLANS]; /* bitmap, 1 = tagged member */
72
73 struct mutex reg_mutex;
74
75 /* use abstraction for regops, we want to add gpio support in the future */
76 u16 (*read)(struct phy_device *phydev, enum admreg reg);
77 void (*write)(struct phy_device *phydev, enum admreg reg, u16 val);
78 };
79
80 #define to_adm(_dev) container_of(_dev, struct adm6996_priv, dev)
81 #define phy_to_adm(_phy) ((struct adm6996_priv *) (_phy)->priv)
82
83 static inline u16
84 r16(struct phy_device *pdev, enum admreg reg)
85 {
86 return phy_to_adm(pdev)->read(pdev, reg);
87 }
88
89 static inline void
90 w16(struct phy_device *pdev, enum admreg reg, u16 val)
91 {
92 phy_to_adm(pdev)->write(pdev, reg, val);
93 }
94
95
96 static u16
97 adm6996_read_mii_reg(struct phy_device *phydev, enum admreg reg)
98 {
99 return phydev->bus->read(phydev->bus, PHYADDR(reg));
100 }
101
102 static void
103 adm6996_write_mii_reg(struct phy_device *phydev, enum admreg reg, u16 val)
104 {
105 phydev->bus->write(phydev->bus, PHYADDR(reg), val);
106 }
107
108 static int
109 adm6996_set_enable_vlan(struct switch_dev *dev, const struct switch_attr *attr,
110 struct switch_val *val)
111 {
112 struct adm6996_priv *priv = to_adm(dev);
113
114 if (val->value.i > 1)
115 return -EINVAL;
116
117 priv->enable_vlan = val->value.i;
118
119 return 0;
120 };
121
122 static int
123 adm6996_get_enable_vlan(struct switch_dev *dev, const struct switch_attr *attr,
124 struct switch_val *val)
125 {
126 struct adm6996_priv *priv = to_adm(dev);
127
128 val->value.i = priv->enable_vlan;
129
130 return 0;
131 };
132
133 #ifdef DEBUG
134
135 static int
136 adm6996_set_addr(struct switch_dev *dev, const struct switch_attr *attr,
137 struct switch_val *val)
138 {
139 struct adm6996_priv *priv = to_adm(dev);
140
141 if (val->value.i > 1023)
142 return -EINVAL;
143
144 priv->addr = val->value.i;
145
146 return 0;
147 };
148
149 static int
150 adm6996_get_addr(struct switch_dev *dev, const struct switch_attr *attr,
151 struct switch_val *val)
152 {
153 struct adm6996_priv *priv = to_adm(dev);
154
155 val->value.i = priv->addr;
156
157 return 0;
158 };
159
160 static int
161 adm6996_set_data(struct switch_dev *dev, const struct switch_attr *attr,
162 struct switch_val *val)
163 {
164 struct adm6996_priv *priv = to_adm(dev);
165
166 if (val->value.i > 65535)
167 return -EINVAL;
168
169 w16(priv->phydev, priv->addr, val->value.i);
170
171 return 0;
172 };
173
174 static int
175 adm6996_get_data(struct switch_dev *dev, const struct switch_attr *attr,
176 struct switch_val *val)
177 {
178 struct adm6996_priv *priv = to_adm(dev);
179
180 val->value.i = r16(priv->phydev, priv->addr);
181
182 return 0;
183 };
184
185 #endif /* def DEBUG */
186
187 static int
188 adm6996_set_pvid(struct switch_dev *dev, int port, int vlan)
189 {
190 struct adm6996_priv *priv = to_adm(dev);
191
192 dev_dbg (&priv->phydev->dev, "set_pvid port %d vlan %d\n", port
193 , vlan);
194
195 if (vlan > ADM_VLAN_MAX_ID)
196 return -EINVAL;
197
198 priv->pvid[port] = vlan;
199
200 return 0;
201 }
202
203 static int
204 adm6996_get_pvid(struct switch_dev *dev, int port, int *vlan)
205 {
206 struct adm6996_priv *priv = to_adm(dev);
207
208 dev_dbg (&priv->phydev->dev, "get_pvid port %d\n", port);
209 *vlan = priv->pvid[port];
210
211 return 0;
212 }
213
214 static int
215 adm6996_set_vid(struct switch_dev *dev, const struct switch_attr *attr,
216 struct switch_val *val)
217 {
218 struct adm6996_priv *priv = to_adm(dev);
219
220 dev_dbg (&priv->phydev->dev, "set_vid port %d vid %d\n", val->port_vlan,
221 val->value.i);
222
223 if (val->value.i > ADM_VLAN_MAX_ID)
224 return -EINVAL;
225
226 priv->vlan_id[val->port_vlan] = val->value.i;
227
228 return 0;
229 };
230
231 static int
232 adm6996_get_vid(struct switch_dev *dev, const struct switch_attr *attr,
233 struct switch_val *val)
234 {
235 struct adm6996_priv *priv = to_adm(dev);
236
237 dev_dbg (&priv->phydev->dev, "get_vid port %d\n", val->port_vlan);
238
239 val->value.i = priv->vlan_id[val->port_vlan];
240
241 return 0;
242 };
243
244 static int
245 adm6996_get_ports(struct switch_dev *dev, struct switch_val *val)
246 {
247 struct adm6996_priv *priv = to_adm(dev);
248 u8 ports = priv->vlan_table[val->port_vlan];
249 u8 tagged = priv->vlan_tagged[val->port_vlan];
250 int i;
251
252 dev_dbg (&priv->phydev->dev, "get_ports port_vlan %d\n",
253 val->port_vlan);
254
255 val->len = 0;
256
257 for (i = 0; i < ADM_NUM_PORTS; i++) {
258 struct switch_port *p;
259
260 if (!(ports & (1 << i)))
261 continue;
262
263 p = &val->value.ports[val->len++];
264 p->id = i;
265 if (tagged & (1 << i))
266 p->flags = (1 << SWITCH_PORT_FLAG_TAGGED);
267 else
268 p->flags = 0;
269 }
270
271 return 0;
272 };
273
274 static int
275 adm6996_set_ports(struct switch_dev *dev, struct switch_val *val)
276 {
277 struct adm6996_priv *priv = to_adm(dev);
278 u8 *ports = &priv->vlan_table[val->port_vlan];
279 u8 *tagged = &priv->vlan_tagged[val->port_vlan];
280 int i;
281
282 dev_dbg (&priv->phydev->dev, "set_ports port_vlan %d ports",
283 val->port_vlan);
284
285 *ports = 0;
286 *tagged = 0;
287
288 for (i = 0; i < val->len; i++) {
289 struct switch_port *p = &val->value.ports[i];
290
291 #ifdef DEBUG
292 pr_cont(" %d%s", p->id,
293 ((p->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) ? "T" :
294 ""));
295 #endif
296
297 if (p->flags & (1 << SWITCH_PORT_FLAG_TAGGED))
298 *tagged |= (1 << p->id);
299
300 *ports |= (1 << p->id);
301 }
302
303 #ifdef DEBUG
304 pr_cont("\n");
305 #endif
306
307 return 0;
308 };
309
310 /*
311 * Precondition: reg_mutex must be held
312 */
313 static void
314 adm6996_enable_vlan(struct adm6996_priv *priv)
315 {
316 u16 reg;
317
318 reg = r16(priv->phydev, ADM_OTBE_P2_PVID);
319 reg &= ~(ADM_OTBE_MASK);
320 w16(priv->phydev, ADM_OTBE_P2_PVID, reg);
321 reg = r16(priv->phydev, ADM_IFNTE);
322 reg &= ~(ADM_IFNTE_MASK);
323 w16(priv->phydev, ADM_IFNTE, reg);
324 reg = r16(priv->phydev, ADM_VID_CHECK);
325 reg |= ADM_VID_CHECK_MASK;
326 w16(priv->phydev, ADM_VID_CHECK, reg);
327 reg = r16(priv->phydev, ADM_SYSC0);
328 reg |= ADM_NTTE;
329 reg &= ~(ADM_RVID1);
330 w16(priv->phydev, ADM_SYSC0, reg);
331 reg = r16(priv->phydev, ADM_SYSC3);
332 reg |= ADM_TBV;
333 w16(priv->phydev, ADM_SYSC3, reg);
334
335 };
336
337 /*
338 * Disable VLANs
339 *
340 * Sets VLAN mapping for port-based VLAN with all ports connected to
341 * eachother (this is also the power-on default).
342 *
343 * Precondition: reg_mutex must be held
344 */
345 static void
346 adm6996_disable_vlan(struct adm6996_priv *priv)
347 {
348 u16 reg;
349 int i;
350
351 for (i = 0; i < ADM_NUM_PORTS; i++) {
352 reg = ADM_VLAN_FILT_MEMBER_MASK;
353 w16(priv->phydev, ADM_VLAN_FILT_L(i), reg);
354 reg = ADM_VLAN_FILT_VALID | ADM_VLAN_FILT_VID(1);
355 w16(priv->phydev, ADM_VLAN_FILT_H(i), reg);
356 }
357
358 reg = r16(priv->phydev, ADM_OTBE_P2_PVID);
359 reg |= ADM_OTBE_MASK;
360 w16(priv->phydev, ADM_OTBE_P2_PVID, reg);
361 reg = r16(priv->phydev, ADM_IFNTE);
362 reg |= ADM_IFNTE_MASK;
363 w16(priv->phydev, ADM_IFNTE, reg);
364 reg = r16(priv->phydev, ADM_VID_CHECK);
365 reg &= ~(ADM_VID_CHECK_MASK);
366 w16(priv->phydev, ADM_VID_CHECK, reg);
367 reg = r16(priv->phydev, ADM_SYSC0);
368 reg &= ~(ADM_NTTE);
369 reg |= ADM_RVID1;
370 w16(priv->phydev, ADM_SYSC0, reg);
371 reg = r16(priv->phydev, ADM_SYSC3);
372 reg &= ~(ADM_TBV);
373 w16(priv->phydev, ADM_SYSC3, reg);
374 }
375
376 /*
377 * Precondition: reg_mutex must be held
378 */
379 static void
380 adm6996_apply_port_pvids(struct adm6996_priv *priv)
381 {
382 u16 reg;
383 int i;
384
385 for (i = 0; i < ADM_NUM_PORTS; i++) {
386 reg = r16(priv->phydev, adm_portcfg[i]);
387 reg &= ~(ADM_PORTCFG_PVID_MASK);
388 reg |= ADM_PORTCFG_PVID(priv->pvid[i]);
389 w16(priv->phydev, adm_portcfg[i], reg);
390 }
391
392 w16(priv->phydev, ADM_P0_PVID, ADM_P0_PVID_VAL(priv->pvid[0]));
393 w16(priv->phydev, ADM_P1_PVID, ADM_P1_PVID_VAL(priv->pvid[1]));
394 reg = r16(priv->phydev, ADM_OTBE_P2_PVID);
395 reg &= ~(ADM_P2_PVID_MASK);
396 reg |= ADM_P2_PVID_VAL(priv->pvid[2]);
397 w16(priv->phydev, ADM_OTBE_P2_PVID, reg);
398 reg = ADM_P3_PVID_VAL(priv->pvid[3]);
399 reg |= ADM_P4_PVID_VAL(priv->pvid[4]);
400 w16(priv->phydev, ADM_P3_P4_PVID, reg);
401 w16(priv->phydev, ADM_P5_PVID, ADM_P5_PVID_VAL(priv->pvid[5]));
402 }
403
404 /*
405 * Precondition: reg_mutex must be held
406 */
407 static void
408 adm6996_apply_vlan_filters(struct adm6996_priv *priv)
409 {
410 u8 ports, tagged;
411 u16 vid, reg;
412 int i;
413
414 for (i = 0; i < ADM_NUM_VLANS; i++) {
415 vid = priv->vlan_id[i];
416 ports = priv->vlan_table[i];
417 tagged = priv->vlan_tagged[i];
418
419 if (ports == 0) {
420 /* Disable VLAN entry */
421 w16(priv->phydev, ADM_VLAN_FILT_H(i), 0);
422 w16(priv->phydev, ADM_VLAN_FILT_L(i), 0);
423 continue;
424 }
425
426 reg = ADM_VLAN_FILT_MEMBER(ports);
427 reg |= ADM_VLAN_FILT_TAGGED(tagged);
428 w16(priv->phydev, ADM_VLAN_FILT_L(i), reg);
429 reg = ADM_VLAN_FILT_VALID | ADM_VLAN_FILT_VID(vid);
430 w16(priv->phydev, ADM_VLAN_FILT_H(i), reg);
431 }
432 }
433
434 static int
435 adm6996_hw_apply(struct switch_dev *dev)
436 {
437 struct adm6996_priv *priv = to_adm(dev);
438
439 dev_dbg(&priv->phydev->dev, "hw_apply\n");
440
441 mutex_lock(&priv->reg_mutex);
442
443 if (!priv->enable_vlan) {
444 if (priv->vlan_enabled) {
445 adm6996_disable_vlan(priv);
446 priv->vlan_enabled = 0;
447 }
448 goto out;
449 }
450
451 if (!priv->vlan_enabled) {
452 adm6996_enable_vlan(priv);
453 priv->vlan_enabled = 1;
454 }
455
456 adm6996_apply_port_pvids(priv);
457 adm6996_apply_vlan_filters(priv);
458
459 out:
460 mutex_unlock(&priv->reg_mutex);
461
462 return 0;
463 }
464
465 /*
466 * Reset the switch
467 *
468 * The ADM6996 can't do a software-initiated reset, so we just initialise the
469 * registers we support in this driver.
470 *
471 * Precondition: reg_mutex must be held
472 */
473 static void
474 adm6996_perform_reset (struct adm6996_priv *priv)
475 {
476 int i;
477
478 /* initialize port and vlan settings */
479 for (i = 0; i < ADM_NUM_PORTS - 1; i++) {
480 w16(priv->phydev, adm_portcfg[i], ADM_PORTCFG_INIT |
481 ADM_PORTCFG_PVID(0));
482 }
483 w16(priv->phydev, adm_portcfg[5], ADM_PORTCFG_CPU);
484
485 /* reset all PHY ports */
486 for (i = 0; i < ADM_PHY_PORTS; i++) {
487 w16(priv->phydev, ADM_PHY_PORT(i), ADM_PHYCFG_INIT);
488 }
489
490 priv->enable_vlan = 0;
491 priv->vlan_enabled = 0;
492
493 for (i = 0; i < ADM_NUM_PORTS; i++) {
494 priv->pvid[i] = 0;
495 }
496
497 for (i = 0; i < ADM_NUM_VLANS; i++) {
498 priv->vlan_id[i] = i;
499 priv->vlan_table[i] = 0;
500 priv->vlan_tagged[i] = 0;
501 }
502
503 if (priv->model == ADM6996M) {
504 /* Clear VLAN priority map so prio's are unused */
505 w16 (priv->phydev, ADM_VLAN_PRIOMAP, 0);
506
507 adm6996_disable_vlan(priv);
508 adm6996_apply_port_pvids(priv);
509 }
510 }
511
512 static int
513 adm6996_reset_switch(struct switch_dev *dev)
514 {
515 struct adm6996_priv *priv = to_adm(dev);
516
517 dev_dbg (&priv->phydev->dev, "reset\n");
518 mutex_lock(&priv->reg_mutex);
519 adm6996_perform_reset (priv);
520 mutex_unlock(&priv->reg_mutex);
521 return 0;
522 }
523
524 static struct switch_attr adm6996_globals[] = {
525 {
526 .type = SWITCH_TYPE_INT,
527 .name = "enable_vlan",
528 .description = "Enable VLANs",
529 .set = adm6996_set_enable_vlan,
530 .get = adm6996_get_enable_vlan,
531 },
532 #ifdef DEBUG
533 {
534 .type = SWITCH_TYPE_INT,
535 .name = "addr",
536 .description =
537 "Direct register access: set register address (0 - 1023)",
538 .set = adm6996_set_addr,
539 .get = adm6996_get_addr,
540 },
541 {
542 .type = SWITCH_TYPE_INT,
543 .name = "data",
544 .description =
545 "Direct register access: read/write to register (0 - 65535)",
546 .set = adm6996_set_data,
547 .get = adm6996_get_data,
548 },
549 #endif /* def DEBUG */
550 };
551
552 static struct switch_attr adm6996_port[] = {
553 };
554
555 static struct switch_attr adm6996_vlan[] = {
556 {
557 .type = SWITCH_TYPE_INT,
558 .name = "vid",
559 .description = "VLAN ID",
560 .set = adm6996_set_vid,
561 .get = adm6996_get_vid,
562 },
563 };
564
565 static const struct switch_dev_ops adm6996_ops = {
566 .attr_global = {
567 .attr = adm6996_globals,
568 .n_attr = ARRAY_SIZE(adm6996_globals),
569 },
570 .attr_port = {
571 .attr = adm6996_port,
572 .n_attr = ARRAY_SIZE(adm6996_port),
573 },
574 .attr_vlan = {
575 .attr = adm6996_vlan,
576 .n_attr = ARRAY_SIZE(adm6996_vlan),
577 },
578 .get_port_pvid = adm6996_get_pvid,
579 .set_port_pvid = adm6996_set_pvid,
580 .get_vlan_ports = adm6996_get_ports,
581 .set_vlan_ports = adm6996_set_ports,
582 .apply_config = adm6996_hw_apply,
583 .reset_switch = adm6996_reset_switch,
584 };
585
586 static int adm6996_config_init(struct phy_device *pdev)
587 {
588 struct adm6996_priv *priv;
589 struct switch_dev *swdev;
590
591 int ret;
592 u16 test, old;
593
594 pdev->supported = ADVERTISED_100baseT_Full;
595 pdev->advertising = ADVERTISED_100baseT_Full;
596
597 if (pdev->addr != 0) {
598 pr_info ("%s: PHY overlaps ADM6996, providing fixed PHY 0x%x.\n"
599 , pdev->attached_dev->name, pdev->addr);
600 return 0;
601 }
602
603 priv = kzalloc(sizeof(struct adm6996_priv), GFP_KERNEL);
604 if (priv == NULL)
605 return -ENOMEM;
606
607 mutex_init(&priv->reg_mutex);
608 priv->phydev = pdev;
609 priv->read = adm6996_read_mii_reg;
610 priv->write = adm6996_write_mii_reg;
611 pdev->priv = priv;
612
613 /* Detect type of chip */
614 old = r16(pdev, ADM_VID_CHECK);
615 test = old ^ (1 << 12);
616 w16(pdev, ADM_VID_CHECK, test);
617 test ^= r16(pdev, ADM_VID_CHECK);
618 if (test & (1 << 12)) {
619 /*
620 * Bit 12 of this register is read-only.
621 * This is the FC model.
622 */
623 priv->model = ADM6996FC;
624 } else {
625 /* Bit 12 is read-write. This is the M model. */
626 priv->model = ADM6996M;
627 w16(pdev, ADM_VID_CHECK, old);
628 }
629
630 swdev = &priv->dev;
631 swdev->name = (adm6996_model_name[priv->model]);
632 swdev->cpu_port = ADM_CPU_PORT;
633 swdev->ports = ADM_NUM_PORTS;
634 swdev->vlans = ADM_NUM_VLANS;
635 swdev->ops = &adm6996_ops;
636
637 pr_info ("%s: %s model PHY found.\n", pdev->attached_dev->name,
638 swdev->name);
639
640 mutex_lock(&priv->reg_mutex);
641 adm6996_perform_reset (priv);
642 mutex_unlock(&priv->reg_mutex);
643
644 if (priv->model == ADM6996M) {
645 if ((ret = register_switch(swdev, pdev->attached_dev)) < 0) {
646 kfree(priv);
647 return ret;
648 }
649 }
650
651 return 0;
652 }
653
654 /*
655 * Warning: phydev->priv is NULL if phydev->addr != 0
656 */
657 static int adm6996_read_status(struct phy_device *phydev)
658 {
659 phydev->speed = SPEED_100;
660 phydev->duplex = DUPLEX_FULL;
661 phydev->link = 1;
662 return 0;
663 }
664
665 /*
666 * Warning: phydev->priv is NULL if phydev->addr != 0
667 */
668 static int adm6996_config_aneg(struct phy_device *phydev)
669 {
670 return 0;
671 }
672
673 static int adm6996_fixup(struct phy_device *dev)
674 {
675 struct mii_bus *bus = dev->bus;
676 u16 reg;
677
678 /* Our custom registers are at PHY addresses 0-10. Claim those. */
679 if (dev->addr > 10)
680 return 0;
681
682 /* look for the switch on the bus */
683 reg = bus->read(bus, PHYADDR(ADM_SIG0)) & ADM_SIG0_MASK;
684 if (reg != ADM_SIG0_VAL)
685 return 0;
686
687 reg = bus->read(bus, PHYADDR(ADM_SIG1)) & ADM_SIG1_MASK;
688 if (reg != ADM_SIG1_VAL)
689 return 0;
690
691 dev->phy_id = (ADM_SIG0_VAL << 16) | ADM_SIG1_VAL;
692
693 return 0;
694 }
695
696 static int adm6996_probe(struct phy_device *pdev)
697 {
698 return 0;
699 }
700
701 static void adm6996_remove(struct phy_device *pdev)
702 {
703 struct adm6996_priv *priv = phy_to_adm(pdev);
704
705 if (priv != NULL && priv->model == ADM6996M)
706 unregister_switch(&priv->dev);
707
708 kfree(priv);
709 }
710
711
712 static struct phy_driver adm6996_driver = {
713 .name = "Infineon ADM6996",
714 .phy_id = (ADM_SIG0_VAL << 16) | ADM_SIG1_VAL,
715 .phy_id_mask = 0xffffffff,
716 .features = PHY_BASIC_FEATURES,
717 .probe = adm6996_probe,
718 .remove = adm6996_remove,
719 .config_init = &adm6996_config_init,
720 .config_aneg = &adm6996_config_aneg,
721 .read_status = &adm6996_read_status,
722 .driver = { .owner = THIS_MODULE,},
723 };
724
725 static int __init adm6996_init(void)
726 {
727 phy_register_fixup_for_id(PHY_ANY_ID, adm6996_fixup);
728 return phy_driver_register(&adm6996_driver);
729 }
730
731 static void __exit adm6996_exit(void)
732 {
733 phy_driver_unregister(&adm6996_driver);
734 }
735
736 module_init(adm6996_init);
737 module_exit(adm6996_exit);
This page took 0.084592 seconds and 5 git commands to generate.