add diag support for the WRT350N
[librecmc/librecmc.git] / package / broadcom-diag / src / diag.c
1 /*
2  * diag.c - GPIO interface driver for Broadcom boards
3  *
4  * Copyright (C) 2006 Mike Baker <mbm@openwrt.org>,
5  *                    Felix Fietkau <nbd@openwrt.org>
6  *
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.
11  *
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.
16  *
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.
20  *
21  * $Id$
22  */
23 #include <linux/module.h>
24 #include <linux/pci.h>
25 #include <linux/kmod.h>
26 #include <linux/proc_fs.h>
27 #include <linux/timer.h>
28 #include <linux/version.h>
29 #include <asm/uaccess.h>
30
31 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
32 #include <linux/kobject.h>
33 #include <linux/workqueue.h>
34 #define hotplug_path uevent_helper
35 #else
36 #include <linux/tqueue.h>
37 #define INIT_WORK INIT_TQUEUE
38 #define schedule_work schedule_task
39 #define work_struct tq_struct
40 #endif
41
42 #include "gpio.h"
43 #include "diag.h"
44 #define getvar(str) (nvram_get(str)?:"")
45
46 static unsigned int gpiomask = 0;
47 module_param(gpiomask, int, 0644);
48
49 enum {
50         /* Linksys */
51         WAP54GV1,
52         WAP54GV3,
53         WRT54GV1,
54         WRT54G,
55         WRTSL54GS,
56         WRT54G3G,
57         WRT350N,
58         
59         /* ASUS */
60         WLHDD,
61         WL300G,
62         WL500G,
63         WL500GD,
64         WL500GP,
65         ASUS_4702,
66         
67         /* Buffalo */
68         WBR2_G54,
69         WHR_G54S,
70         WHR_HP_G54,
71         WHR2_A54G54,
72         WLA2_G54L,
73         WZR_RS_G54,
74         WZR_RS_G54HP,
75         BUFFALO_UNKNOWN,
76         BUFFALO_UNKNOWN_4710,
77
78         /* Siemens */
79         SE505V1,
80         SE505V2,
81         
82         /* US Robotics */
83         USR5461,
84
85         /* Dell */
86         TM2300,
87
88         /* Motorola */
89         WE800G,
90         WR850GV1,
91         WR850GV2V3,
92
93         /* Belkin */
94         BELKIN_UNKNOWN,
95
96         /* Netgear */
97         WGT634U,
98
99         /* Trendware */
100         TEW411BRPP,
101 };
102
103 static struct platform_t __initdata platforms[] = {
104         /* Linksys */
105         [WAP54GV1] = {
106                 .name           = "Linksys WAP54G V1",
107                 .buttons        = {
108                         { .name = "reset",      .gpio = 1 << 0 },
109                 },
110                 .leds           = { 
111                         { .name = "diag",       .gpio = 1 << 3 },
112                         { .name = "wlan",       .gpio = 1 << 4 },
113                 },
114         },
115         [WAP54GV3] = {
116                 .name           = "Linksys WAP54G V3",
117                 .buttons        = {
118                         /* FIXME: verify this */
119                         { .name = "reset",      .gpio = 1 << 7 },
120                         { .name = "ses",        .gpio = 1 << 0 },
121                 },
122                 .leds           = { 
123                         /* FIXME: diag? */
124                         { .name = "ses",        .gpio = 1 << 1 },
125                 },
126         },
127         [WRT54GV1] = {
128                 .name           = "Linksys WRT54G V1.x",
129                 .buttons        = {
130                         { .name = "reset",      .gpio = 1 << 6 },
131                 },
132                 .leds           = { 
133                         { .name = "diag",       .gpio = 0x13 | GPIO_TYPE_EXTIF, .polarity = NORMAL },
134                         { .name = "dmz",        .gpio = 0x12 | GPIO_TYPE_EXTIF, .polarity = NORMAL },
135                 },
136         },
137         [WRT54G] = {
138                 .name           = "Linksys WRT54G/GS/GL",
139                 .buttons        = {
140                         { .name = "reset",      .gpio = 1 << 6 },
141                         { .name = "ses",        .gpio = 1 << 4 },
142                 },
143                 .leds           = {
144                         { .name = "power",      .gpio = 1 << 1, .polarity = NORMAL },
145                         { .name = "dmz",        .gpio = 1 << 7, .polarity = REVERSE },
146                         { .name = "ses_white",  .gpio = 1 << 2, .polarity = REVERSE },
147                         { .name = "ses_orange", .gpio = 1 << 3, .polarity = REVERSE },
148                         { .name = "wlan",       .gpio = 1 << 0, .polarity = REVERSE },
149                 },
150         },
151         [WRTSL54GS] = {
152                 .name           = "Linksys WRTSL54GS",
153                 .buttons        = {
154                         { .name = "reset",      .gpio = 1 << 6 },
155                         { .name = "ses",        .gpio = 1 << 4 },
156                 },
157                 .leds           = {
158                         { .name = "power",      .gpio = 1 << 1, .polarity = NORMAL },
159                         { .name = "dmz",        .gpio = 1 << 0, .polarity = REVERSE },
160                         { .name = "ses_white",  .gpio = 1 << 5, .polarity = REVERSE },
161                         { .name = "ses_orange", .gpio = 1 << 7, .polarity = REVERSE },
162                 },
163         },
164         [WRT54G3G] = {
165                 .name           = "Linksys WRT54G3G",
166                 .buttons        = {
167                         { .name = "reset",      .gpio = 1 << 6 },
168                         { .name = "3g",         .gpio = 1 << 4 },
169                 },
170                 .leds           = {
171                         { .name = "power",      .gpio = 1 << 1, .polarity = NORMAL },
172                         { .name = "dmz",        .gpio = 1 << 7, .polarity = REVERSE },
173                         { .name = "3g_green",   .gpio = 1 << 2, .polarity = NORMAL },
174                         { .name = "3g_blue",    .gpio = 1 << 3, .polarity = NORMAL },
175                         { .name = "3g_blink",   .gpio = 1 << 5, .polarity = NORMAL },
176                 },
177         },
178         [WRT350N] = {
179                 .name           = "Linksys WRT350N",
180                 .buttons        = {
181                         { .name = "reset",      .gpio = 1 << 6 },
182                         { .name = "ses",        .gpio = 1 << 8 },
183                 },
184                 .leds           = {
185                         { .name = "power",      .gpio = 1 << 1, .polarity = NORMAL },
186                         { .name = "ses",        .gpio = 1 << 3, .polarity = REVERSE },
187                 },
188         },
189         /* Asus */
190         [WLHDD] = {
191                 .name           = "ASUS WL-HDD",
192                 .buttons        = {
193                         { .name = "reset",      .gpio = 1 << 6 },
194                 },
195                 .leds           = {
196                         { .name = "power",      .gpio = 1 << 0, .polarity = REVERSE },
197                 },
198         },
199         [WL300G] = {
200                 .name           = "ASUS WL-300g",
201                 .buttons        = {
202                         { .name = "reset",      .gpio = 1 << 6 },
203                 },
204                 .leds           = {
205                         { .name = "power",      .gpio = 1 << 0, .polarity = REVERSE },
206                 },
207         },
208         [WL500G] = {
209                 .name           = "ASUS WL-500g",
210                 .buttons        = {
211                         { .name = "reset",      .gpio = 1 << 6 },
212                 },
213                 .leds           = {
214                         { .name = "power",      .gpio = 1 << 0, .polarity = REVERSE },
215                 },
216         },
217         [WL500GD] = {
218                 .name           = "ASUS WL-500g Deluxe",
219                 .buttons        = {
220                         { .name = "reset",      .gpio = 1 << 6 },
221                 },
222                 .leds           = {
223                         { .name = "power",      .gpio = 1 << 0, .polarity = REVERSE },
224                 },
225         },
226         [WL500GP] = {
227                 .name           = "ASUS WL-500g Premium",
228                 .buttons        = {
229                         { .name = "reset",      .gpio = 1 << 0 },
230                         { .name = "ses",        .gpio = 1 << 4 },
231                 },
232                 .leds           = {
233                         { .name = "power",      .gpio = 1 << 1, .polarity = REVERSE },
234                 },
235         },
236         [ASUS_4702] = {
237                 .name           = "ASUS (unknown, BCM4702)",
238                 .buttons        = {
239                         { .name = "reset",      .gpio = 1 << 6 },
240                 },
241                 .leds           = {
242                         { .name = "power",      .gpio = 1 << 0, .polarity = REVERSE },
243                 },
244         },
245         /* Buffalo */
246         [WHR_G54S] = {
247                 .name           = "Buffalo WHR-G54S",
248                 .buttons        = {
249                         { .name = "reset",      .gpio = 1 << 4 },
250                         { .name = "bridge",     .gpio = 1 << 5 },
251                         { .name = "ses",        .gpio = 1 << 0 },
252                 },
253                 .leds           = {
254                         { .name = "diag",       .gpio = 1 << 7, .polarity = REVERSE },
255                         { .name = "internal",   .gpio = 1 << 3, .polarity = REVERSE },
256                         { .name = "ses",        .gpio = 1 << 6, .polarity = REVERSE },
257                         { .name = "bridge",     .gpio = 1 << 1, .polarity = REVERSE },
258                 },
259         },
260         [WBR2_G54] = {
261                 .name           = "Buffalo WBR2-G54",
262                 /* FIXME: verify */
263                 .buttons        = {
264                         { .name = "reset",      .gpio = 1 << 7 },
265                 },
266                 .leds           = {
267                         { .name = "diag",       .gpio = 1 << 1, .polarity = REVERSE },
268                 },
269         },
270         [WHR_HP_G54] = {
271                 .name           = "Buffalo WHR-HP-G54",
272                 .buttons        = {
273                         { .name = "reset",      .gpio = 1 << 4 },
274                         { .name = "bridge",     .gpio = 1 << 5 },
275                         { .name = "ses",        .gpio = 1 << 0 },
276                 },
277                 .leds           = {
278                         { .name = "diag",       .gpio = 1 << 7, .polarity = REVERSE },
279                         { .name = "bridge",     .gpio = 1 << 1, .polarity = REVERSE },
280                         { .name = "ses",        .gpio = 1 << 6, .polarity = REVERSE },
281                 },
282         },
283         [WHR2_A54G54] = {
284                 .name           = "Buffalo WHR2-A54G54",
285                 .buttons        = {
286                         { .name = "reset",      .gpio = 1 << 4 },
287                 },
288                 .leds           = {
289                         { .name = "diag",       .gpio = 1 << 7, .polarity = REVERSE },
290                 },
291         },
292         [WLA2_G54L] = {
293                 .name           = "Buffalo WLA2-G54L",
294                 /* FIXME: verify */
295                 .buttons        = {
296                         { .name = "reset",      .gpio = 1 << 7 },
297                 },
298                 .leds           = {
299                         { .name = "diag",       .gpio = 1 << 1, .polarity = REVERSE },
300                 },
301         },
302         [WZR_RS_G54] = {
303                 .name           = "Buffalo WZR-RS-G54",
304                 .buttons        = {
305                         { .name = "ses",        .gpio = 1 << 0 },
306                         { .name = "reset",      .gpio = 1 << 4 },
307                 },
308                 .leds           = {
309                         { .name = "diag",       .gpio = 1 << 7, .polarity = REVERSE },
310                         { .name = "ses",        .gpio = 1 << 6, .polarity = REVERSE },
311                         { .name = "vpn",        .gpio = 1 << 1, .polarity = REVERSE },
312                 },
313         },
314         [WZR_RS_G54HP] = {
315                 .name           = "Buffalo WZR-RS-G54HP",
316                 .buttons        = {
317                         { .name = "ses",        .gpio = 1 << 0 },
318                         { .name = "reset",      .gpio = 1 << 4 },
319                 },
320                 .leds           = {
321                         { .name = "diag",       .gpio = 1 << 7, .polarity = REVERSE },
322                         { .name = "ses",        .gpio = 1 << 6, .polarity = REVERSE },
323                         { .name = "vpn",        .gpio = 1 << 1, .polarity = REVERSE },
324                 },
325         },
326         [BUFFALO_UNKNOWN] = {
327                 .name           = "Buffalo (unknown)",
328                 .buttons        = {
329                         { .name = "reset",      .gpio = 1 << 7 },
330                 },
331                 .leds           = {
332                         { .name = "diag",       .gpio = 1 << 1, .polarity = REVERSE },
333                 },
334         },
335         [BUFFALO_UNKNOWN_4710] = {
336                 .name           = "Buffalo (unknown, BCM4710)",
337                 .buttons        = {
338                         { .name = "reset",      .gpio = 1 << 4 },
339                 },
340                 .leds           = {
341                         { .name = "diag",       .gpio = 1 << 1, .polarity = REVERSE },
342                 },
343         },
344         /* Siemens */
345         [SE505V1] = {
346                 .name           = "Siemens SE505 V1",
347                 .buttons        = {
348                         /* No usable buttons */
349                 },
350                 .leds           = {
351                         { .name = "dmz",        .gpio = 1 << 4, .polarity = REVERSE },
352                         { .name = "wlan",       .gpio = 1 << 3, .polarity = REVERSE },
353                 },
354         },
355         [SE505V2] = {
356                 .name           = "Siemens SE505 V2",
357                 .buttons        = {
358                         /* No usable buttons */
359                 },
360                 .leds           = {
361                         { .name = "power",      .gpio = 1 << 5, .polarity = REVERSE },
362                         { .name = "dmz",        .gpio = 1 << 0, .polarity = REVERSE },
363                         { .name = "wlan",       .gpio = 1 << 3, .polarity = REVERSE },
364                 },
365         },
366         /* US Robotics */
367         [USR5461] = {
368                 .name           = "U.S. Robotics USR5461",
369                 .buttons        = {
370                         /* No usable buttons */
371                 },
372                 .leds           = {
373                         { .name = "wlan",       .gpio = 1 << 0, .polarity = REVERSE },
374                         { .name = "printer",    .gpio = 1 << 1, .polarity = REVERSE },
375                 },
376         },
377         /* Dell */
378         [TM2300] = {
379                 .name           = "Dell TrueMobile 2300",
380                 .buttons        = {
381                         { .name = "reset",      .gpio = 1 << 0 },
382                 },
383                 .leds           = {
384                         { .name = "diag",       .gpio = 1 << 7, .polarity = REVERSE },
385                 },
386         },
387         /* Motorola */
388         [WE800G] = {
389                 .name           = "Motorola WE800G",
390                 .buttons        = {
391                         { .name = "reset",      .gpio = 1 << 0 },
392                 },
393                 .leds           = {
394                         { .name = "power",      .gpio = 1 << 4, .polarity = NORMAL },
395                         { .name = "diag",       .gpio = 1 << 2, .polarity = REVERSE },
396                         { .name = "wlan_amber", .gpio = 1 << 1, .polarity = NORMAL },
397                 },
398         },
399         [WR850GV1] = {
400                 .name           = "Motorola WR850G V1",
401                 .buttons        = {
402                         { .name = "reset",      .gpio = 1 << 0 },
403                 },
404                 .leds           = {
405                         { .name = "power",      .gpio = 1 << 4, .polarity = NORMAL },
406                         { .name = "diag",       .gpio = 1 << 3, .polarity = REVERSE },
407                         { .name = "dmz",        .gpio = 1 << 6, .polarity = NORMAL },
408                         { .name = "wlan_red",   .gpio = 1 << 5, .polarity = REVERSE },
409                         { .name = "wlan_green", .gpio = 1 << 7, .polarity = REVERSE },
410                 },
411         },
412         [WR850GV2V3] = {
413                 .name           = "Motorola WR850G V2/V3",
414                 .buttons        = {
415                         { .name = "reset",      .gpio = 1 << 5 },
416                 },
417                 .leds           = {
418                         { .name = "power",      .gpio = 1 << 1, .polarity = NORMAL },
419                         { .name = "wlan",       .gpio = 1 << 0, .polarity = REVERSE },
420                         { .name = "dmz",        .gpio = 1 << 6, .polarity = REVERSE },
421                         { .name = "diag",       .gpio = 1 << 7, .polarity = REVERSE },
422                 },
423         },
424         /* Belkin */
425         [BELKIN_UNKNOWN] = {
426                 .name           = "Belkin (unknown)",
427                 /* FIXME: verify & add detection */
428                 .buttons        = {
429                         { .name = "reset",      .gpio = 1 << 7 },
430                 },
431                 .leds           = {
432                         { .name = "power",      .gpio = 1 << 5, .polarity = NORMAL },
433                         { .name = "wlan",       .gpio = 1 << 3, .polarity = NORMAL },
434                         { .name = "connected",  .gpio = 1 << 0, .polarity = NORMAL },
435                 },
436         },
437         /* Netgear */
438         [WGT634U] = {
439                 .name           = "Netgear WGT634U",
440                 .buttons        = {
441                         { .name = "reset",      .gpio = 1 << 2 },
442                 },
443                 .leds           = {
444                         { .name = "power",      .gpio = 1 << 3, .polarity = REVERSE },
445                 },
446         },
447         /* Trendware */
448         [TEW411BRPP] = {
449                 .name           = "Trendware TEW411BRP+",
450                 .buttons        = {
451                         { /* No usable buttons */ },
452                 },
453                 .leds           = {
454                         { .name = "power",      .gpio = 1 << 7, .polarity = NORMAL },
455                         { .name = "wlan",       .gpio = 1 << 1, .polarity = NORMAL },
456                         { .name = "bridge",     .gpio = 1 << 6, .polarity = NORMAL },
457                 },
458         },
459 };
460
461 static struct platform_t __init *platform_detect(void)
462 {
463         char *boardnum, *boardtype, *buf;
464
465         boardnum = getvar("boardnum");
466         boardtype = getvar("boardtype");
467         if (strncmp(getvar("pmon_ver"), "CFE", 3) == 0) {
468                 /* CFE based - newer hardware */
469                 if (!strcmp(boardnum, "42")) { /* Linksys */
470                         if (!strcmp(boardtype, "0x478") && !strcmp(getvar("cardbus"), 1))
471                                 return &platforms[WRT350N];
472
473                         if (!strcmp(boardtype, "0x0101") && !strcmp(getvar("boot_ver"), "v3.6"))
474                                 return &platforms[WRT54G3G];
475
476                         if (!strcmp(getvar("et1phyaddr"),"5") && !strcmp(getvar("et1mdcport"), "1"))
477                                 return &platforms[WRTSL54GS];
478                         
479                         /* default to WRT54G */
480                         return &platforms[WRT54G];
481                 }
482                 
483                 if (!strcmp(boardnum, "45")) { /* ASUS */
484                         if (!strcmp(boardtype,"0x042f"))
485                                 return &platforms[WL500GP];
486                         else
487                                 return &platforms[WL500GD];
488                 }
489                 
490                 if (!strcmp(boardnum, "10496"))
491                         return &platforms[USR5461];
492         } else { /* PMON based - old stuff */
493                 if ((simple_strtoul(getvar("GemtekPmonVer"), NULL, 0) == 9) &&
494                         (simple_strtoul(getvar("et0phyaddr"), NULL, 0) == 30)) {
495                         if (!strncmp(getvar("ModelId"),"WE800G", 6))
496                                 return &platforms[WE800G];
497                         else
498                                 return &platforms[WR850GV1];
499                 }
500                 if (!strncmp(boardtype, "bcm94710dev", 11)) {
501                         if (!strcmp(boardnum, "42"))
502                                 return &platforms[WRT54GV1];
503                         if (simple_strtoul(boardnum, NULL, 0) == 2)
504                                 return &platforms[WAP54GV1];
505                 }
506                 if (!strncmp(getvar("hardware_version"), "WL500-", 6))
507                         return &platforms[WL500G];
508                 if (!strncmp(getvar("hardware_version"), "WL300-", 6)) {
509                         /* Either WL-300g or WL-HDD, do more extensive checks */
510                         if ((simple_strtoul(getvar("et0phyaddr"), NULL, 0) == 0) &&
511                                 (simple_strtoul(getvar("et1phyaddr"), NULL, 0) == 1))
512                                 return &platforms[WLHDD];
513                         if ((simple_strtoul(getvar("et0phyaddr"), NULL, 0) == 0) &&
514                                 (simple_strtoul(getvar("et1phyaddr"), NULL, 0) == 10))
515                                 return &platforms[WL300G];
516                 }
517
518                 /* unknown asus stuff, probably bcm4702 */
519                 if (!strncmp(boardnum, "asusX", 5))
520                         return &platforms[ASUS_4702];
521         }
522
523         if ((buf = (nvram_get("melco_id") ?: nvram_get("buffalo_id")))) {
524                 /* Buffalo hardware, check id for specific hardware matches */
525                 if (!strcmp(buf, "29bb0332"))
526                         return &platforms[WBR2_G54];
527                 if (!strcmp(buf, "29129"))
528                         return &platforms[WLA2_G54L];
529                 if (!strcmp(buf, "30189"))
530                         return &platforms[WHR_HP_G54];
531                 if (!strcmp(buf, "30182"))
532                         return &platforms[WHR_G54S];
533                 if (!strcmp(buf, "290441dd"))
534                         return &platforms[WHR2_A54G54];
535                 if (!strcmp(buf, "30083"))
536                         return &platforms[WZR_RS_G54];
537                 if (!strcmp(buf, "30103"))
538                         return &platforms[WZR_RS_G54HP];
539         }
540
541         if (buf || !strcmp(boardnum, "00")) {/* probably buffalo */
542                 if (!strncmp(boardtype, "bcm94710ap", 10))
543                         return &platforms[BUFFALO_UNKNOWN_4710];
544                 else
545                         return &platforms[BUFFALO_UNKNOWN];
546         }
547
548         if (!strcmp(getvar("CFEver"), "MotoWRv203") ||
549                 !strcmp(getvar("MOTO_BOARD_TYPE"), "WR_FEM1")) {
550
551                 return &platforms[WR850GV2V3];
552         }
553
554         if (!strcmp(boardnum, "44")) {  /* Trendware TEW-411BRP+ */
555                 return &platforms[TEW411BRPP];
556         }
557
558         /* not found */
559         return NULL;
560 }
561
562 static void register_buttons(struct button_t *b)
563 {
564         for (; b->name; b++)
565                 platform.button_mask |= b->gpio;
566
567         platform.button_mask &= ~gpiomask;
568
569         gpio_outen(platform.button_mask, 0);
570         gpio_control(platform.button_mask, 0);
571         platform.button_polarity = gpio_in() & platform.button_mask;
572         gpio_intpolarity(platform.button_mask, platform.button_polarity);
573         gpio_intmask(platform.button_mask, platform.button_mask);
574
575         gpio_set_irqenable(1, button_handler);
576 }
577
578 static void unregister_buttons(struct button_t *b)
579 {
580         gpio_intmask(platform.button_mask, 0);
581
582         gpio_set_irqenable(0, button_handler);
583 }
584
585 static void hotplug_button(struct event_t *event)
586 {
587 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
588         call_usermodehelper (event->argv[0], event->argv, event->envp, 1);
589 #else
590         call_usermodehelper (event->argv[0], event->argv, event->envp);
591 #endif
592         kfree(event);
593 }
594
595 static irqreturn_t button_handler(int irq, void *dev_id, struct pt_regs *regs)
596 {
597         struct button_t *b;
598         u32 in, changed;
599
600         in = gpio_in() & platform.button_mask;
601         gpio_intpolarity(platform.button_mask, in);
602         changed = platform.button_polarity ^ in;
603         platform.button_polarity = in;
604
605         changed &= ~gpio_outen(0, 0);
606
607         for (b = platform.buttons; b->name; b++) { 
608                 struct event_t *event;
609
610                 if (!(b->gpio & changed)) continue;
611
612                 b->pressed ^= 1;
613
614                 if ((event = (struct event_t *)kmalloc (sizeof(struct event_t), GFP_ATOMIC))) {
615                         int i;
616                         char *scratch = event->buf;
617
618                         i = 0;
619                         event->argv[i++] = hotplug_path;
620                         event->argv[i++] = "button";
621                         event->argv[i] = 0;
622
623                         i = 0;
624                         event->envp[i++] = "HOME=/";
625                         event->envp[i++] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin";
626                         event->envp[i++] = scratch;
627                         scratch += sprintf (scratch, "ACTION=%s", b->pressed?"pressed":"released") + 1;
628                         event->envp[i++] = scratch;
629                         scratch += sprintf (scratch, "BUTTON=%s", b->name) + 1;
630                         event->envp[i++] = scratch;
631                         scratch += sprintf (scratch, "SEEN=%ld", (jiffies - b->seen)/HZ) + 1;
632                         event->envp[i] = 0;
633
634                         INIT_WORK(&event->wq, (void *)(void *)hotplug_button, (void *)event);
635                         schedule_work(&event->wq);
636                 }
637
638                 b->seen = jiffies;
639         }
640         return IRQ_HANDLED;
641 }
642
643 static void register_leds(struct led_t *l)
644 {
645         struct proc_dir_entry *p;
646         u32 mask = 0;
647         u32 val = 0;
648
649         leds = proc_mkdir("led", diag);
650         if (!leds) 
651                 return;
652
653         for(; l->name; l++) {
654                 if (l->gpio & gpiomask)
655                         continue;
656         
657                 if (l->gpio & GPIO_TYPE_EXTIF) {
658                         l->state = 0;
659                         set_led_extif(l);
660                 } else {
661                         mask |= l->gpio;
662                         val |= (l->polarity == NORMAL)?0:l->gpio;
663                 }
664
665                 if ((p = create_proc_entry(l->name, S_IRUSR, leds))) {
666                         l->proc.type = PROC_LED;
667                         l->proc.ptr = l;
668                         p->data = (void *) &l->proc;
669                         p->proc_fops = &diag_proc_fops;
670                 }
671         }
672
673         gpio_outen(mask, mask);
674         gpio_control(mask, 0);
675         gpio_out(mask, val);
676 }
677
678 static void unregister_leds(struct led_t *l)
679 {
680         for(; l->name; l++)
681                 remove_proc_entry(l->name, leds);
682
683         remove_proc_entry("led", diag);
684 }
685
686 static void set_led_extif(struct led_t *led)
687 {
688         gpio_set_extif(led->gpio, led->state);
689 }
690
691 static void led_flash(unsigned long dummy) {
692         struct led_t *l;
693         u32 mask = 0;
694         u8 extif_blink = 0;
695
696         for (l = platform.leds; l->name; l++) {
697                 if (l->flash) {
698                         if (l->gpio & GPIO_TYPE_EXTIF) {
699                                 extif_blink = 1;
700                                 l->state = !l->state;
701                                 set_led_extif(l);
702                         } else {
703                                 mask |= l->gpio;
704                         }
705                 }
706         }
707
708         mask &= ~gpiomask;
709         if (mask) {
710                 u32 val = ~gpio_in();
711
712                 gpio_outen(mask, mask);
713                 gpio_control(mask, 0);
714                 gpio_out(mask, val);
715         }
716         if (mask || extif_blink) {
717                 mod_timer(&led_timer, jiffies + FLASH_TIME);
718         }
719 }
720
721 static ssize_t diag_proc_read(struct file *file, char *buf, size_t count, loff_t *ppos)
722 {
723 #ifdef LINUX_2_4
724         struct inode *inode = file->f_dentry->d_inode;
725         struct proc_dir_entry *dent = inode->u.generic_ip;
726 #else
727         struct proc_dir_entry *dent = PDE(file->f_dentry->d_inode);
728 #endif
729         char *page;
730         int len = 0;
731         
732         if ((page = kmalloc(1024, GFP_KERNEL)) == NULL)
733                 return -ENOBUFS;
734         
735         if (dent->data != NULL) {
736                 struct prochandler_t *handler = (struct prochandler_t *) dent->data;
737                 switch (handler->type) {
738                         case PROC_LED: {
739                                 struct led_t * led = (struct led_t *) handler->ptr;
740                                 if (led->flash) {
741                                         len = sprintf(page, "f\n");
742                                 } else {
743                                         if (led->gpio & GPIO_TYPE_EXTIF) {
744                                                 len = sprintf(page, "%d\n", led->state);
745                                         } else {
746                                                 u32 in = (gpio_in() & led->gpio ? 1 : 0);
747                                                 u8 p = (led->polarity == NORMAL ? 0 : 1);
748                                                 len = sprintf(page, "%d\n", ((in ^ p) ? 1 : 0));
749                                         }
750                                 }
751                                 break;
752                         }
753                         case PROC_MODEL:
754                                 len = sprintf(page, "%s\n", platform.name);
755                                 break;
756                         case PROC_GPIOMASK:
757                                 len = sprintf(page, "0x%04x\n", gpiomask);
758                                 break;
759                 }
760         }
761         len += 1;
762
763         if (*ppos < len) {
764                 len = min_t(int, len - *ppos, count);
765                 if (copy_to_user(buf, (page + *ppos), len)) {
766                         kfree(page);
767                         return -EFAULT;
768                 }
769                 *ppos += len;
770         } else {
771                 len = 0;
772         }
773
774         return len;
775 }
776
777
778 static ssize_t diag_proc_write(struct file *file, const char *buf, size_t count, loff_t *ppos)
779 {
780 #ifdef LINUX_2_4
781         struct inode *inode = file->f_dentry->d_inode;
782         struct proc_dir_entry *dent = inode->u.generic_ip;
783 #else
784         struct proc_dir_entry *dent = PDE(file->f_dentry->d_inode);
785 #endif
786         char *page;
787         int ret = -EINVAL;
788
789         if ((page = kmalloc(count + 1, GFP_KERNEL)) == NULL)
790                 return -ENOBUFS;
791
792         if (copy_from_user(page, buf, count)) {
793                 kfree(page);
794                 return -EINVAL;
795         }
796         page[count] = 0;
797         
798         if (dent->data != NULL) {
799                 struct prochandler_t *handler = (struct prochandler_t *) dent->data;
800                 switch (handler->type) {
801                         case PROC_LED: {
802                                 struct led_t *led = (struct led_t *) handler->ptr;
803                                 int p = (led->polarity == NORMAL ? 0 : 1);
804                                 
805                                 if (page[0] == 'f') {
806                                         led->flash = 1;
807                                         led_flash(0);
808                                 } else {
809                                         led->flash = 0;
810                                         if (led->gpio & GPIO_TYPE_EXTIF) {
811                                                 led->state = p ^ ((page[0] == '1') ? 1 : 0);
812                                                 set_led_extif(led);
813                                         } else {
814                                                 gpio_outen(led->gpio, led->gpio);
815                                                 gpio_control(led->gpio, 0);
816                                                 gpio_out(led->gpio, ((p ^ (page[0] == '1')) ? led->gpio : 0));
817                                         }
818                                 }
819                                 break;
820                         }
821                         case PROC_GPIOMASK:
822                                 gpiomask = simple_strtoul(page, NULL, 0);
823
824                                 if (platform.buttons) {
825                                         unregister_buttons(platform.buttons);
826                                         register_buttons(platform.buttons);
827                                 }
828
829                                 if (platform.leds) {
830                                         unregister_leds(platform.leds);
831                                         register_leds(platform.leds);
832                                 }
833                                 break;
834                 }
835                 ret = count;
836         }
837
838         kfree(page);
839         return ret;
840 }
841
842 static int __init diag_init(void)
843 {
844         static struct proc_dir_entry *p;
845         static struct platform_t *detected;
846
847         detected = platform_detect();
848         if (!detected) {
849                 printk(MODULE_NAME ": Router model not detected.\n");
850                 return -ENODEV;
851         }
852         memcpy(&platform, detected, sizeof(struct platform_t));
853
854         printk(MODULE_NAME ": Detected '%s'\n", platform.name);
855
856         if (!(diag = proc_mkdir("diag", NULL))) {
857                 printk(MODULE_NAME ": proc_mkdir on /proc/diag failed\n");
858                 return -EINVAL;
859         }
860
861         if ((p = create_proc_entry("model", S_IRUSR, diag))) {
862                 p->data = (void *) &proc_model;
863                 p->proc_fops = &diag_proc_fops;
864         }
865
866         if ((p = create_proc_entry("gpiomask", S_IRUSR | S_IWUSR, diag))) {
867                 p->data = (void *) &proc_gpiomask;
868                 p->proc_fops = &diag_proc_fops;
869         }
870
871         if (platform.buttons)
872                 register_buttons(platform.buttons);
873
874         if (platform.leds)
875                 register_leds(platform.leds);
876
877         return 0;
878 }
879
880 static void __exit diag_exit(void)
881 {
882
883         del_timer(&led_timer);
884
885         if (platform.buttons)
886                 unregister_buttons(platform.buttons);
887
888         if (platform.leds)
889                 unregister_leds(platform.leds);
890
891         remove_proc_entry("model", diag);
892         remove_proc_entry("gpiomask", diag);
893         remove_proc_entry("diag", NULL);
894 }
895
896 module_init(diag_init);
897 module_exit(diag_exit);
898
899 MODULE_AUTHOR("Mike Baker, Felix Fietkau / OpenWrt.org");
900 MODULE_LICENSE("GPL");