Commit | Line | Data |
---|---|---|
d189164a GKH |
1 | /* |
2 | * Samsung N130 Laptop driver | |
3 | * | |
4 | * Copyright (C) 2009 Greg Kroah-Hartman (gregkh@suse.de) | |
5 | * Copyright (C) 2009 Novell Inc. | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or modify it | |
8 | * under the terms of the GNU General Public License version 2 as published by | |
9 | * the Free Software Foundation. | |
10 | * | |
11 | */ | |
d189164a GKH |
12 | #include <linux/kernel.h> |
13 | #include <linux/init.h> | |
14 | #include <linux/module.h> | |
15 | #include <linux/delay.h> | |
16 | #include <linux/pci.h> | |
17 | #include <linux/backlight.h> | |
18 | #include <linux/fb.h> | |
19 | #include <linux/dmi.h> | |
20 | #include <linux/platform_device.h> | |
21 | #include <linux/rfkill.h> | |
22 | ||
23 | /* | |
24 | * This driver is needed because a number of Samsung laptops do not hook | |
25 | * their control settings through ACPI. So we have to poke around in the | |
26 | * BIOS to do things like brightness values, and "special" key controls. | |
27 | */ | |
28 | ||
29 | /* | |
30 | * We have 0 - 8 as valid brightness levels. The specs say that level 0 should | |
31 | * be reserved by the BIOS (which really doesn't make much sense), we tell | |
32 | * userspace that the value is 0 - 7 and then just tell the hardware 1 - 8 | |
33 | */ | |
34 | #define MAX_BRIGHT 0x07 | |
35 | ||
d189164a GKH |
36 | |
37 | #define SABI_IFACE_MAIN 0x00 | |
38 | #define SABI_IFACE_SUB 0x02 | |
39 | #define SABI_IFACE_COMPLETE 0x04 | |
40 | #define SABI_IFACE_DATA 0x05 | |
41 | ||
42 | /* Structure to get data back to the calling function */ | |
43 | struct sabi_retval { | |
44 | u8 retval[20]; | |
45 | }; | |
46 | ||
988a29bf IS |
47 | struct sabi_header_offsets { |
48 | u8 port; | |
49 | u8 re_mem; | |
50 | u8 iface_func; | |
51 | u8 en_mem; | |
52 | u8 data_offset; | |
53 | u8 data_segment; | |
54 | }; | |
55 | ||
56 | struct sabi_commands { | |
55bb5f4b GKH |
57 | /* |
58 | * Brightness is 0 - 8, as described above. | |
59 | * Value 0 is for the BIOS to use | |
60 | */ | |
988a29bf IS |
61 | u8 get_brightness; |
62 | u8 set_brightness; | |
63 | ||
55bb5f4b GKH |
64 | /* |
65 | * first byte: | |
988a29bf IS |
66 | * 0x00 - wireless is off |
67 | * 0x01 - wireless is on | |
68 | * second byte: | |
69 | * 0x02 - 3G is off | |
70 | * 0x03 - 3G is on | |
71 | * TODO, verify 3G is correct, that doesn't seem right... | |
72 | */ | |
73 | u8 get_wireless_button; | |
74 | u8 set_wireless_button; | |
75 | ||
76 | /* 0 is off, 1 is on */ | |
77 | u8 get_backlight; | |
78 | u8 set_backlight; | |
79 | ||
80 | /* | |
81 | * 0x80 or 0x00 - no action | |
82 | * 0x81 - recovery key pressed | |
83 | */ | |
84 | u8 get_recovery_mode; | |
85 | u8 set_recovery_mode; | |
86 | ||
87 | /* | |
88 | * on seclinux: 0 is low, 1 is high, | |
89 | * on swsmi: 0 is normal, 1 is silent, 2 is turbo | |
90 | */ | |
91 | u8 get_performance_level; | |
92 | u8 set_performance_level; | |
93 | ||
94 | /* | |
95 | * Tell the BIOS that Linux is running on this machine. | |
96 | * 81 is on, 80 is off | |
97 | */ | |
98 | u8 set_linux; | |
99 | }; | |
100 | ||
101 | struct sabi_performance_level { | |
102 | const char *name; | |
103 | u8 value; | |
104 | }; | |
105 | ||
106 | struct sabi_config { | |
107 | const char *test_string; | |
108 | u16 main_function; | |
109 | struct sabi_header_offsets header_offsets; | |
110 | struct sabi_commands commands; | |
111 | struct sabi_performance_level performance_levels[4]; | |
112 | }; | |
113 | ||
114 | static struct sabi_config sabi_configs[] = { | |
115 | { | |
1540e350 | 116 | .test_string = "SECLINUX", |
988a29bf | 117 | |
1540e350 | 118 | .main_function = 0x4c59, |
27f55e92 | 119 | |
1540e350 GKH |
120 | .header_offsets = { |
121 | .port = 0x00, | |
122 | .re_mem = 0x02, | |
123 | .iface_func = 0x03, | |
124 | .en_mem = 0x04, | |
125 | .data_offset = 0x05, | |
126 | .data_segment = 0x07, | |
988a29bf IS |
127 | }, |
128 | ||
1540e350 GKH |
129 | .commands = { |
130 | .get_brightness = 0x00, | |
131 | .set_brightness = 0x01, | |
988a29bf | 132 | |
1540e350 GKH |
133 | .get_wireless_button = 0x02, |
134 | .set_wireless_button = 0x03, | |
988a29bf | 135 | |
1540e350 GKH |
136 | .get_backlight = 0x04, |
137 | .set_backlight = 0x05, | |
988a29bf | 138 | |
1540e350 GKH |
139 | .get_recovery_mode = 0x06, |
140 | .set_recovery_mode = 0x07, | |
988a29bf | 141 | |
1540e350 GKH |
142 | .get_performance_level = 0x08, |
143 | .set_performance_level = 0x09, | |
988a29bf | 144 | |
1540e350 | 145 | .set_linux = 0x0a, |
988a29bf IS |
146 | }, |
147 | ||
1540e350 | 148 | .performance_levels = { |
988a29bf | 149 | { |
1540e350 GKH |
150 | .name = "silent", |
151 | .value = 0, | |
988a29bf IS |
152 | }, |
153 | { | |
1540e350 GKH |
154 | .name = "normal", |
155 | .value = 1, | |
988a29bf IS |
156 | }, |
157 | { }, | |
158 | }, | |
159 | }, | |
160 | { | |
1540e350 | 161 | .test_string = "SwSmi@", |
988a29bf | 162 | |
1540e350 | 163 | .main_function = 0x5843, |
988a29bf | 164 | |
1540e350 GKH |
165 | .header_offsets = { |
166 | .port = 0x00, | |
167 | .re_mem = 0x04, | |
168 | .iface_func = 0x02, | |
169 | .en_mem = 0x03, | |
170 | .data_offset = 0x05, | |
171 | .data_segment = 0x07, | |
988a29bf IS |
172 | }, |
173 | ||
1540e350 GKH |
174 | .commands = { |
175 | .get_brightness = 0x10, | |
176 | .set_brightness = 0x11, | |
988a29bf | 177 | |
1540e350 GKH |
178 | .get_wireless_button = 0x12, |
179 | .set_wireless_button = 0x13, | |
988a29bf | 180 | |
1540e350 GKH |
181 | .get_backlight = 0x2d, |
182 | .set_backlight = 0x2e, | |
988a29bf | 183 | |
1540e350 GKH |
184 | .get_recovery_mode = 0xff, |
185 | .set_recovery_mode = 0xff, | |
988a29bf | 186 | |
1540e350 GKH |
187 | .get_performance_level = 0x31, |
188 | .set_performance_level = 0x32, | |
988a29bf | 189 | |
1540e350 | 190 | .set_linux = 0xff, |
988a29bf IS |
191 | }, |
192 | ||
1540e350 | 193 | .performance_levels = { |
988a29bf | 194 | { |
1540e350 GKH |
195 | .name = "normal", |
196 | .value = 0, | |
988a29bf IS |
197 | }, |
198 | { | |
1540e350 GKH |
199 | .name = "silent", |
200 | .value = 1, | |
988a29bf IS |
201 | }, |
202 | { | |
1540e350 GKH |
203 | .name = "overclock", |
204 | .value = 2, | |
988a29bf IS |
205 | }, |
206 | { }, | |
207 | }, | |
208 | }, | |
209 | { }, | |
210 | }; | |
211 | ||
212 | static struct sabi_config *sabi_config; | |
213 | ||
d189164a GKH |
214 | static void __iomem *sabi; |
215 | static void __iomem *sabi_iface; | |
216 | static void __iomem *f0000_segment; | |
217 | static struct backlight_device *backlight_device; | |
218 | static struct mutex sabi_mutex; | |
219 | static struct platform_device *sdev; | |
220 | static struct rfkill *rfk; | |
221 | ||
222 | static int force; | |
223 | module_param(force, bool, 0); | |
d3c796fd CC |
224 | MODULE_PARM_DESC(force, |
225 | "Disable the DMI check and forces the driver to be loaded"); | |
d189164a GKH |
226 | |
227 | static int debug; | |
228 | module_param(debug, bool, S_IRUGO | S_IWUSR); | |
229 | MODULE_PARM_DESC(debug, "Debug enabled or not"); | |
230 | ||
231 | static int sabi_get_command(u8 command, struct sabi_retval *sretval) | |
232 | { | |
233 | int retval = 0; | |
988a29bf | 234 | u16 port = readw(sabi + sabi_config->header_offsets.port); |
d189164a GKH |
235 | |
236 | mutex_lock(&sabi_mutex); | |
237 | ||
238 | /* enable memory to be able to write to it */ | |
988a29bf | 239 | outb(readb(sabi + sabi_config->header_offsets.en_mem), port); |
d189164a GKH |
240 | |
241 | /* write out the command */ | |
988a29bf | 242 | writew(sabi_config->main_function, sabi_iface + SABI_IFACE_MAIN); |
d189164a GKH |
243 | writew(command, sabi_iface + SABI_IFACE_SUB); |
244 | writeb(0, sabi_iface + SABI_IFACE_COMPLETE); | |
988a29bf | 245 | outb(readb(sabi + sabi_config->header_offsets.iface_func), port); |
d189164a GKH |
246 | |
247 | /* write protect memory to make it safe */ | |
988a29bf | 248 | outb(readb(sabi + sabi_config->header_offsets.re_mem), port); |
d189164a GKH |
249 | |
250 | /* see if the command actually succeeded */ | |
251 | if (readb(sabi_iface + SABI_IFACE_COMPLETE) == 0xaa && | |
252 | readb(sabi_iface + SABI_IFACE_DATA) != 0xff) { | |
253 | /* | |
254 | * It did! | |
255 | * Save off the data into a structure so the caller use it. | |
256 | * Right now we only care about the first 4 bytes, | |
257 | * I suppose there are commands that need more, but I don't | |
258 | * know about them. | |
259 | */ | |
260 | sretval->retval[0] = readb(sabi_iface + SABI_IFACE_DATA); | |
261 | sretval->retval[1] = readb(sabi_iface + SABI_IFACE_DATA + 1); | |
262 | sretval->retval[2] = readb(sabi_iface + SABI_IFACE_DATA + 2); | |
263 | sretval->retval[3] = readb(sabi_iface + SABI_IFACE_DATA + 3); | |
264 | goto exit; | |
265 | } | |
266 | ||
267 | /* Something bad happened, so report it and error out */ | |
268 | printk(KERN_WARNING "SABI command 0x%02x failed with completion flag 0x%02x and output 0x%02x\n", | |
269 | command, readb(sabi_iface + SABI_IFACE_COMPLETE), | |
270 | readb(sabi_iface + SABI_IFACE_DATA)); | |
271 | retval = -EINVAL; | |
272 | exit: | |
273 | mutex_unlock(&sabi_mutex); | |
274 | return retval; | |
275 | ||
276 | } | |
277 | ||
278 | static int sabi_set_command(u8 command, u8 data) | |
279 | { | |
280 | int retval = 0; | |
988a29bf | 281 | u16 port = readw(sabi + sabi_config->header_offsets.port); |
d189164a GKH |
282 | |
283 | mutex_lock(&sabi_mutex); | |
284 | ||
285 | /* enable memory to be able to write to it */ | |
988a29bf | 286 | outb(readb(sabi + sabi_config->header_offsets.en_mem), port); |
d189164a GKH |
287 | |
288 | /* write out the command */ | |
988a29bf | 289 | writew(sabi_config->main_function, sabi_iface + SABI_IFACE_MAIN); |
d189164a GKH |
290 | writew(command, sabi_iface + SABI_IFACE_SUB); |
291 | writeb(0, sabi_iface + SABI_IFACE_COMPLETE); | |
292 | writeb(data, sabi_iface + SABI_IFACE_DATA); | |
988a29bf | 293 | outb(readb(sabi + sabi_config->header_offsets.iface_func), port); |
d189164a GKH |
294 | |
295 | /* write protect memory to make it safe */ | |
988a29bf | 296 | outb(readb(sabi + sabi_config->header_offsets.re_mem), port); |
d189164a GKH |
297 | |
298 | /* see if the command actually succeeded */ | |
299 | if (readb(sabi_iface + SABI_IFACE_COMPLETE) == 0xaa && | |
300 | readb(sabi_iface + SABI_IFACE_DATA) != 0xff) { | |
301 | /* it did! */ | |
302 | goto exit; | |
303 | } | |
304 | ||
305 | /* Something bad happened, so report it and error out */ | |
306 | printk(KERN_WARNING "SABI command 0x%02x failed with completion flag 0x%02x and output 0x%02x\n", | |
307 | command, readb(sabi_iface + SABI_IFACE_COMPLETE), | |
308 | readb(sabi_iface + SABI_IFACE_DATA)); | |
309 | retval = -EINVAL; | |
310 | exit: | |
311 | mutex_unlock(&sabi_mutex); | |
312 | return retval; | |
313 | } | |
314 | ||
315 | static void test_backlight(void) | |
316 | { | |
317 | struct sabi_retval sretval; | |
318 | ||
988a29bf | 319 | sabi_get_command(sabi_config->commands.get_backlight, &sretval); |
d189164a GKH |
320 | printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]); |
321 | ||
988a29bf | 322 | sabi_set_command(sabi_config->commands.set_backlight, 0); |
d189164a GKH |
323 | printk(KERN_DEBUG "backlight should be off\n"); |
324 | ||
988a29bf | 325 | sabi_get_command(sabi_config->commands.get_backlight, &sretval); |
d189164a GKH |
326 | printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]); |
327 | ||
328 | msleep(1000); | |
329 | ||
988a29bf | 330 | sabi_set_command(sabi_config->commands.set_backlight, 1); |
d189164a GKH |
331 | printk(KERN_DEBUG "backlight should be on\n"); |
332 | ||
988a29bf | 333 | sabi_get_command(sabi_config->commands.get_backlight, &sretval); |
d189164a GKH |
334 | printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]); |
335 | } | |
336 | ||
337 | static void test_wireless(void) | |
338 | { | |
339 | struct sabi_retval sretval; | |
340 | ||
988a29bf | 341 | sabi_get_command(sabi_config->commands.get_wireless_button, &sretval); |
d189164a GKH |
342 | printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]); |
343 | ||
988a29bf | 344 | sabi_set_command(sabi_config->commands.set_wireless_button, 0); |
d189164a GKH |
345 | printk(KERN_DEBUG "wireless led should be off\n"); |
346 | ||
988a29bf | 347 | sabi_get_command(sabi_config->commands.get_wireless_button, &sretval); |
d189164a GKH |
348 | printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]); |
349 | ||
350 | msleep(1000); | |
351 | ||
988a29bf | 352 | sabi_set_command(sabi_config->commands.set_wireless_button, 1); |
d189164a GKH |
353 | printk(KERN_DEBUG "wireless led should be on\n"); |
354 | ||
988a29bf | 355 | sabi_get_command(sabi_config->commands.get_wireless_button, &sretval); |
d189164a GKH |
356 | printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]); |
357 | } | |
358 | ||
359 | static u8 read_brightness(void) | |
360 | { | |
361 | struct sabi_retval sretval; | |
362 | int user_brightness = 0; | |
363 | int retval; | |
364 | ||
55bb5f4b GKH |
365 | retval = sabi_get_command(sabi_config->commands.get_brightness, |
366 | &sretval); | |
d189164a GKH |
367 | if (!retval) |
368 | user_brightness = sretval.retval[0]; | |
369 | if (user_brightness != 0) | |
370 | --user_brightness; | |
371 | return user_brightness; | |
372 | } | |
373 | ||
374 | static void set_brightness(u8 user_brightness) | |
375 | { | |
55bb5f4b GKH |
376 | sabi_set_command(sabi_config->commands.set_brightness, |
377 | user_brightness + 1); | |
d189164a GKH |
378 | } |
379 | ||
380 | static int get_brightness(struct backlight_device *bd) | |
381 | { | |
382 | return (int)read_brightness(); | |
383 | } | |
384 | ||
385 | static int update_status(struct backlight_device *bd) | |
386 | { | |
387 | set_brightness(bd->props.brightness); | |
388 | ||
389 | if (bd->props.power == FB_BLANK_UNBLANK) | |
988a29bf | 390 | sabi_set_command(sabi_config->commands.set_backlight, 1); |
d189164a | 391 | else |
988a29bf | 392 | sabi_set_command(sabi_config->commands.set_backlight, 0); |
d189164a GKH |
393 | return 0; |
394 | } | |
395 | ||
acc2472e | 396 | static const struct backlight_ops backlight_ops = { |
d189164a GKH |
397 | .get_brightness = get_brightness, |
398 | .update_status = update_status, | |
399 | }; | |
400 | ||
d189164a GKH |
401 | static int rfkill_set(void *data, bool blocked) |
402 | { | |
403 | /* Do something with blocked...*/ | |
404 | /* | |
405 | * blocked == false is on | |
406 | * blocked == true is off | |
407 | */ | |
408 | if (blocked) | |
988a29bf | 409 | sabi_set_command(sabi_config->commands.set_wireless_button, 0); |
d189164a | 410 | else |
988a29bf | 411 | sabi_set_command(sabi_config->commands.set_wireless_button, 1); |
d189164a GKH |
412 | |
413 | return 0; | |
414 | } | |
415 | ||
416 | static struct rfkill_ops rfkill_ops = { | |
417 | .set_block = rfkill_set, | |
418 | }; | |
419 | ||
420 | static int init_wireless(struct platform_device *sdev) | |
421 | { | |
422 | int retval; | |
423 | ||
424 | rfk = rfkill_alloc("samsung-wifi", &sdev->dev, RFKILL_TYPE_WLAN, | |
425 | &rfkill_ops, NULL); | |
426 | if (!rfk) | |
427 | return -ENOMEM; | |
428 | ||
429 | retval = rfkill_register(rfk); | |
430 | if (retval) { | |
431 | rfkill_destroy(rfk); | |
432 | return -ENODEV; | |
433 | } | |
434 | ||
435 | return 0; | |
436 | } | |
437 | ||
438 | static void destroy_wireless(void) | |
439 | { | |
440 | rfkill_unregister(rfk); | |
441 | rfkill_destroy(rfk); | |
442 | } | |
443 | ||
988a29bf IS |
444 | static ssize_t get_performance_level(struct device *dev, |
445 | struct device_attribute *attr, char *buf) | |
d189164a GKH |
446 | { |
447 | struct sabi_retval sretval; | |
448 | int retval; | |
5bf8b724 | 449 | int i; |
d189164a GKH |
450 | |
451 | /* Read the state */ | |
55bb5f4b GKH |
452 | retval = sabi_get_command(sabi_config->commands.get_performance_level, |
453 | &sretval); | |
d189164a GKH |
454 | if (retval) |
455 | return retval; | |
456 | ||
457 | /* The logic is backwards, yeah, lots of fun... */ | |
5bf8b724 GKH |
458 | for (i = 0; sabi_config->performance_levels[i].name; ++i) { |
459 | if (sretval.retval[0] == sabi_config->performance_levels[i].value) | |
460 | return sprintf(buf, "%s\n", sabi_config->performance_levels[i].name); | |
988a29bf IS |
461 | } |
462 | return sprintf(buf, "%s\n", "unknown"); | |
d189164a GKH |
463 | } |
464 | ||
988a29bf | 465 | static ssize_t set_performance_level(struct device *dev, |
d189164a GKH |
466 | struct device_attribute *attr, const char *buf, |
467 | size_t count) | |
468 | { | |
d189164a | 469 | if (count >= 1) { |
5bf8b724 GKH |
470 | int i; |
471 | for (i = 0; sabi_config->performance_levels[i].name; ++i) { | |
988a29bf | 472 | struct sabi_performance_level *level = |
5bf8b724 | 473 | &sabi_config->performance_levels[i]; |
3e55eb37 | 474 | if (!strncasecmp(level->name, buf, strlen(level->name))) { |
988a29bf | 475 | sabi_set_command(sabi_config->commands.set_performance_level, |
55bb5f4b | 476 | level->value); |
988a29bf IS |
477 | break; |
478 | } | |
d189164a | 479 | } |
5bf8b724 | 480 | if (!sabi_config->performance_levels[i].name) |
988a29bf | 481 | return -EINVAL; |
d189164a GKH |
482 | } |
483 | return count; | |
484 | } | |
988a29bf IS |
485 | static DEVICE_ATTR(performance_level, S_IWUSR | S_IRUGO, |
486 | get_performance_level, set_performance_level); | |
d189164a GKH |
487 | |
488 | ||
489 | static int __init dmi_check_cb(const struct dmi_system_id *id) | |
490 | { | |
491 | printk(KERN_INFO KBUILD_MODNAME ": found laptop model '%s'\n", | |
492 | id->ident); | |
493 | return 0; | |
494 | } | |
495 | ||
496 | static struct dmi_system_id __initdata samsung_dmi_table[] = { | |
497 | { | |
498 | .ident = "N128", | |
499 | .matches = { | |
d3c796fd CC |
500 | DMI_MATCH(DMI_SYS_VENDOR, |
501 | "SAMSUNG ELECTRONICS CO., LTD."), | |
d189164a GKH |
502 | DMI_MATCH(DMI_PRODUCT_NAME, "N128"), |
503 | DMI_MATCH(DMI_BOARD_NAME, "N128"), | |
504 | }, | |
505 | .callback = dmi_check_cb, | |
506 | }, | |
507 | { | |
508 | .ident = "N130", | |
509 | .matches = { | |
d3c796fd CC |
510 | DMI_MATCH(DMI_SYS_VENDOR, |
511 | "SAMSUNG ELECTRONICS CO., LTD."), | |
d189164a GKH |
512 | DMI_MATCH(DMI_PRODUCT_NAME, "N130"), |
513 | DMI_MATCH(DMI_BOARD_NAME, "N130"), | |
514 | }, | |
515 | .callback = dmi_check_cb, | |
516 | }, | |
837b03cd IS |
517 | { |
518 | .ident = "X125", | |
519 | .matches = { | |
520 | DMI_MATCH(DMI_SYS_VENDOR, | |
521 | "SAMSUNG ELECTRONICS CO., LTD."), | |
522 | DMI_MATCH(DMI_PRODUCT_NAME, "X125"), | |
523 | DMI_MATCH(DMI_BOARD_NAME, "X125"), | |
524 | }, | |
525 | .callback = dmi_check_cb, | |
526 | }, | |
d3c29116 GKH |
527 | { |
528 | .ident = "NC10", | |
529 | .matches = { | |
530 | DMI_MATCH(DMI_SYS_VENDOR, | |
531 | "SAMSUNG ELECTRONICS CO., LTD."), | |
532 | DMI_MATCH(DMI_PRODUCT_NAME, "NC10"), | |
533 | DMI_MATCH(DMI_BOARD_NAME, "NC10"), | |
534 | }, | |
535 | .callback = dmi_check_cb, | |
536 | }, | |
78a2fcb4 GKH |
537 | { |
538 | .ident = "NP-Q45", | |
539 | .matches = { | |
540 | DMI_MATCH(DMI_SYS_VENDOR, | |
541 | "SAMSUNG ELECTRONICS CO., LTD."), | |
542 | DMI_MATCH(DMI_PRODUCT_NAME, "SQ45S70S"), | |
543 | DMI_MATCH(DMI_BOARD_NAME, "SQ45S70S"), | |
544 | }, | |
545 | .callback = dmi_check_cb, | |
546 | }, | |
d189164a GKH |
547 | { }, |
548 | }; | |
549 | MODULE_DEVICE_TABLE(dmi, samsung_dmi_table); | |
550 | ||
988a29bf IS |
551 | static int find_signature(void __iomem *memcheck, const char *testStr) |
552 | { | |
5bf8b724 | 553 | int i = 0; |
988a29bf | 554 | int loca; |
5bf8b724 | 555 | |
988a29bf IS |
556 | for (loca = 0; loca < 0xffff; loca++) { |
557 | char temp = readb(memcheck + loca); | |
558 | ||
5bf8b724 GKH |
559 | if (temp == testStr[i]) { |
560 | if (i == strlen(testStr)-1) | |
988a29bf | 561 | break; |
5bf8b724 | 562 | ++i; |
988a29bf | 563 | } else { |
5bf8b724 | 564 | i = 0; |
988a29bf IS |
565 | } |
566 | } | |
567 | return loca; | |
568 | } | |
569 | ||
d189164a GKH |
570 | static int __init samsung_init(void) |
571 | { | |
a19a6ee6 | 572 | struct backlight_properties props; |
d189164a | 573 | struct sabi_retval sretval; |
d189164a | 574 | unsigned int ifaceP; |
5bf8b724 | 575 | int i; |
d189164a GKH |
576 | int loca; |
577 | int retval; | |
578 | ||
579 | mutex_init(&sabi_mutex); | |
580 | ||
581 | if (!force && !dmi_check_system(samsung_dmi_table)) | |
582 | return -ENODEV; | |
583 | ||
584 | f0000_segment = ioremap(0xf0000, 0xffff); | |
585 | if (!f0000_segment) { | |
586 | printk(KERN_ERR "Can't map the segment at 0xf0000\n"); | |
587 | return -EINVAL; | |
588 | } | |
589 | ||
988a29bf | 590 | /* Try to find one of the signatures in memory to find the header */ |
5bf8b724 GKH |
591 | for (i = 0; sabi_configs[i].test_string != 0; ++i) { |
592 | sabi_config = &sabi_configs[i]; | |
988a29bf IS |
593 | loca = find_signature(f0000_segment, sabi_config->test_string); |
594 | if (loca != 0xffff) | |
595 | break; | |
d189164a | 596 | } |
988a29bf | 597 | |
d189164a GKH |
598 | if (loca == 0xffff) { |
599 | printk(KERN_ERR "This computer does not support SABI\n"); | |
600 | goto error_no_signature; | |
988a29bf | 601 | } |
d189164a GKH |
602 | |
603 | /* point to the SMI port Number */ | |
604 | loca += 1; | |
988a29bf | 605 | sabi = (f0000_segment + loca); |
d189164a GKH |
606 | |
607 | if (debug) { | |
608 | printk(KERN_DEBUG "This computer supports SABI==%x\n", | |
609 | loca + 0xf0000 - 6); | |
610 | printk(KERN_DEBUG "SABI header:\n"); | |
611 | printk(KERN_DEBUG " SMI Port Number = 0x%04x\n", | |
988a29bf | 612 | readw(sabi + sabi_config->header_offsets.port)); |
d189164a | 613 | printk(KERN_DEBUG " SMI Interface Function = 0x%02x\n", |
988a29bf | 614 | readb(sabi + sabi_config->header_offsets.iface_func)); |
d189164a | 615 | printk(KERN_DEBUG " SMI enable memory buffer = 0x%02x\n", |
988a29bf | 616 | readb(sabi + sabi_config->header_offsets.en_mem)); |
d189164a | 617 | printk(KERN_DEBUG " SMI restore memory buffer = 0x%02x\n", |
988a29bf | 618 | readb(sabi + sabi_config->header_offsets.re_mem)); |
d189164a | 619 | printk(KERN_DEBUG " SABI data offset = 0x%04x\n", |
988a29bf | 620 | readw(sabi + sabi_config->header_offsets.data_offset)); |
d189164a | 621 | printk(KERN_DEBUG " SABI data segment = 0x%04x\n", |
988a29bf | 622 | readw(sabi + sabi_config->header_offsets.data_segment)); |
d189164a GKH |
623 | } |
624 | ||
625 | /* Get a pointer to the SABI Interface */ | |
988a29bf IS |
626 | ifaceP = (readw(sabi + sabi_config->header_offsets.data_segment) & 0x0ffff) << 4; |
627 | ifaceP += readw(sabi + sabi_config->header_offsets.data_offset) & 0x0ffff; | |
d189164a GKH |
628 | sabi_iface = ioremap(ifaceP, 16); |
629 | if (!sabi_iface) { | |
630 | printk(KERN_ERR "Can't remap %x\n", ifaceP); | |
631 | goto exit; | |
632 | } | |
633 | if (debug) { | |
634 | printk(KERN_DEBUG "ifaceP = 0x%08x\n", ifaceP); | |
635 | printk(KERN_DEBUG "sabi_iface = %p\n", sabi_iface); | |
636 | ||
637 | test_backlight(); | |
638 | test_wireless(); | |
639 | ||
55bb5f4b GKH |
640 | retval = sabi_get_command(sabi_config->commands.get_brightness, |
641 | &sretval); | |
d189164a GKH |
642 | printk(KERN_DEBUG "brightness = 0x%02x\n", sretval.retval[0]); |
643 | } | |
644 | ||
645 | /* Turn on "Linux" mode in the BIOS */ | |
3e55eb37 | 646 | if (sabi_config->commands.set_linux != 0xff) { |
55bb5f4b GKH |
647 | retval = sabi_set_command(sabi_config->commands.set_linux, |
648 | 0x81); | |
988a29bf IS |
649 | if (retval) { |
650 | printk(KERN_ERR KBUILD_MODNAME ": Linux mode was not set!\n"); | |
651 | goto error_no_platform; | |
652 | } | |
d189164a GKH |
653 | } |
654 | ||
655 | /* knock up a platform device to hang stuff off of */ | |
656 | sdev = platform_device_register_simple("samsung", -1, NULL, 0); | |
657 | if (IS_ERR(sdev)) | |
658 | goto error_no_platform; | |
659 | ||
660 | /* create a backlight device to talk to this one */ | |
a19a6ee6 MG |
661 | memset(&props, 0, sizeof(struct backlight_properties)); |
662 | props.max_brightness = MAX_BRIGHT; | |
d189164a | 663 | backlight_device = backlight_device_register("samsung", &sdev->dev, |
a19a6ee6 MG |
664 | NULL, &backlight_ops, |
665 | &props); | |
d189164a GKH |
666 | if (IS_ERR(backlight_device)) |
667 | goto error_no_backlight; | |
668 | ||
d189164a GKH |
669 | backlight_device->props.brightness = read_brightness(); |
670 | backlight_device->props.power = FB_BLANK_UNBLANK; | |
671 | backlight_update_status(backlight_device); | |
672 | ||
673 | retval = init_wireless(sdev); | |
674 | if (retval) | |
675 | goto error_no_rfk; | |
676 | ||
988a29bf | 677 | retval = device_create_file(&sdev->dev, &dev_attr_performance_level); |
d189164a GKH |
678 | if (retval) |
679 | goto error_file_create; | |
680 | ||
681 | exit: | |
682 | return 0; | |
683 | ||
684 | error_file_create: | |
685 | destroy_wireless(); | |
686 | ||
687 | error_no_rfk: | |
688 | backlight_device_unregister(backlight_device); | |
689 | ||
690 | error_no_backlight: | |
691 | platform_device_unregister(sdev); | |
692 | ||
693 | error_no_platform: | |
694 | iounmap(sabi_iface); | |
695 | ||
696 | error_no_signature: | |
697 | iounmap(f0000_segment); | |
698 | return -EINVAL; | |
699 | } | |
700 | ||
701 | static void __exit samsung_exit(void) | |
702 | { | |
703 | /* Turn off "Linux" mode in the BIOS */ | |
988a29bf IS |
704 | if (sabi_config->commands.set_linux != 0xff) |
705 | sabi_set_command(sabi_config->commands.set_linux, 0x80); | |
d189164a | 706 | |
988a29bf | 707 | device_remove_file(&sdev->dev, &dev_attr_performance_level); |
d189164a GKH |
708 | backlight_device_unregister(backlight_device); |
709 | destroy_wireless(); | |
710 | iounmap(sabi_iface); | |
711 | iounmap(f0000_segment); | |
712 | platform_device_unregister(sdev); | |
713 | } | |
714 | ||
715 | module_init(samsung_init); | |
716 | module_exit(samsung_exit); | |
717 | ||
718 | MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@suse.de>"); | |
719 | MODULE_DESCRIPTION("Samsung Backlight driver"); | |
720 | MODULE_LICENSE("GPL"); |