]>
Commit | Line | Data |
---|---|---|
ec9a943c CB |
1 | /* |
2 | * LEDs driver for PCEngines ALIX.2 and ALIX.3 | |
3 | * | |
4 | * Copyright (C) 2008 Constantin Baranov <[email protected]> | |
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> | |
7f131cf3 | 14 | #include <linux/pci.h> |
ec9a943c CB |
15 | |
16 | static int force = 0; | |
17 | module_param(force, bool, 0444); | |
2216c6e8 | 18 | MODULE_PARM_DESC(force, "Assume system has ALIX.2/ALIX.3 style LEDs"); |
ec9a943c | 19 | |
7f131cf3 DM |
20 | #define MSR_LBAR_GPIO 0x5140000C |
21 | #define CS5535_GPIO_SIZE 256 | |
22 | ||
23 | static u32 gpio_base; | |
24 | ||
25 | static struct pci_device_id divil_pci[] = { | |
26 | { PCI_DEVICE(PCI_VENDOR_ID_NS, PCI_DEVICE_ID_NS_CS5535_ISA) }, | |
27 | { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_CS5536_ISA) }, | |
28 | { } /* NULL entry */ | |
29 | }; | |
30 | MODULE_DEVICE_TABLE(pci, divil_pci); | |
31 | ||
ec9a943c CB |
32 | struct alix_led { |
33 | struct led_classdev cdev; | |
34 | unsigned short port; | |
35 | unsigned int on_value; | |
36 | unsigned int off_value; | |
37 | }; | |
38 | ||
39 | static void alix_led_set(struct led_classdev *led_cdev, | |
40 | enum led_brightness brightness) | |
41 | { | |
42 | struct alix_led *led_dev = | |
43 | container_of(led_cdev, struct alix_led, cdev); | |
44 | ||
45 | if (brightness) | |
7f131cf3 | 46 | outl(led_dev->on_value, gpio_base + led_dev->port); |
ec9a943c | 47 | else |
7f131cf3 | 48 | outl(led_dev->off_value, gpio_base + led_dev->port); |
ec9a943c CB |
49 | } |
50 | ||
51 | static struct alix_led alix_leds[] = { | |
52 | { | |
53 | .cdev = { | |
54 | .name = "alix:1", | |
55 | .brightness_set = alix_led_set, | |
56 | }, | |
7f131cf3 | 57 | .port = 0x00, |
ec9a943c CB |
58 | .on_value = 1 << 22, |
59 | .off_value = 1 << 6, | |
60 | }, | |
61 | { | |
62 | .cdev = { | |
63 | .name = "alix:2", | |
64 | .brightness_set = alix_led_set, | |
65 | }, | |
7f131cf3 | 66 | .port = 0x80, |
ec9a943c CB |
67 | .on_value = 1 << 25, |
68 | .off_value = 1 << 9, | |
69 | }, | |
70 | { | |
71 | .cdev = { | |
72 | .name = "alix:3", | |
73 | .brightness_set = alix_led_set, | |
74 | }, | |
7f131cf3 | 75 | .port = 0x80, |
ec9a943c CB |
76 | .on_value = 1 << 27, |
77 | .off_value = 1 << 11, | |
78 | }, | |
79 | }; | |
80 | ||
ec9a943c CB |
81 | static int __init alix_led_probe(struct platform_device *pdev) |
82 | { | |
83 | int i; | |
84 | int ret; | |
85 | ||
86 | for (i = 0; i < ARRAY_SIZE(alix_leds); i++) { | |
859cb7f2 | 87 | alix_leds[i].cdev.flags |= LED_CORE_SUSPENDRESUME; |
ec9a943c CB |
88 | ret = led_classdev_register(&pdev->dev, &alix_leds[i].cdev); |
89 | if (ret < 0) | |
90 | goto fail; | |
91 | } | |
92 | return 0; | |
93 | ||
94 | fail: | |
95 | while (--i >= 0) | |
96 | led_classdev_unregister(&alix_leds[i].cdev); | |
97 | return ret; | |
98 | } | |
99 | ||
100 | static int alix_led_remove(struct platform_device *pdev) | |
101 | { | |
102 | int i; | |
103 | ||
104 | for (i = 0; i < ARRAY_SIZE(alix_leds); i++) | |
105 | led_classdev_unregister(&alix_leds[i].cdev); | |
106 | return 0; | |
107 | } | |
108 | ||
109 | static struct platform_driver alix_led_driver = { | |
110 | .remove = alix_led_remove, | |
ec9a943c CB |
111 | .driver = { |
112 | .name = KBUILD_MODNAME, | |
113 | .owner = THIS_MODULE, | |
114 | }, | |
115 | }; | |
116 | ||
7f131cf3 DM |
117 | static int __init alix_present(unsigned long bios_phys, |
118 | const char *alix_sig, | |
119 | size_t alix_sig_len) | |
ec9a943c | 120 | { |
ec9a943c | 121 | const size_t bios_len = 0x00010000; |
ec9a943c CB |
122 | const char *bios_virt; |
123 | const char *scan_end; | |
124 | const char *p; | |
7f131cf3 | 125 | char name[64]; |
ec9a943c CB |
126 | |
127 | if (force) { | |
128 | printk(KERN_NOTICE "%s: forced to skip BIOS test, " | |
129 | "assume system has ALIX.2 style LEDs\n", | |
130 | KBUILD_MODNAME); | |
7f131cf3 | 131 | return 1; |
ec9a943c CB |
132 | } |
133 | ||
134 | bios_virt = phys_to_virt(bios_phys); | |
135 | scan_end = bios_virt + bios_len - (alix_sig_len + 2); | |
136 | for (p = bios_virt; p < scan_end; p++) { | |
137 | const char *tail; | |
7f131cf3 | 138 | char *a; |
ec9a943c | 139 | |
7f131cf3 | 140 | if (memcmp(p, alix_sig, alix_sig_len) != 0) |
ec9a943c | 141 | continue; |
7f131cf3 DM |
142 | |
143 | memcpy(name, p, sizeof(name)); | |
144 | ||
145 | /* remove the first \0 character from string */ | |
146 | a = strchr(name, '\0'); | |
147 | if (a) | |
148 | *a = ' '; | |
149 | ||
150 | /* cut the string at a newline */ | |
151 | a = strchr(name, '\r'); | |
152 | if (a) | |
153 | *a = '\0'; | |
ec9a943c CB |
154 | |
155 | tail = p + alix_sig_len; | |
7f131cf3 | 156 | if ((tail[0] == '2' || tail[0] == '3')) { |
ec9a943c CB |
157 | printk(KERN_INFO |
158 | "%s: system is recognized as \"%s\"\n", | |
7f131cf3 DM |
159 | KBUILD_MODNAME, name); |
160 | return 1; | |
ec9a943c CB |
161 | } |
162 | } | |
163 | ||
7f131cf3 | 164 | return 0; |
ec9a943c CB |
165 | } |
166 | ||
167 | static struct platform_device *pdev; | |
168 | ||
7f131cf3 | 169 | static int __init alix_pci_led_init(void) |
ec9a943c | 170 | { |
7f131cf3 | 171 | u32 low, hi; |
ec9a943c | 172 | |
7f131cf3 DM |
173 | if (pci_dev_present(divil_pci) == 0) { |
174 | printk(KERN_WARNING KBUILD_MODNAME": DIVIL not found\n"); | |
175 | return -ENODEV; | |
ec9a943c CB |
176 | } |
177 | ||
7f131cf3 DM |
178 | /* Grab the GPIO I/O range */ |
179 | rdmsr(MSR_LBAR_GPIO, low, hi); | |
180 | ||
181 | /* Check the mask and whether GPIO is enabled (sanity check) */ | |
182 | if (hi != 0x0000f001) { | |
183 | printk(KERN_WARNING KBUILD_MODNAME": GPIO not enabled\n"); | |
184 | return -ENODEV; | |
185 | } | |
186 | ||
187 | /* Mask off the IO base address */ | |
188 | gpio_base = low & 0x0000ff00; | |
189 | ||
190 | if (!request_region(gpio_base, CS5535_GPIO_SIZE, KBUILD_MODNAME)) { | |
191 | printk(KERN_ERR KBUILD_MODNAME": can't allocate I/O for GPIO\n"); | |
192 | return -ENODEV; | |
193 | } | |
194 | ||
195 | /* Set GPIO function to output */ | |
196 | outl(1 << 6, gpio_base + 0x04); | |
197 | outl(1 << 9, gpio_base + 0x84); | |
198 | outl(1 << 11, gpio_base + 0x84); | |
199 | ||
200 | return 0; | |
201 | } | |
202 | ||
203 | static int __init alix_led_init(void) | |
204 | { | |
205 | int ret = -ENODEV; | |
206 | const char tinybios_sig[] = "PC Engines ALIX."; | |
207 | const char coreboot_sig[] = "PC Engines\0ALIX."; | |
208 | ||
209 | if (alix_present(0xf0000, tinybios_sig, sizeof(tinybios_sig) - 1) || | |
210 | alix_present(0x500, coreboot_sig, sizeof(coreboot_sig) - 1)) | |
211 | ret = alix_pci_led_init(); | |
212 | ||
213 | if (ret < 0) | |
214 | return ret; | |
2216c6e8 | 215 | |
ec9a943c CB |
216 | pdev = platform_device_register_simple(KBUILD_MODNAME, -1, NULL, 0); |
217 | if (!IS_ERR(pdev)) { | |
218 | ret = platform_driver_probe(&alix_led_driver, alix_led_probe); | |
219 | if (ret) | |
220 | platform_device_unregister(pdev); | |
221 | } else | |
222 | ret = PTR_ERR(pdev); | |
223 | ||
ec9a943c CB |
224 | return ret; |
225 | } | |
226 | ||
227 | static void __exit alix_led_exit(void) | |
228 | { | |
229 | platform_device_unregister(pdev); | |
230 | platform_driver_unregister(&alix_led_driver); | |
7f131cf3 | 231 | release_region(gpio_base, CS5535_GPIO_SIZE); |
ec9a943c CB |
232 | } |
233 | ||
234 | module_init(alix_led_init); | |
235 | module_exit(alix_led_exit); | |
236 | ||
237 | MODULE_AUTHOR("Constantin Baranov <[email protected]>"); | |
238 | MODULE_DESCRIPTION("PCEngines ALIX.2 and ALIX.3 LED driver"); | |
239 | MODULE_LICENSE("GPL"); |