Root/package/switch/src/switch-adm.c

1/*
2 * ADMTEK Adm6996 switch configuration module
3 *
4 * Copyright (C) 2005 Felix Fietkau <nbd@nbd.name>
5 *
6 * Partially based on Broadcom Home Networking Division 10/100 Mbit/s
7 * Ethernet Device Driver (from Montavista 2.4.20_mvl31 Kernel).
8 * Copyright (C) 2004 Broadcom Corporation
9 *
10 * adm_rreg function from adm6996
11 * Copyright (C) 2004 Nikki Chumakov <nikki@gattaca.ru>
12 *
13 * This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License
15 * as published by the Free Software Foundation; either version 2
16 * of the License, or (at your option) any later version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26 * 02110-1301, USA.
27 */
28
29#include <linux/module.h>
30#include <linux/init.h>
31#include <linux/if.h>
32#include <linux/if_arp.h>
33#include <linux/sockios.h>
34#include <linux/delay.h>
35#include <asm/uaccess.h>
36
37#include "switch-core.h"
38#include "gpio.h"
39
40#define DRIVER_NAME "adm6996"
41#define DRIVER_VERSION "0.01"
42
43static int eecs = 0;
44static int eesk = 0;
45static int eedi = 0;
46static int eerc = 0;
47static int force = 0;
48
49MODULE_AUTHOR("Felix Fietkau <openwrt@nbd.name>");
50MODULE_LICENSE("GPL");
51#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,52)
52module_param(eecs, int, 0);
53module_param(eesk, int, 0);
54module_param(eedi, int, 0);
55module_param(eerc, int, 0);
56module_param(force, int, 0);
57#else
58MODULE_PARM(eecs, "i");
59MODULE_PARM(eesk, "i");
60MODULE_PARM(eedi, "i");
61MODULE_PARM(eerc, "i");
62MODULE_PARM(force, "i");
63#endif
64
65/* Minimum timing constants */
66#define EECK_EDGE_TIME 3 /* 3us - max(adm 2.5us, 93c 1us) */
67#define EEDI_SETUP_TIME 1 /* 1us - max(adm 10ns, 93c 400ns) */
68#define EECS_SETUP_TIME 1 /* 1us - max(adm no, 93c 200ns) */
69
70/* Handy macros for writing fixed length values */
71#define adm_write8(cs, b) { __u8 val = (__u8) (b); adm_write(cs, &val, sizeof(val)*8); }
72#define adm_write16(cs, w) { __u16 val = hton16(w); adm_write(cs, (__u8 *)&val, sizeof(val)*8); }
73#define adm_write32(cs, i) { uint32 val = hton32(i); adm_write(cs, (__u8 *)&val, sizeof(val)*8); }
74
75#define atoi(str) simple_strtoul(((str != NULL) ? str : ""), NULL, 0)
76
77#ifdef BROADCOM
78extern char *nvram_get(char *name);
79
80/* Return gpio pin number assigned to the named pin */
81/*
82* Variable should be in format:
83*
84* gpio<N>=pin_name
85*
86* 'def_pin' is returned if there is no such variable found.
87*/
88static unsigned int get_gpiopin(char *pin_name, unsigned int def_pin)
89{
90    char name[] = "gpioXXXX";
91    char *val;
92    unsigned int pin;
93
94    /* Go thru all possibilities till a match in pin name */
95    for (pin = 0; pin < 16; pin ++) {
96        sprintf(name, "gpio%d", pin);
97        val = nvram_get(name);
98        if (val && !strcmp(val, pin_name))
99            return pin;
100    }
101    return def_pin;
102}
103#endif
104
105
106static void adm_write(int cs, char *buf, unsigned int bits)
107{
108    int i, len = (bits + 7) / 8;
109    __u8 mask;
110
111    gpio_out(eecs, (cs ? eecs : 0));
112    udelay(EECK_EDGE_TIME);
113
114    /* Byte assemble from MSB to LSB */
115    for (i = 0; i < len; i++) {
116        /* Bit bang from MSB to LSB */
117        for (mask = 0x80; mask && bits > 0; mask >>= 1, bits --) {
118            /* Clock low */
119            gpio_out(eesk, 0);
120            udelay(EECK_EDGE_TIME);
121
122            /* Output on rising edge */
123            gpio_out(eedi, ((mask & buf[i]) ? eedi : 0));
124            udelay(EEDI_SETUP_TIME);
125
126            /* Clock high */
127            gpio_out(eesk, eesk);
128            udelay(EECK_EDGE_TIME);
129        }
130    }
131
132    /* Clock low */
133    gpio_out(eesk, 0);
134    udelay(EECK_EDGE_TIME);
135
136    if (cs)
137        gpio_out(eecs, 0);
138}
139
140
141static void adm_read(int cs, char *buf, unsigned int bits)
142{
143    int i, len = (bits + 7) / 8;
144    __u8 mask;
145
146    gpio_out(eecs, (cs ? eecs : 0));
147    udelay(EECK_EDGE_TIME);
148
149    /* Byte assemble from MSB to LSB */
150    for (i = 0; i < len; i++) {
151        __u8 byte;
152
153        /* Bit bang from MSB to LSB */
154        for (mask = 0x80, byte = 0; mask && bits > 0; mask >>= 1, bits --) {
155            __u8 gp;
156
157            /* Clock low */
158            gpio_out(eesk, 0);
159            udelay(EECK_EDGE_TIME);
160
161            /* Input on rising edge */
162            gp = gpio_in();
163            if (gp & eedi)
164                byte |= mask;
165
166            /* Clock high */
167            gpio_out(eesk, eesk);
168            udelay(EECK_EDGE_TIME);
169        }
170
171        *buf++ = byte;
172    }
173
174    /* Clock low */
175    gpio_out(eesk, 0);
176    udelay(EECK_EDGE_TIME);
177
178    if (cs)
179        gpio_out(eecs, 0);
180}
181
182
183/* Enable outputs with specified value to the chip */
184static void adm_enout(__u8 pins, __u8 val)
185{
186    /* Prepare GPIO output value */
187    gpio_out(pins, val);
188    
189    /* Enable GPIO outputs */
190    gpio_outen(pins, pins);
191    udelay(EECK_EDGE_TIME);
192}
193
194
195/* Disable outputs to the chip */
196static void adm_disout(__u8 pins)
197{
198    /* Disable GPIO outputs */
199    gpio_outen(pins, 0);
200    udelay(EECK_EDGE_TIME);
201}
202
203
204/* Advance clock(s) */
205static void adm_adclk(int clocks)
206{
207    int i;
208    for (i = 0; i < clocks; i++) {
209        /* Clock high */
210        gpio_out(eesk, eesk);
211        udelay(EECK_EDGE_TIME);
212
213        /* Clock low */
214        gpio_out(eesk, 0);
215        udelay(EECK_EDGE_TIME);
216    }
217}
218
219static __u32 adm_rreg(__u8 table, __u8 addr)
220{
221    /* cmd: 01 10 T DD R RRRRRR */
222    __u8 bits[6] = {
223        0xFF, 0xFF, 0xFF, 0xFF,
224        (0x06 << 4) | ((table & 0x01) << 3 | (addr&64)>>6),
225        ((addr&63)<<2)
226    };
227
228    __u8 rbits[4];
229
230    /* Enable GPIO outputs with all pins to 0 */
231    adm_enout((__u8)(eecs | eesk | eedi), 0);
232
233    adm_write(0, bits, 46);
234    adm_disout((__u8)(eedi));
235    adm_adclk(2);
236    adm_read (0, rbits, 32);
237
238    /* Extra clock(s) required per datasheet */
239    adm_adclk(2);
240
241    /* Disable GPIO outputs */
242    adm_disout((__u8)(eecs | eesk));
243
244    if (!table) /* EEPROM has 16-bit registers, but pumps out two registers in one request */
245        return (addr & 0x01 ? (rbits[0]<<8) | rbits[1] : (rbits[2]<<8) | (rbits[3]));
246    else
247        return (rbits[0]<<24) | (rbits[1]<<16) | (rbits[2]<<8) | rbits[3];
248}
249
250
251
252/* Write chip configuration register */
253/* Follow 93c66 timing and chip's min EEPROM timing requirement */
254void
255adm_wreg(__u8 addr, __u16 val)
256{
257    /* cmd(27bits): sb(1) + opc(01) + addr(bbbbbbbb) + data(bbbbbbbbbbbbbbbb) */
258    __u8 bits[4] = {
259        (0x05 << 5) | (addr >> 3),
260        (addr << 5) | (__u8)(val >> 11),
261        (__u8)(val >> 3),
262        (__u8)(val << 5)
263    };
264
265    /* Enable GPIO outputs with all pins to 0 */
266    adm_enout((__u8)(eecs | eesk | eedi), 0);
267
268    /* Write cmd. Total 27 bits */
269    adm_write(1, bits, 27);
270
271    /* Extra clock(s) required per datasheet */
272    adm_adclk(2);
273
274    /* Disable GPIO outputs */
275    adm_disout((__u8)(eecs | eesk | eedi));
276}
277
278
279/* Port configuration registers */
280static int port_conf[] = { 0x01, 0x03, 0x05, 0x07, 0x08, 0x09 };
281
282/* Bits in VLAN port mapping */
283static int vlan_ports[] = { 1 << 0, 1 << 2, 1 << 4, 1 << 6, 1 << 7, 1 << 8 };
284
285static int handle_vlan_port_read(void *driver, char *buf, int nr)
286{
287    int ports, i, c, len = 0;
288            
289    if ((nr < 0) || (nr > 15))
290        return 0;
291
292    /* Get VLAN port map */
293    ports = adm_rreg(0, 0x13 + nr);
294    
295    for (i = 0; i <= 5; i++) {
296        if (ports & vlan_ports[i]) {
297            c = adm_rreg(0, port_conf[i]);
298            
299            len += sprintf(buf + len, "%d", i);
300            if (c & (1 << 4)) {
301                buf[len++] = 't';
302                if (((c & (0xf << 10)) >> 10) == nr)
303                    buf[len++] = '*';
304            } else if (i == 5)
305                buf[len++] = 'u';
306
307            buf[len++] = '\t';
308        }
309    }
310    len += sprintf(buf + len, "\n");
311
312    return len;
313}
314
315static int handle_vlan_port_write(void *driver, char *buf, int nr)
316{
317    int i, cfg, ports;
318    switch_driver *d = (switch_driver *) driver;
319    switch_vlan_config *c = switch_parse_vlan(d, buf);
320
321    if (c == NULL)
322        return -1;
323
324    ports = adm_rreg(0, 0x13 + nr);
325    for (i = 0; i < d->ports; i++) {
326        if (c->port & (1 << i)) {
327            ports |= vlan_ports[i];
328
329            cfg = adm_rreg(0, port_conf[i]);
330            
331            /* Tagging */
332            if (c->untag & (1 << i))
333                cfg &= ~(1 << 4);
334            else
335                cfg |= (1 << 4);
336            
337            if ((c->untag | c->pvid) & (1 << i)) {
338                cfg = (cfg & ~(0xf << 10)) | (nr << 10);
339            }
340            
341            adm_wreg(port_conf[i], (__u16) cfg);
342        } else {
343            ports &= ~(vlan_ports[i]);
344        }
345    }
346    adm_wreg(0x13 + nr, (__u16) ports);
347
348    return 0;
349}
350
351static int handle_port_enable_read(void *driver, char *buf, int nr)
352{
353    return sprintf(buf, "%d\n", ((adm_rreg(0, port_conf[nr]) & (1 << 5)) ? 0 : 1));
354}
355
356static int handle_port_enable_write(void *driver, char *buf, int nr)
357{
358    int reg = adm_rreg(0, port_conf[nr]);
359    
360    if (buf[0] == '0')
361        reg |= (1 << 5);
362    else if (buf[0] == '1')
363        reg &= ~(1 << 5);
364    else return -1;
365
366    adm_wreg(port_conf[nr], (__u16) reg);
367    return 0;
368}
369
370static int handle_port_media_read(void *driver, char *buf, int nr)
371{
372    int len;
373    int media = 0;
374    int reg = adm_rreg(0, port_conf[nr]);
375
376    if (reg & (1 << 1))
377        media |= SWITCH_MEDIA_AUTO;
378    if (reg & (1 << 2))
379        media |= SWITCH_MEDIA_100;
380    if (reg & (1 << 3))
381        media |= SWITCH_MEDIA_FD;
382
383    len = switch_print_media(buf, media);
384    return len + sprintf(buf + len, "\n");
385}
386
387static int handle_port_media_write(void *driver, char *buf, int nr)
388{
389    int media = switch_parse_media(buf);
390    int reg = adm_rreg(0, port_conf[nr]);
391
392    if (media < 0)
393        return -1;
394    
395    reg &= ~((1 << 1) | (1 << 2) | (1 << 3));
396    if (media & SWITCH_MEDIA_AUTO)
397        reg |= 1 << 1;
398    if (media & SWITCH_MEDIA_100)
399        reg |= 1 << 2;
400    if (media & SWITCH_MEDIA_FD)
401        reg |= 1 << 3;
402
403    adm_wreg(port_conf[nr], reg);
404    
405    return 0;
406}
407
408static int handle_vlan_enable_read(void *driver, char *buf, int nr)
409{
410    return sprintf(buf, "%d\n", ((adm_rreg(0, 0x11) & (1 << 5)) ? 1 : 0));
411}
412
413static int handle_vlan_enable_write(void *driver, char *buf, int nr)
414{
415    int reg = adm_rreg(0, 0x11);
416    
417    if (buf[0] == '1')
418        reg |= (1 << 5);
419    else if (buf[0] == '0')
420        reg &= ~(1 << 5);
421    else return -1;
422
423    adm_wreg(0x11, (__u16) reg);
424    return 0;
425}
426
427static int handle_reset(void *driver, char *buf, int nr)
428{
429    int i;
430    u32 cfg;
431
432    /*
433     * Reset sequence: RC high->low(100ms)->high(30ms)
434     *
435     * WAR: Certain boards don't have the correct power on
436     * reset logic therefore we must explicitly perform the
437     * sequence in software.
438     */
439    if (eerc) {
440        /* Keep RC high for at least 20ms */
441        adm_enout(eerc, eerc);
442        for (i = 0; i < 20; i ++)
443            udelay(1000);
444        /* Keep RC low for at least 100ms */
445        adm_enout(eerc, 0);
446        for (i = 0; i < 100; i++)
447            udelay(1000);
448        /* Set default configuration */
449        adm_enout((__u8)(eesk | eedi), eesk);
450        /* Keep RC high for at least 30ms */
451        adm_enout(eerc, eerc);
452        for (i = 0; i < 30; i++)
453            udelay(1000);
454        /* Leave RC high and disable GPIO outputs */
455        adm_disout((__u8)(eecs | eesk | eedi));
456    
457    }
458
459    /* set up initial configuration for cpu port */
460    cfg = (0x8000 | /* Auto MDIX */
461          (0xf << 10) | /* PVID */
462          (1 << 4) | /* Tagging */
463          0xf); /* full duplex, 100Mbps, auto neg, flow ctrl */
464    adm_wreg(port_conf[5], cfg);
465    
466    /* vlan mode select register (0x11): vlan on, mac clone */
467    adm_wreg(0x11, 0xff30);
468
469    return 0;
470}
471
472static int handle_registers(void *driver, char *buf, int nr)
473{
474    int i, len = 0;
475    
476    for (i = 0; i <= 0x33; i++) {
477        len += sprintf(buf + len, "0x%02x: 0x%04x\n", i, adm_rreg(0, i));
478    }
479
480    return len;
481}
482
483static int handle_counters(void *driver, char *buf, int nr)
484{
485    int i, len = 0;
486
487    for (i = 0; i <= 0x3c; i++) {
488        len += sprintf(buf + len, "0x%02x: 0x%08x\n", i, adm_rreg(1, i));
489    }
490
491    return len;
492}
493
494static int detect_adm(void)
495{
496    int ret = 0;
497
498#ifdef BROADCOM
499    int boardflags = atoi(nvram_get("boardflags"));
500        int boardnum = atoi(nvram_get("boardnum"));
501
502    if ((boardnum == 44) && (boardflags == 0x0388)) { /* Trendware TEW-411BRP+ */
503        ret = 1;
504
505        eecs = get_gpiopin("adm_eecs", 2);
506        eesk = get_gpiopin("adm_eesk", 3);
507        eedi = get_gpiopin("adm_eedi", 4);
508        eerc = get_gpiopin("adm_rc", 5);
509
510    } else if ((boardflags & 0x80) || force) {
511        ret = 1;
512
513        eecs = get_gpiopin("adm_eecs", 2);
514        eesk = get_gpiopin("adm_eesk", 3);
515        eedi = get_gpiopin("adm_eedi", 4);
516        eerc = get_gpiopin("adm_rc", 0);
517
518    } else if ((strcmp(nvram_get("boardtype") ?: "", "bcm94710dev") == 0) &&
519            (strncmp(nvram_get("boardnum") ?: "", "42", 2) == 0)) {
520        /* WRT54G v1.1 hack */
521        eecs = 2;
522        eesk = 3;
523        eedi = 5;
524
525        ret = 1;
526    }
527
528    if (eecs)
529        eecs = (1 << eecs);
530    if (eesk)
531        eesk = (1 << eesk);
532    if (eedi)
533        eedi = (1 << eedi);
534    if (eerc)
535        eerc = (1 << eerc);
536#else
537    ret = 1;
538#endif
539
540    return ret;
541}
542
543static int __init adm_init(void)
544{
545    switch_config cfg[] = {
546        {"registers", handle_registers, NULL},
547        {"counters", handle_counters, NULL},
548        {"reset", NULL, handle_reset},
549        {"enable_vlan", handle_vlan_enable_read, handle_vlan_enable_write},
550        {NULL, NULL, NULL}
551    };
552    switch_config port[] = {
553        {"enable", handle_port_enable_read, handle_port_enable_write},
554        {"media", handle_port_media_read, handle_port_media_write},
555        {NULL, NULL, NULL}
556    };
557    switch_config vlan[] = {
558        {"ports", handle_vlan_port_read, handle_vlan_port_write},
559        {NULL, NULL, NULL}
560    };
561    switch_driver driver = {
562        name: DRIVER_NAME,
563        version: DRIVER_VERSION,
564        interface: "eth0",
565        ports: 6,
566        cpuport: 5,
567        vlans: 16,
568        driver_handlers: cfg,
569        port_handlers: port,
570        vlan_handlers: vlan,
571    };
572
573    if (!detect_adm())
574        return -ENODEV;
575
576    return switch_register_driver(&driver);
577}
578
579static void __exit adm_exit(void)
580{
581    switch_unregister_driver(DRIVER_NAME);
582}
583
584
585module_init(adm_init);
586module_exit(adm_exit);
587

Archive Download this file



interactive