2 * diag.c - GPIO interface driver for Broadcom boards
4 * Copyright (C) 2006 Mike Baker <mbm@openwrt.org>,
5 * Felix Fietkau <nbd@openwrt.org>
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 #include <linux/module.h>
24 #include <linux/pci.h>
25 #include <linux/kmod.h>
26 #include <linux/proc_fs.h>
27 #include <linux/moduleparam.h>
28 #include <linux/tqueue.h>
29 #include <linux/timer.h>
30 #include <asm/uaccess.h>
38 #define MODULE_NAME "diag"
41 #define FLASH_TIME HZ/6
43 static unsigned int gpiomask
= 0;
44 module_param(gpiomask
, int, 0644);
58 struct prochandler_t
{
64 struct prochandler_t proc
;
73 struct prochandler_t proc
;
82 struct button_t buttons
[MAX_GPIO
];
83 struct led_t leds
[MAX_GPIO
];
110 BUFFALO_UNKNOWN_4710
,
130 static struct platform_t __init platforms
[] = {
133 .name
= "Linksys WAP54G V1",
135 { .name
= "reset", .gpio
= 1 << 0 },
138 { .name
= "diag", .gpio
= 1 << 3 },
139 { .name
= "wlan", .gpio
= 1 << 4 },
143 .name
= "Linksys WAP54G V3",
145 /* FIXME: verify this */
146 { .name
= "reset", .gpio
= 1 << 7 },
147 { .name
= "ses", .gpio
= 1 << 0 },
151 { .name
= "ses", .gpio
= 1 << 1 },
155 .name
= "Linksys WRT54G V1.x",
157 { .name
= "reset", .gpio
= 1 << 6 },
161 { .name
= "diag", .gpio
= 1 << 1 },
162 { .name
= "dmz", .gpio
= 1 << 7 },
163 { .name
= "wlan", .gpio
= 1 << 0 },
167 .name
= "Linksys WRT54G*",
169 { .name
= "reset", .gpio
= 1 << 6 },
170 { .name
= "ses", .gpio
= 1 << 4 },
173 { .name
= "power", .gpio
= 1 << 1, .polarity
= NORMAL
},
174 { .name
= "dmz", .gpio
= 1 << 7, .polarity
= REVERSE
},
175 { .name
= "ses_white", .gpio
= 1 << 2, .polarity
= REVERSE
},
176 { .name
= "ses_orange", .gpio
= 1 << 3, .polarity
= REVERSE
},
177 { .name
= "wlan", .gpio
= 1 << 0, .polarity
= REVERSE
},
181 .name
= "Linksys WRTSL54GS",
183 { .name
= "reset", .gpio
= 1 << 6 },
184 { .name
= "ses", .gpio
= 1 << 4 },
187 { .name
= "power", .gpio
= 1 << 1, .polarity
= NORMAL
},
188 { .name
= "dmz", .gpio
= 1 << 7, .polarity
= REVERSE
},
189 { .name
= "ses_white", .gpio
= 1 << 5, .polarity
= REVERSE
},
190 { .name
= "ses_orange", .gpio
= 1 << 7, .polarity
= REVERSE
},
194 .name
= "Linksys WRT54G3G",
196 { .name
= "reset", .gpio
= 1 << 6 },
197 { .name
= "3g", .gpio
= 1 << 4 },
200 { .name
= "power", .gpio
= 1 << 1, .polarity
= NORMAL
},
201 { .name
= "dmz", .gpio
= 1 << 7, .polarity
= REVERSE
},
202 { .name
= "3g_green", .gpio
= 1 << 2, .polarity
= NORMAL
},
203 { .name
= "3g_blue", .gpio
= 1 << 3, .polarity
= NORMAL
},
204 { .name
= "3g_blink", .gpio
= 1 << 5, .polarity
= NORMAL
},
209 .name
= "ASUS WL-HDD",
211 { .name
= "reset", .gpio
= 1 << 6 },
214 { .name
= "power", .gpio
= 1 << 0, .polarity
= REVERSE
},
218 .name
= "ASUS WL-500g",
220 { .name
= "reset", .gpio
= 1 << 6 },
223 { .name
= "power", .gpio
= 1 << 0, .polarity
= REVERSE
},
227 .name
= "ASUS WL-500g",
229 { .name
= "reset", .gpio
= 1 << 6 },
232 { .name
= "power", .gpio
= 1 << 0, .polarity
= REVERSE
},
236 .name
= "ASUS WL-500g Deluxe",
238 { .name
= "reset", .gpio
= 1 << 6 },
241 { .name
= "power", .gpio
= 1 << 0, .polarity
= REVERSE
},
245 .name
= "ASUS WL-500g Premium",
247 { .name
= "reset", .gpio
= 1 << 0 },
250 { .name
= "power", .gpio
= 1 << 1, .polarity
= REVERSE
},
251 { .name
= "ses", .gpio
= 1 << 4, .polarity
= REVERSE
},
255 .name
= "ASUS (unknown, BCM4702)",
257 { .name
= "reset", .gpio
= 1 << 6 },
260 { .name
= "power", .gpio
= 1 << 0, .polarity
= REVERSE
},
265 .name
= "Buffalo WHR-G54S",
267 { .name
= "reset", .gpio
= 1 << 4 },
268 { .name
= "ses", .gpio
= 1 << 0 },
271 { .name
= "diag", .gpio
= 1 << 1, .polarity
= REVERSE
},
272 { .name
= "internal", .gpio
= 1 << 3, .polarity
= REVERSE
},
273 { .name
= "ses", .gpio
= 1 << 6, .polarity
= REVERSE
},
277 .name
= "Buffalo WBR2-G54",
280 { .name
= "reset", .gpio
= 1 << 7 },
283 { .name
= "diag", .gpio
= 1 << 1, .polarity
= REVERSE
},
287 .name
= "Buffalo WHR-HP-G54",
289 { .name
= "reset", .gpio
= 1 << 4 },
290 { .name
= "bridge", .gpio
= 1 << 5 },
291 { .name
= "ses", .gpio
= 1 << 0 },
294 { .name
= "diag", .gpio
= 1 << 7, .polarity
= REVERSE
},
295 { .name
= "bridge", .gpio
= 1 << 1, .polarity
= REVERSE
},
296 { .name
= "ses", .gpio
= 1 << 6, .polarity
= REVERSE
},
300 .name
= "Buffalo WHR2-A54G54",
302 { .name
= "reset", .gpio
= 1 << 4 },
305 { .name
= "diag", .gpio
= 1 << 7, .polarity
= REVERSE
},
309 .name
= "Buffalo WLA2-G54L",
312 { .name
= "reset", .gpio
= 1 << 7 },
315 { .name
= "diag", .gpio
= 1 << 1, .polarity
= REVERSE
},
318 [BUFFALO_UNKNOWN
] = {
319 .name
= "Buffalo (unknown)",
321 { .name
= "reset", .gpio
= 1 << 7 },
324 { .name
= "diag", .gpio
= 1 << 1, .polarity
= REVERSE
},
327 [BUFFALO_UNKNOWN_4710
] = {
328 .name
= "Buffalo (unknown, BCM4710)",
330 { .name
= "reset", .gpio
= 1 << 4 },
333 { .name
= "diag", .gpio
= 1 << 1, .polarity
= REVERSE
},
338 .name
= "Siemens SE505 V1",
340 /* No usable buttons */
343 { .name
= "dmz", .gpio
= 1 << 4, .polarity
= REVERSE
},
344 { .name
= "wlan", .gpio
= 1 << 3, .polarity
= REVERSE
},
348 .name
= "Siemens SE505 V2",
350 /* No usable buttons */
353 { .name
= "power", .gpio
= 1 << 5, .polarity
= REVERSE
},
354 { .name
= "dmz", .gpio
= 1 << 0, .polarity
= REVERSE
},
355 { .name
= "wlan", .gpio
= 1 << 3, .polarity
= REVERSE
},
360 .name
= "U.S. Robotics USR5461",
362 /* No usable buttons */
365 { .name
= "wlan", .gpio
= 1 << 0, .polarity
= REVERSE
},
366 { .name
= "printer", .gpio
= 1 << 1, .polarity
= REVERSE
},
371 .name
= "Dell TrueMobile 2300",
373 { .name
= "reset", .gpio
= 1 << 0 },
376 { .name
= "diag", .gpio
= 1 << 7, .polarity
= REVERSE
},
381 .name
= "Motorola WR850G V1",
383 { .name
= "reset", .gpio
= 1 << 0 },
386 { .name
= "power", .gpio
= 1 << 4, .polarity
= NORMAL
},
387 { .name
= "diag", .gpio
= 1 << 3, .polarity
= REVERSE
},
388 { .name
= "modem", .gpio
= 1 << 6, .polarity
= NORMAL
},
389 { .name
= "wlan_red", .gpio
= 1 << 5, .polarity
= REVERSE
},
390 { .name
= "wlan_green", .gpio
= 1 << 7, .polarity
= REVERSE
},
394 .name
= "Motorola WR850G V2",
396 { .name
= "reset", .gpio
= 1 << 5 },
399 { .name
= "diag", .gpio
= 1 << 1, .polarity
= REVERSE
},
400 { .name
= "wlan", .gpio
= 1 << 0, .polarity
= NORMAL
},
401 { .name
= "modem_green",.gpio
= 1 << 6, .polarity
= REVERSE
},
402 { .name
= "modem_red", .gpio
= 1 << 7, .polarity
= REVERSE
},
407 .name
= "Belkin (unknown)",
408 /* FIXME: verify & add detection */
410 { .name
= "reset", .gpio
= 1 << 7 },
413 { .name
= "power", .gpio
= 1 << 5, .polarity
= NORMAL
},
414 { .name
= "wlan", .gpio
= 1 << 3, .polarity
= NORMAL
},
415 { .name
= "connected", .gpio
= 1 << 0, .polarity
= NORMAL
},
420 static struct proc_dir_entry
*diag
, *leds
;
422 extern void *bcm947xx_sbh
;
423 #define sbh bcm947xx_sbh
425 static int sb_irq(void *sbh
);
426 static struct platform_t platform
;
428 extern char *nvram_get(char *str
);
430 static void led_flash(unsigned long dummy
);
432 static inline char __init
*getvar(char *str
)
434 return nvram_get(str
)?:"";
437 static struct platform_t __init
*platform_detect(void)
439 char *boardnum
, *boardtype
, *buf
;
441 boardnum
= getvar("boardnum");
442 boardtype
= getvar("boardtype");
443 if (strncmp(getvar("pmon_ver"), "CFE", 3) == 0) {
444 /* CFE based - newer hardware */
445 if (!strcmp(boardnum
, "42")) { /* Linksys */
446 if (!strcmp(boardtype
, "0x0101"))
447 return &platforms
[WRT54G3G
];
449 if (!strcmp(getvar("et1phyaddr"),"5") && !strcmp(getvar("et1mdcport"), "1"))
450 return &platforms
[WRTSL54GS
];
452 /* default to WRT54G */
453 return &platforms
[WRT54G
];
456 if (!strcmp(boardnum
, "45")) { /* ASUS */
457 if (!strcmp(boardtype
,"0x042f"))
458 return &platforms
[WL500GP
];
460 return &platforms
[WL500GD
];
463 if (!strcmp(boardnum
, "10496"))
464 return &platforms
[USR5461
];
465 } else { /* PMON based - old stuff */
466 if (!strncmp(boardtype
, "bcm94710dev", 11)) {
467 if (!strcmp(boardtype
, "42"))
468 return &platforms
[WRT54GV1
];
469 if (simple_strtoul(boardnum
, NULL
, 9) == 2)
470 return &platforms
[WAP54GV1
];
472 if (!strncmp(getvar("hardware_version"), "WL500-", 6))
473 return &platforms
[WL500G
];
474 if (!strncmp(getvar("hardware_version"), "WL300-", 6)) {
475 /* Either WL-300g or WL-HDD, do more extensive checks */
476 if ((simple_strtoul(getvar("et0phyaddr"), NULL
, 0) == 0) &&
477 (simple_strtoul(getvar("et1phyaddr"), NULL
, 9) == 1))
478 return &platforms
[WLHDD
];
479 if ((simple_strtoul(getvar("et0phyaddr"), NULL
, 0) == 0) &&
480 (simple_strtoul(getvar("et1phyaddr"), NULL
, 9) == 10))
481 return &platforms
[WL300G
];
484 /* unknown asus stuff, probably bcm4702 */
485 if (!strncmp(boardnum
, "asusX", 5))
486 return &platforms
[ASUS_4702
];
488 if ((simple_strtoul(getvar("GemtekPmonVer"), NULL
, 0) == 9) &&
489 (simple_strtoul(getvar("et0phyaddr"), NULL
, 0) == 30))
490 return &platforms
[WR850GV1
];
493 if ((buf
= (nvram_get("melco_id") ?: nvram_get("buffalo_id")))) {
494 /* Buffalo hardware, check id for specific hardware matches */
495 if (!strcmp(buf
, "29bb0332"))
496 return &platforms
[WBR2_G54
];
497 if (!strcmp(buf
, "29129"))
498 return &platforms
[WLA2_G54L
];
499 if (!strcmp(buf
, "30189"))
500 return &platforms
[WHR_HP_G54
];
501 if (!strcmp(buf
, "30182"))
502 return &platforms
[WHR_G54S
];
503 if (!strcmp(buf
, "290441dd"))
504 return &platforms
[WHR2_A54G54
];
507 if (buf
|| !strcmp(boardnum
, "00")) {/* probably buffalo */
508 if (!strncmp(boardtype
, "bcm94710ap", 10))
509 return &platforms
[BUFFALO_UNKNOWN_4710
];
511 return &platforms
[BUFFALO_UNKNOWN
];
514 if (!strcmp(getvar("CFEver"), "MotoWRv203"))
515 return &platforms
[WR850GV2
];
521 static ssize_t
diag_proc_read(struct file
*file
, char *buf
, size_t count
, loff_t
*ppos
);
522 static ssize_t
diag_proc_write(struct file
*file
, const char *buf
, size_t count
, void *data
);
523 static struct file_operations diag_proc_fops
= {
524 read
: diag_proc_read
,
525 write
: diag_proc_write
530 static ssize_t
diag_proc_read(struct file
*file
, char *buf
, size_t count
, loff_t
*ppos
)
533 struct inode
*inode
= file
->f_dentry
->d_inode
;
534 struct proc_dir_entry
*dent
= inode
->u
.generic_ip
;
536 struct proc_dir_entry
*dent
= PDE(file
->f_dentry
->d_inode
);
541 if ((page
= kmalloc(1024, GFP_KERNEL
)) == NULL
)
544 if (dent
->data
!= NULL
) {
545 struct prochandler_t
*handler
= (struct prochandler_t
*) dent
->data
;
546 switch (handler
->type
) {
548 struct led_t
* led
= (struct led_t
*) handler
->ptr
;
550 len
= sprintf(page
, "f\n");
552 int in
= (sb_gpioin(sbh
) & led
->gpio
? 1 : 0);
553 int p
= (led
->polarity
== NORMAL
? 0 : 1);
554 len
= sprintf(page
, "%d\n", ((in
^ p
) ? 1 : 0));
559 len
= sprintf(page
, "%s\n", platform
.name
);
562 len
= sprintf(page
, "%d\n", gpiomask
);
569 len
= min_t(int, len
- *ppos
, count
);
570 if (copy_to_user(buf
, (page
+ *ppos
), len
)) {
583 static ssize_t
diag_proc_write(struct file
*file
, const char *buf
, size_t count
, void *data
)
586 struct inode
*inode
= file
->f_dentry
->d_inode
;
587 struct proc_dir_entry
*dent
= inode
->u
.generic_ip
;
589 struct proc_dir_entry
*dent
= PDE(file
->f_dentry
->d_inode
);
594 if ((page
= kmalloc(count
+ 1, GFP_KERNEL
)) == NULL
)
597 if (copy_from_user(page
, buf
, count
)) {
603 if (dent
->data
!= NULL
) {
604 struct prochandler_t
*handler
= (struct prochandler_t
*) dent
->data
;
605 switch (handler
->type
) {
607 struct led_t
*led
= (struct led_t
*) handler
->ptr
;
608 int p
= (led
->polarity
== NORMAL
? 0 : 1);
610 if (led
->gpio
& gpiomask
)
613 if (page
[0] == 'f') {
618 sb_gpioouten(sbh
, led
->gpio
, led
->gpio
);
619 sb_gpiocontrol(sbh
, led
->gpio
, 0);
620 sb_gpioout(sbh
, led
->gpio
, ((p
^ (page
[0] == '1')) ? led
->gpio
: 0));
625 gpiomask
= simple_strtoul(page
, NULL
, 16);
642 static void hotplug_button(struct event_t
*event
)
644 call_usermodehelper (event
->argv
[0], event
->argv
, event
->envp
);
648 static void button_handler(int irq
, void *dev_id
, struct pt_regs
*regs
)
651 int in
= sb_gpioin(sbh
);
652 struct event_t
*event
;
654 for (b
= platform
.buttons
; b
->name
; b
++) {
655 if (b
->gpio
& gpiomask
)
658 if (b
->polarity
!= (in
& b
->gpio
)) {
662 if ((event
= (struct event_t
*)kmalloc (256, GFP_KERNEL
))) {
664 char *scratch
= event
->buf
;
667 event
->argv
[i
++] = hotplug_path
;
668 event
->argv
[i
++] = "button";
672 event
->envp
[i
++] = "HOME=/";
673 event
->envp
[i
++] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin";
674 event
->envp
[i
++] = scratch
;
675 scratch
+= sprintf (scratch
, "ACTION=%s", b
->pressed
?"pressed":"released") + 1;
676 event
->envp
[i
++] = scratch
;
677 scratch
+= sprintf (scratch
, "BUTTON=%s", b
->name
) + 1;
678 event
->envp
[i
++] = scratch
;
679 scratch
+= sprintf (scratch
, "SEEN=%ld", (jiffies
- b
->seen
)/HZ
) + 1;
682 INIT_TQUEUE(&event
->tq
, (void *)(void *)hotplug_button
, (void *)event
);
683 schedule_task(&event
->tq
);
687 b
->polarity
^= b
->gpio
;
688 sb_gpiointpolarity(sbh
, b
->gpio
, b
->polarity
);
693 static struct timer_list led_timer
= {
697 static void led_flash(unsigned long dummy
) {
701 for (l
= platform
.leds
; l
->name
; l
++) {
711 val
= ~sb_gpioin(sbh
);
714 sb_gpioouten(sbh
, mask
, mask
);
715 sb_gpiocontrol(sbh
, mask
, 0);
716 sb_gpioout(sbh
, mask
, val
);
718 mod_timer(&led_timer
, jiffies
+ FLASH_TIME
);
722 static void __init
register_buttons(struct button_t
*b
)
724 int irq
= sb_irq(sbh
) + 2;
727 request_irq(irq
, button_handler
, SA_SHIRQ
| SA_SAMPLE_RANDOM
, "gpio", button_handler
);
729 for (; b
->name
; b
++) {
730 if (b
->gpio
& gpiomask
)
733 sb_gpioouten(sbh
, b
->gpio
,0);
734 sb_gpiocontrol(sbh
, b
->gpio
,0);
735 b
->polarity
= sb_gpioin(sbh
) & b
->gpio
;
736 sb_gpiointpolarity(sbh
, b
->gpio
, b
->polarity
);
737 sb_gpiointmask(sbh
, b
->gpio
, b
->gpio
);
740 if ((cc
= sb_setcore(sbh
, SB_CC
, 0))) {
743 intmask
= readl(&cc
->intmask
);
745 writel(intmask
, &cc
->intmask
);
749 static void __exit
unregister_buttons(struct button_t
*b
)
751 int irq
= sb_irq(sbh
) + 2;
754 sb_gpiointmask(sbh
, b
->gpio
, 0);
756 free_irq(irq
, button_handler
);
759 static void __init
register_leds(struct led_t
*l
)
761 struct proc_dir_entry
*p
;
763 leds
= proc_mkdir("led", diag
);
767 for(; l
->name
; l
++) {
768 if (l
->gpio
& gpiomask
)
771 sb_gpioouten(sbh
, l
->gpio
, l
->gpio
);
772 sb_gpiocontrol(sbh
, l
->gpio
, 0);
773 sb_gpioout(sbh
, l
->gpio
, (l
->polarity
== NORMAL
)?0:l
->gpio
);
775 if ((p
= create_proc_entry(l
->name
, S_IRUSR
, leds
))) {
776 l
->proc
.type
= PROC_LED
;
778 p
->data
= (void *) &l
->proc
;
779 p
->proc_fops
= &diag_proc_fops
;
784 static void __exit
unregister_leds(struct led_t
*l
)
787 remove_proc_entry(l
->name
, leds
);
789 remove_proc_entry("led", diag
);
792 static void __exit
diag_exit(void)
795 del_timer(&led_timer
);
797 if (platform
.buttons
)
798 unregister_buttons(platform
.buttons
);
801 unregister_leds(platform
.leds
);
803 remove_proc_entry("model", diag
);
804 remove_proc_entry("gpiomask", diag
);
805 remove_proc_entry("diag", NULL
);
808 static struct prochandler_t proc_model
= { .type
= PROC_MODEL
};
809 static struct prochandler_t proc_gpiomask
= { .type
= PROC_GPIOMASK
};
811 static int __init
diag_init(void)
813 static struct proc_dir_entry
*p
;
814 static struct platform_t
*detected
;
816 detected
= platform_detect();
818 printk(MODULE_NAME
": Router model not detected.\n");
821 memcpy(&platform
, detected
, sizeof(struct platform_t
));
823 printk(MODULE_NAME
": Detected '%s'\n", platform
.name
);
825 if (!(diag
= proc_mkdir("diag", NULL
))) {
826 printk(MODULE_NAME
": proc_mkdir on /proc/diag failed\n");
830 if ((p
= create_proc_entry("model", S_IRUSR
, diag
))) {
831 p
->data
= (void *) &proc_model
;
832 p
->proc_fops
= &diag_proc_fops
;
835 if ((p
= create_proc_entry("gpiomask", S_IRUSR
| S_IWUSR
, diag
))) {
836 p
->data
= (void *) &proc_gpiomask
;
837 p
->proc_fops
= &diag_proc_fops
;
840 if (platform
.buttons
)
841 register_buttons(platform
.buttons
);
844 register_leds(platform
.leds
);
851 module_init(diag_init
);
852 module_exit(diag_exit
);
854 MODULE_AUTHOR("Mike Baker, Felix Fietkau / OpenWrt.org");
855 MODULE_LICENSE("GPL");
857 /* TODO: export existing sb_irq instead */
858 static int sb_irq(void *sbh
)
863 uint32 flag
, sbipsflag
;
866 regs
= sb_coreregs(sbh
);
867 sb
= (sbconfig_t
*)((ulong
) regs
+ SBCONFIGOFF
);
868 flag
= (R_REG(&sb
->sbtpsflag
) & SBTPS_NUM0_MASK
);
870 idx
= sb_coreidx(sbh
);
872 if ((regs
= sb_setcore(sbh
, SB_MIPS
, 0)) ||
873 (regs
= sb_setcore(sbh
, SB_MIPS33
, 0))) {
874 sb
= (sbconfig_t
*)((ulong
) regs
+ SBCONFIGOFF
);
876 /* sbipsflag specifies which core is routed to interrupts 1 to 4 */
877 sbipsflag
= R_REG(&sb
->sbipsflag
);
878 for (irq
= 1; irq
<= 4; irq
++, sbipsflag
>>= 8) {
879 if ((sbipsflag
& 0x3f) == flag
)
886 sb_setcoreidx(sbh
, idx
);