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 <asm/uaccess.h>
36 #define MODULE_NAME "diag"
44 static unsigned int gpiomask
= 0;
45 module_param(gpiomask
, int, 0644);
60 struct prochandler_t
{
67 struct prochandler_t proc
;
76 struct prochandler_t proc
;
84 struct button_t buttons
[MAX_GPIO
];
85 struct led_t leds
[MAX_GPIO
];
111 BUFFALO_UNKNOWN_4710
,
128 static struct platform_t platforms
[] = {
131 .name
= "Linksys WAP54G V1",
133 { .name
= "reset", .gpio
= 1 << 0 },
136 { .name
= "diag", .gpio
= 1 << 3 },
137 { .name
= "wlan", .gpio
= 1 << 4 },
141 .name
= "Linksys WAP54G V3",
143 /* FIXME: verify this */
144 { .name
= "reset", .gpio
= 1 << 7 },
145 { .name
= "ses", .gpio
= 1 << 0 },
149 { .name
= "ses", .gpio
= 1 << 1 },
153 .name
= "Linksys WRT54G V1.x",
155 { .name
= "reset", .gpio
= 1 << 6 },
162 .name
= "Linksys WRT54G(S)",
164 { .name
= "reset", .gpio
= 1 << 6 },
165 { .name
= "ses", .gpio
= 1 << 4 }
168 { .name
= "power", .gpio
= 1 << 1, .polarity
= NORMAL
},
169 { .name
= "dmz", .gpio
= 1 << 7 },
170 { .name
= "ses_white", .gpio
= 1 << 2 },
171 { .name
= "ses_orange", .gpio
= 1 << 3 },
175 .name
= "Linksys WRTSL54GS",
177 { .name
= "reset", .gpio
= 1 << 6 },
178 { .name
= "ses", .gpio
= 1 << 4 }
181 { .name
= "power", .gpio
= 1 << 1, .polarity
= NORMAL
},
182 { .name
= "dmz", .gpio
= 1 << 7 },
183 { .name
= "ses_white", .gpio
= 1 << 5 },
184 { .name
= "ses_orange", .gpio
= 1 << 7 },
188 .name
= "Linksys WRT54G3G",
190 { .name
= "reset", .gpio
= 1 << 6 },
191 { .name
= "3g", .gpio
= 1 << 4 }
194 { .name
= "power", .gpio
= 1 << 1, .polarity
= NORMAL
},
195 { .name
= "dmz", .gpio
= 1 << 7 },
196 { .name
= "3g_green", .gpio
= 1 << 2, .polarity
= NORMAL
},
197 { .name
= "3g_blue", .gpio
= 1 << 3, .polarity
= NORMAL
},
198 { .name
= "3g_blink", .gpio
= 1 << 5, .polarity
= NORMAL
},
203 .name
= "ASUS WL-HDD",
205 { .name
= "reset", .gpio
= 1 << 6 },
208 { .name
= "power", .gpio
= 1 << 0 },
212 .name
= "ASUS WL-500g",
214 { .name
= "reset", .gpio
= 1 << 6 },
217 { .name
= "power", .gpio
= 1 << 0 },
221 .name
= "ASUS WL-500g",
223 { .name
= "reset", .gpio
= 1 << 6 },
226 { .name
= "power", .gpio
= 1 << 0 },
230 .name
= "ASUS WL-500g Deluxe",
232 { .name
= "reset", .gpio
= 1 << 6 },
235 { .name
= "power", .gpio
= 1 << 0 },
239 .name
= "ASUS WL-500g Premium",
241 { .name
= "reset", .gpio
= 1 << 0 },
244 { .name
= "power", .gpio
= 1 << 1, .polarity
= NORMAL
},
245 { .name
= "ses", .gpio
= 1 << 4 },
249 .name
= "ASUS (unknown, BCM4702)",
251 { .name
= "reset", .gpio
= 1 << 6 },
254 { .name
= "power", .gpio
= 1 << 0 },
259 .name
= "Buffalo WHR-G54S",
261 { .name
= "reset", .gpio
= 1 << 4 },
262 { .name
= "ses", .gpio
= 1 << 0 },
265 { .name
= "diag", .gpio
= 1 << 1 },
266 { .name
= "internal", .gpio
= 1 << 3 },
267 { .name
= "ses", .gpio
= 1 << 6 },
271 .name
= "Buffalo WBR2-G54",
274 { .name
= "reset", .gpio
= 1 << 7 },
277 { .name
= "diag", .gpio
= 1 << 1 },
281 .name
= "Buffalo WHR-HP-G54",
284 { .name
= "reset", .gpio
= 1 << 4 },
285 { .name
= "ses", .gpio
= 1 << 0 },
288 { .name
= "diag", .gpio
= 1 << 1 },
289 { .name
= "internal", .gpio
= 1 << 3 },
290 { .name
= "ses", .gpio
= 1 << 6 },
294 .name
= "Buffalo WLA2-G54L",
297 { .name
= "reset", .gpio
= 1 << 7 },
300 { .name
= "diag", .gpio
= 1 << 1 },
303 [BUFFALO_UNKNOWN
] = {
304 .name
= "Buffalo (unknown)",
306 { .name
= "reset", .gpio
= 1 << 7 },
309 { .name
= "diag", .gpio
= 1 << 1 },
312 [BUFFALO_UNKNOWN_4710
] = {
313 .name
= "Buffalo (unknown, BCM4710)",
315 { .name
= "reset", .gpio
= 1 << 4 },
318 { .name
= "diag", .gpio
= 1 << 1 },
323 .name
= "Siemens SE505 V1",
325 /* No usable buttons */
328 { .name
= "dmz", .gpio
= 1 << 4 },
329 { .name
= "wlan", .gpio
= 1 << 3 },
333 .name
= "Siemens SE505 V2",
335 /* No usable buttons */
338 { .name
= "power", .gpio
= 1 << 5 },
339 { .name
= "dmz", .gpio
= 1 << 0 },
340 { .name
= "wlan", .gpio
= 1 << 3 },
345 .name
= "U.S. Robotics USR5461",
347 /* No usable buttons */
350 { .name
= "wlan", .gpio
= 1 << 0 },
351 { .name
= "printer", .gpio
= 1 << 1 },
356 .name
= "Dell TrueMobile 2300",
358 { .name
= "reset", .gpio
= 1 << 0 },
361 { .name
= "diag", .gpio
= 1 << 7 },
366 .name
= "Motorola WR850G V1",
368 { .name
= "reset", .gpio
= 1 << 0 },
371 { .name
= "diag", .gpio
= 1 << 3 },
372 { .name
= "wlan_red", .gpio
= 1 << 5, .polarity
= NORMAL
},
373 { .name
= "wlan_green", .gpio
= 1 << 7, .polarity
= NORMAL
},
377 .name
= "Motorola WR850G V2",
379 { .name
= "reset", .gpio
= 1 << 5 },
382 { .name
= "diag", .gpio
= 1 << 1 },
383 { .name
= "wlan", .gpio
= 1 << 0 },
384 { .name
= "modem", .gpio
= 1 << 7, .polarity
= NORMAL
},
389 static struct proc_dir_entry
*diag
, *leds
;
391 extern void *bcm947xx_sbh
;
392 #define sbh bcm947xx_sbh
394 static int sb_irq(void *sbh
);
395 static struct platform_t
*platform
;
397 #include <linux/tqueue.h>
398 static struct tq_struct tq
;
399 extern char *nvram_get(char *str
);
401 static inline char *getvar(char *str
)
403 return nvram_get(str
)?:"";
406 static struct platform_t
*platform_detect(void)
408 char *boardnum
, *boardtype
, *buf
;
410 boardnum
= getvar("boardnum");
411 boardtype
= getvar("boardtype");
412 if (strncmp(getvar("pmon_ver"), "CFE", 3) == 0) {
413 /* CFE based - newer hardware */
414 if (!strcmp(boardnum
, "42")) { /* Linksys */
415 if (!strcmp(boardtype
, "0x0101"))
416 return &platforms
[WRT54G3G
];
418 if (!strcmp(getvar("et1phyaddr"),"5") && !strcmp(getvar("et1mdcport"), "1"))
419 return &platforms
[WRTSL54GS
];
421 /* default to WRT54G */
422 return &platforms
[WRT54G
];
425 if (!strcmp(boardnum
, "45")) { /* ASUS */
426 if (!strcmp(boardtype
,"0x042f"))
427 return &platforms
[WL500GP
];
429 return &platforms
[WL500GD
];
432 if (!strcmp(boardnum
, "10496"))
433 return &platforms
[USR5461
];
434 } else { /* PMON based - old stuff */
435 if (!strncmp(boardtype
, "bcm94710dev", 11)) {
436 if (!strcmp(boardtype
, "42"))
437 return &platforms
[WRT54GV1
];
438 if (simple_strtoul(boardnum
, NULL
, 9) == 2)
439 return &platforms
[WAP54GV1
];
441 if (!strncmp(getvar("hardware_version"), "WL500-", 6))
442 return &platforms
[WL500G
];
443 if (!strncmp(getvar("hardware_version"), "WL300-", 6)) {
444 /* Either WL-300g or WL-HDD, do more extensive checks */
445 if ((simple_strtoul(getvar("et0phyaddr"), NULL
, 0) == 0) &&
446 (simple_strtoul(getvar("et1phyaddr"), NULL
, 9) == 1))
447 return &platforms
[WLHDD
];
448 if ((simple_strtoul(getvar("et0phyaddr"), NULL
, 0) == 0) &&
449 (simple_strtoul(getvar("et1phyaddr"), NULL
, 9) == 10))
450 return &platforms
[WL300G
];
453 /* unknown asus stuff, probably bcm4702 */
454 if (!strncmp(boardnum
, "asusX", 5))
455 return &platforms
[ASUS_4702
];
457 if ((simple_strtoul(getvar("GemtekPmonVer"), NULL
, 0) == 9) &&
458 (simple_strtoul(getvar("et0phyaddr"), NULL
, 0) == 30))
459 return &platforms
[WR850GV1
];
462 if ((buf
= (nvram_get("melco_id") ?: nvram_get("buffalo_id")))) {
463 /* Buffalo hardware, check id for specific hardware matches */
464 if (!strcmp(buf
, "29bb0332"))
465 return &platforms
[WBR2_G54
];
466 if (!strcmp(buf
, "29129"))
467 return &platforms
[WLA2_G54L
];
468 if (!strcmp(buf
, "30189"))
469 return &platforms
[WHR_HP_G54
];
470 if (!strcmp(buf
, "30182"))
471 return &platforms
[WHR_G54S
];
474 if (buf
|| !strcmp(boardnum
, "00")) {/* probably buffalo */
475 if (!strncmp(boardtype
, "bcm94710ap", 10))
476 return &platforms
[BUFFALO_UNKNOWN_4710
];
478 return &platforms
[BUFFALO_UNKNOWN
];
481 if (!strcmp(getvar("CFEver"), "MotoWRv203"))
482 return &platforms
[WR850GV2
];
488 static ssize_t
diag_proc_read(struct file
*file
, char *buf
, size_t count
, loff_t
*ppos
);
489 static ssize_t
diag_proc_write(struct file
*file
, const char *buf
, size_t count
, void *data
);
490 static struct file_operations diag_proc_fops
= {
491 read
: diag_proc_read
,
492 write
: diag_proc_write
497 static ssize_t
diag_proc_read(struct file
*file
, char *buf
, size_t count
, loff_t
*ppos
)
500 struct inode
*inode
= file
->f_dentry
->d_inode
;
501 struct proc_dir_entry
*dent
= inode
->u
.generic_ip
;
503 struct proc_dir_entry
*dent
= PDE(file
->f_dentry
->d_inode
);
508 if ((page
= kmalloc(1024, GFP_KERNEL
)) == NULL
)
511 if (dent
->data
!= NULL
) {
512 struct prochandler_t
*handler
= (struct prochandler_t
*) dent
->data
;
513 switch (handler
->type
) {
516 struct button_t
* button
= (struct button_t
*) handler
->ptr
;
517 len
= sprintf(page
, "%d\n", button
->pressed
);
521 struct led_t
* led
= (struct led_t
*) handler
->ptr
;
522 int in
= (sb_gpioin(sbh
) & led
->gpio
? 1 : 0);
523 int p
= (led
->polarity
== NORMAL
? 0 : 1);
524 len
= sprintf(page
, "%d\n", ((in
^ p
) ? 1 : 0));
528 len
= sprintf(page
, "%s\n", platform
->name
);
531 len
= sprintf(page
, "%d\n", gpiomask
);
538 len
= min_t(int, len
- *ppos
, count
);
539 if (copy_to_user(buf
, (page
+ *ppos
), len
)) {
552 static ssize_t
diag_proc_write(struct file
*file
, const char *buf
, size_t count
, void *data
)
555 struct inode
*inode
= file
->f_dentry
->d_inode
;
556 struct proc_dir_entry
*dent
= inode
->u
.generic_ip
;
558 struct proc_dir_entry
*dent
= PDE(file
->f_dentry
->d_inode
);
563 if ((page
= kmalloc(count
+ 1, GFP_KERNEL
)) == NULL
)
566 if (copy_from_user(page
, buf
, count
)) {
572 if (dent
->data
!= NULL
) {
573 struct prochandler_t
*handler
= (struct prochandler_t
*) dent
->data
;
574 switch (handler
->type
) {
576 struct led_t
*led
= (struct led_t
*) handler
->ptr
;
577 int p
= (led
->polarity
== NORMAL
? 0 : 1);
579 if (led
->gpio
& gpiomask
)
581 sb_gpiocontrol(sbh
, led
->gpio
, 0);
582 sb_gpioouten(sbh
, led
->gpio
, led
->gpio
);
583 sb_gpioout(sbh
, led
->gpio
, ((p
^ (page
[0] == '1')) ? led
->gpio
: 0));
587 gpiomask
= simple_strtoul(page
, NULL
, 16);
597 static void hotplug_button(struct button_t
*b
)
599 char *argv
[3], *envp
[6], *buf
, *scratch
;
602 if (!hotplug_path
[0])
605 if (in_interrupt()) {
606 printk(MODULE_NAME
": HOTPLUG WHILE IN IRQ!\n");
610 if (!(buf
= kmalloc (256, GFP_KERNEL
)))
616 argv
[i
++] = hotplug_path
;
617 argv
[i
++] = "button";
621 envp
[i
++] = "HOME=/";
622 envp
[i
++] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin";
624 scratch
+= sprintf (scratch
, "ACTION=%s", b
->pressed
?"pressed":"released") + 1;
626 scratch
+= sprintf (scratch
, "BUTTON=%s", b
->name
) + 1;
628 scratch
+= sprintf (scratch
, "SEEN=%ld", (jiffies
- b
->seen
)/HZ
) + 1;
631 call_usermodehelper (argv
[0], argv
, envp
);
635 static void button_handler(int irq
, void *dev_id
, struct pt_regs
*regs
)
639 int in
= sb_gpioin(sbh
);
641 for (b
= platform
->buttons
; b
->name
; b
++) {
642 if (b
->gpio
& gpiomask
)
644 if (b
->polarity
!= (in
& b
->gpio
)) {
646 b
->polarity
^= b
->gpio
;
647 sb_gpiointpolarity(sbh
, b
->gpio
, b
->polarity
);
651 printk(MODULE_NAME
": button: %s pressed: %d seen: %ld\n",b
->name
, b
->pressed
, (jiffies
- b
->seen
)/HZ
);
653 INIT_TQUEUE(&tq
, (void *)(void *)hotplug_button
, (void *)b
);
661 static void register_buttons(struct button_t
*b
)
663 int irq
= sb_irq(sbh
) + 2;
667 struct proc_dir_entry
*p
;
669 buttons
= proc_mkdir("button", diag
);
674 request_irq(irq
, button_handler
, SA_SHIRQ
| SA_SAMPLE_RANDOM
, "gpio", button_handler
);
676 for (; b
->name
; b
++) {
677 if (b
->gpio
& gpiomask
)
679 sb_gpioouten(sbh
,b
->gpio
,0);
680 sb_gpiocontrol(sbh
,b
->gpio
,0);
681 b
->polarity
= sb_gpioin(sbh
) & b
->gpio
;
682 sb_gpiointpolarity(sbh
, b
->gpio
, b
->polarity
);
683 sb_gpiointmask(sbh
, b
->gpio
,b
->gpio
);
685 if ((p
= create_proc_entry(b
->name
, S_IRUSR
, buttons
))) {
686 b
->proc
.type
= PROC_BUTTON
;
687 b
->proc
.ptr
= (void *) b
;
688 p
->data
= (void *) &b
->proc
;
689 p
->proc_fops
= &diag_proc_fops
;
694 if ((cc
= sb_setcore(sbh
, SB_CC
, 0))) {
697 intmask
= readl(&cc
->intmask
);
699 writel(intmask
, &cc
->intmask
);
703 static void unregister_buttons(struct button_t
*b
)
705 int irq
= sb_irq(sbh
) + 2;
707 for (; b
->name
; b
++) {
708 sb_gpiointmask(sbh
, b
->gpio
, 0);
710 remove_proc_entry(b
->name
, buttons
);
714 remove_proc_entry("buttons", diag
);
717 free_irq(irq
, button_handler
);
720 static void register_leds(struct led_t
*l
)
722 struct proc_dir_entry
*p
;
724 leds
= proc_mkdir("led", diag
);
728 for(; l
->name
; l
++) {
729 sb_gpiointmask(sbh
, l
->gpio
, 0);
730 if ((p
= create_proc_entry(l
->name
, S_IRUSR
, leds
))) {
731 l
->proc
.type
= PROC_LED
;
733 p
->data
= (void *) &l
->proc
;
734 p
->proc_fops
= &diag_proc_fops
;
739 static void unregister_leds(struct led_t
*l
)
741 for(; l
->name
; l
++) {
742 remove_proc_entry(l
->name
, leds
);
744 remove_proc_entry("led", diag
);
747 static void __exit
diag_exit(void)
749 if (platform
->buttons
)
750 unregister_buttons(platform
->buttons
);
752 unregister_leds(platform
->leds
);
753 remove_proc_entry("model", diag
);
754 remove_proc_entry("gpiomask", diag
);
755 remove_proc_entry("diag", NULL
);
758 static struct prochandler_t proc_model
= { .type
= PROC_MODEL
};
759 static struct prochandler_t proc_gpiomask
= { .type
= PROC_GPIOMASK
};
761 static int __init
diag_init(void)
763 static struct proc_dir_entry
*p
;
765 platform
= platform_detect();
767 printk(MODULE_NAME
": Router model not detected.\n");
770 printk(MODULE_NAME
": Detected '%s'\n", platform
->name
);
772 if (!(diag
= proc_mkdir("diag", NULL
))) {
773 printk(MODULE_NAME
": proc_mkdir on /proc/diag failed\n");
776 if ((p
= create_proc_entry("model", S_IRUSR
, diag
))) {
777 p
->data
= (void *) &proc_model
;
778 p
->proc_fops
= &diag_proc_fops
;
780 if ((p
= create_proc_entry("gpiomask", S_IRUSR
| S_IWUSR
, diag
))) {
781 p
->data
= (void *) &proc_gpiomask
;
782 p
->proc_fops
= &diag_proc_fops
;
785 if (platform
->buttons
)
786 register_buttons(platform
->buttons
);
789 register_leds(platform
->leds
);
796 module_init(diag_init
);
797 module_exit(diag_exit
);
799 MODULE_AUTHOR("Mike Baker, Felix Fietkau / OpenWrt.org");
800 MODULE_LICENSE("GPL");
803 /* TODO: export existing sb_irq instead */
804 static int sb_irq(void *sbh
)
809 uint32 flag
, sbipsflag
;
812 regs
= sb_coreregs(sbh
);
813 sb
= (sbconfig_t
*)((ulong
) regs
+ SBCONFIGOFF
);
814 flag
= (R_REG(&sb
->sbtpsflag
) & SBTPS_NUM0_MASK
);
816 idx
= sb_coreidx(sbh
);
818 if ((regs
= sb_setcore(sbh
, SB_MIPS
, 0)) ||
819 (regs
= sb_setcore(sbh
, SB_MIPS33
, 0))) {
820 sb
= (sbconfig_t
*)((ulong
) regs
+ SBCONFIGOFF
);
822 /* sbipsflag specifies which core is routed to interrupts 1 to 4 */
823 sbipsflag
= R_REG(&sb
->sbipsflag
);
824 for (irq
= 1; irq
<= 4; irq
++, sbipsflag
>>= 8) {
825 if ((sbipsflag
& 0x3f) == flag
)
832 sb_setcoreidx(sbh
, idx
);