Merge branch 'x86-fixes-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git...
[linux-2.6] / drivers / leds / leds-alix2.c
1 /*
2  * LEDs driver for PCEngines ALIX.2 and ALIX.3
3  *
4  * Copyright (C) 2008 Constantin Baranov <const@mimas.ru>
5  */
6
7 #include <linux/err.h>
8 #include <linux/io.h>
9 #include <linux/kernel.h>
10 #include <linux/leds.h>
11 #include <linux/module.h>
12 #include <linux/platform_device.h>
13 #include <linux/string.h>
14
15 static int force = 0;
16 module_param(force, bool, 0444);
17 MODULE_PARM_DESC(force, "Assume system has ALIX.2/ALIX.3 style LEDs");
18
19 struct alix_led {
20         struct led_classdev cdev;
21         unsigned short port;
22         unsigned int on_value;
23         unsigned int off_value;
24 };
25
26 static void alix_led_set(struct led_classdev *led_cdev,
27                          enum led_brightness brightness)
28 {
29         struct alix_led *led_dev =
30                 container_of(led_cdev, struct alix_led, cdev);
31
32         if (brightness)
33                 outl(led_dev->on_value, led_dev->port);
34         else
35                 outl(led_dev->off_value, led_dev->port);
36 }
37
38 static struct alix_led alix_leds[] = {
39         {
40                 .cdev = {
41                         .name = "alix:1",
42                         .brightness_set = alix_led_set,
43                 },
44                 .port = 0x6100,
45                 .on_value = 1 << 22,
46                 .off_value = 1 << 6,
47         },
48         {
49                 .cdev = {
50                         .name = "alix:2",
51                         .brightness_set = alix_led_set,
52                 },
53                 .port = 0x6180,
54                 .on_value = 1 << 25,
55                 .off_value = 1 << 9,
56         },
57         {
58                 .cdev = {
59                         .name = "alix:3",
60                         .brightness_set = alix_led_set,
61                 },
62                 .port = 0x6180,
63                 .on_value = 1 << 27,
64                 .off_value = 1 << 11,
65         },
66 };
67
68 static int __init alix_led_probe(struct platform_device *pdev)
69 {
70         int i;
71         int ret;
72
73         for (i = 0; i < ARRAY_SIZE(alix_leds); i++) {
74                 alix_leds[i].cdev.flags |= LED_CORE_SUSPENDRESUME;
75                 ret = led_classdev_register(&pdev->dev, &alix_leds[i].cdev);
76                 if (ret < 0)
77                         goto fail;
78         }
79         return 0;
80
81 fail:
82         while (--i >= 0)
83                 led_classdev_unregister(&alix_leds[i].cdev);
84         return ret;
85 }
86
87 static int alix_led_remove(struct platform_device *pdev)
88 {
89         int i;
90
91         for (i = 0; i < ARRAY_SIZE(alix_leds); i++)
92                 led_classdev_unregister(&alix_leds[i].cdev);
93         return 0;
94 }
95
96 static struct platform_driver alix_led_driver = {
97         .remove = alix_led_remove,
98         .driver = {
99                 .name = KBUILD_MODNAME,
100                 .owner = THIS_MODULE,
101         },
102 };
103
104 static int __init alix_present(void)
105 {
106         const unsigned long bios_phys = 0x000f0000;
107         const size_t bios_len = 0x00010000;
108         const char alix_sig[] = "PC Engines ALIX.";
109         const size_t alix_sig_len = sizeof(alix_sig) - 1;
110
111         const char *bios_virt;
112         const char *scan_end;
113         const char *p;
114         int ret = 0;
115
116         if (force) {
117                 printk(KERN_NOTICE "%s: forced to skip BIOS test, "
118                        "assume system has ALIX.2 style LEDs\n",
119                        KBUILD_MODNAME);
120                 ret = 1;
121                 goto out;
122         }
123
124         bios_virt = phys_to_virt(bios_phys);
125         scan_end = bios_virt + bios_len - (alix_sig_len + 2);
126         for (p = bios_virt; p < scan_end; p++) {
127                 const char *tail;
128
129                 if (memcmp(p, alix_sig, alix_sig_len) != 0) {
130                         continue;
131                 }
132
133                 tail = p + alix_sig_len;
134                 if ((tail[0] == '2' || tail[0] == '3') && tail[1] == '\0') {
135                         printk(KERN_INFO
136                                "%s: system is recognized as \"%s\"\n",
137                                KBUILD_MODNAME, p);
138                         ret = 1;
139                         break;
140                 }
141         }
142
143 out:
144         return ret;
145 }
146
147 static struct platform_device *pdev;
148
149 static int __init alix_led_init(void)
150 {
151         int ret;
152
153         if (!alix_present()) {
154                 ret = -ENODEV;
155                 goto out;
156         }
157
158         /* enable output on GPIO for LED 1,2,3 */
159         outl(1 << 6, 0x6104);
160         outl(1 << 9, 0x6184);
161         outl(1 << 11, 0x6184);
162
163         pdev = platform_device_register_simple(KBUILD_MODNAME, -1, NULL, 0);
164         if (!IS_ERR(pdev)) {
165                 ret = platform_driver_probe(&alix_led_driver, alix_led_probe);
166                 if (ret)
167                         platform_device_unregister(pdev);
168         } else
169                 ret = PTR_ERR(pdev);
170
171 out:
172         return ret;
173 }
174
175 static void __exit alix_led_exit(void)
176 {
177         platform_device_unregister(pdev);
178         platform_driver_unregister(&alix_led_driver);
179 }
180
181 module_init(alix_led_init);
182 module_exit(alix_led_exit);
183
184 MODULE_AUTHOR("Constantin Baranov <const@mimas.ru>");
185 MODULE_DESCRIPTION("PCEngines ALIX.2 and ALIX.3 LED driver");
186 MODULE_LICENSE("GPL");