]>
Commit | Line | Data |
---|---|---|
2d70b73a GKH |
1 | /* |
2 | * Samsung Laptop driver | |
3 | * | |
4 | * Copyright (C) 2009,2011 Greg Kroah-Hartman (gregkh@suse.de) | |
5 | * Copyright (C) 2009,2011 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 | */ | |
12 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | |
13 | ||
14 | #include <linux/kernel.h> | |
15 | #include <linux/init.h> | |
16 | #include <linux/module.h> | |
17 | #include <linux/delay.h> | |
18 | #include <linux/pci.h> | |
19 | #include <linux/backlight.h> | |
20 | #include <linux/fb.h> | |
21 | #include <linux/dmi.h> | |
22 | #include <linux/platform_device.h> | |
23 | #include <linux/rfkill.h> | |
24 | ||
25 | /* | |
26 | * This driver is needed because a number of Samsung laptops do not hook | |
27 | * their control settings through ACPI. So we have to poke around in the | |
28 | * BIOS to do things like brightness values, and "special" key controls. | |
29 | */ | |
30 | ||
31 | /* | |
32 | * We have 0 - 8 as valid brightness levels. The specs say that level 0 should | |
33 | * be reserved by the BIOS (which really doesn't make much sense), we tell | |
34 | * userspace that the value is 0 - 7 and then just tell the hardware 1 - 8 | |
35 | */ | |
36 | #define MAX_BRIGHT 0x07 | |
37 | ||
38 | ||
39 | #define SABI_IFACE_MAIN 0x00 | |
40 | #define SABI_IFACE_SUB 0x02 | |
41 | #define SABI_IFACE_COMPLETE 0x04 | |
42 | #define SABI_IFACE_DATA 0x05 | |
43 | ||
44 | /* Structure to get data back to the calling function */ | |
45 | struct sabi_retval { | |
46 | u8 retval[20]; | |
47 | }; | |
48 | ||
49 | struct sabi_header_offsets { | |
50 | u8 port; | |
51 | u8 re_mem; | |
52 | u8 iface_func; | |
53 | u8 en_mem; | |
54 | u8 data_offset; | |
55 | u8 data_segment; | |
56 | }; | |
57 | ||
58 | struct sabi_commands { | |
59 | /* | |
60 | * Brightness is 0 - 8, as described above. | |
61 | * Value 0 is for the BIOS to use | |
62 | */ | |
63 | u8 get_brightness; | |
64 | u8 set_brightness; | |
65 | ||
66 | /* | |
67 | * first byte: | |
68 | * 0x00 - wireless is off | |
69 | * 0x01 - wireless is on | |
70 | * second byte: | |
71 | * 0x02 - 3G is off | |
72 | * 0x03 - 3G is on | |
73 | * TODO, verify 3G is correct, that doesn't seem right... | |
74 | */ | |
75 | u8 get_wireless_button; | |
76 | u8 set_wireless_button; | |
77 | ||
78 | /* 0 is off, 1 is on */ | |
79 | u8 get_backlight; | |
80 | u8 set_backlight; | |
81 | ||
82 | /* | |
83 | * 0x80 or 0x00 - no action | |
84 | * 0x81 - recovery key pressed | |
85 | */ | |
86 | u8 get_recovery_mode; | |
87 | u8 set_recovery_mode; | |
88 | ||
89 | /* | |
90 | * on seclinux: 0 is low, 1 is high, | |
91 | * on swsmi: 0 is normal, 1 is silent, 2 is turbo | |
92 | */ | |
93 | u8 get_performance_level; | |
94 | u8 set_performance_level; | |
95 | ||
96 | /* | |
97 | * Tell the BIOS that Linux is running on this machine. | |
98 | * 81 is on, 80 is off | |
99 | */ | |
100 | u8 set_linux; | |
101 | }; | |
102 | ||
103 | struct sabi_performance_level { | |
104 | const char *name; | |
105 | u8 value; | |
106 | }; | |
107 | ||
108 | struct sabi_config { | |
109 | const char *test_string; | |
110 | u16 main_function; | |
111 | const struct sabi_header_offsets header_offsets; | |
112 | const struct sabi_commands commands; | |
113 | const struct sabi_performance_level performance_levels[4]; | |
114 | u8 min_brightness; | |
115 | u8 max_brightness; | |
116 | }; | |
117 | ||
118 | static const struct sabi_config sabi_configs[] = { | |
119 | { | |
120 | .test_string = "SECLINUX", | |
121 | ||
122 | .main_function = 0x4c49, | |
123 | ||
124 | .header_offsets = { | |
125 | .port = 0x00, | |
126 | .re_mem = 0x02, | |
127 | .iface_func = 0x03, | |
128 | .en_mem = 0x04, | |
129 | .data_offset = 0x05, | |
130 | .data_segment = 0x07, | |
131 | }, | |
132 | ||
133 | .commands = { | |
134 | .get_brightness = 0x00, | |
135 | .set_brightness = 0x01, | |
136 | ||
137 | .get_wireless_button = 0x02, | |
138 | .set_wireless_button = 0x03, | |
139 | ||
140 | .get_backlight = 0x04, | |
141 | .set_backlight = 0x05, | |
142 | ||
143 | .get_recovery_mode = 0x06, | |
144 | .set_recovery_mode = 0x07, | |
145 | ||
146 | .get_performance_level = 0x08, | |
147 | .set_performance_level = 0x09, | |
148 | ||
149 | .set_linux = 0x0a, | |
150 | }, | |
151 | ||
152 | .performance_levels = { | |
153 | { | |
154 | .name = "silent", | |
155 | .value = 0, | |
156 | }, | |
157 | { | |
158 | .name = "normal", | |
159 | .value = 1, | |
160 | }, | |
161 | { }, | |
162 | }, | |
163 | .min_brightness = 1, | |
164 | .max_brightness = 8, | |
165 | }, | |
166 | { | |
167 | .test_string = "SwSmi@", | |
168 | ||
169 | .main_function = 0x5843, | |
170 | ||
171 | .header_offsets = { | |
172 | .port = 0x00, | |
173 | .re_mem = 0x04, | |
174 | .iface_func = 0x02, | |
175 | .en_mem = 0x03, | |
176 | .data_offset = 0x05, | |
177 | .data_segment = 0x07, | |
178 | }, | |
179 | ||
180 | .commands = { | |
181 | .get_brightness = 0x10, | |
182 | .set_brightness = 0x11, | |
183 | ||
184 | .get_wireless_button = 0x12, | |
185 | .set_wireless_button = 0x13, | |
186 | ||
187 | .get_backlight = 0x2d, | |
188 | .set_backlight = 0x2e, | |
189 | ||
190 | .get_recovery_mode = 0xff, | |
191 | .set_recovery_mode = 0xff, | |
192 | ||
193 | .get_performance_level = 0x31, | |
194 | .set_performance_level = 0x32, | |
195 | ||
196 | .set_linux = 0xff, | |
197 | }, | |
198 | ||
199 | .performance_levels = { | |
200 | { | |
201 | .name = "normal", | |
202 | .value = 0, | |
203 | }, | |
204 | { | |
205 | .name = "silent", | |
206 | .value = 1, | |
207 | }, | |
208 | { | |
209 | .name = "overclock", | |
210 | .value = 2, | |
211 | }, | |
212 | { }, | |
213 | }, | |
214 | .min_brightness = 0, | |
215 | .max_brightness = 8, | |
216 | }, | |
217 | { }, | |
218 | }; | |
219 | ||
220 | static const struct sabi_config *sabi_config; | |
221 | ||
222 | static void __iomem *sabi; | |
223 | static void __iomem *sabi_iface; | |
224 | static void __iomem *f0000_segment; | |
225 | static struct backlight_device *backlight_device; | |
226 | static struct mutex sabi_mutex; | |
227 | static struct platform_device *sdev; | |
228 | static struct rfkill *rfk; | |
ac080523 | 229 | static bool has_stepping_quirk; |
2d70b73a | 230 | |
90ab5ee9 | 231 | static bool force; |
2d70b73a GKH |
232 | module_param(force, bool, 0); |
233 | MODULE_PARM_DESC(force, | |
234 | "Disable the DMI check and forces the driver to be loaded"); | |
235 | ||
90ab5ee9 | 236 | static bool debug; |
2d70b73a GKH |
237 | module_param(debug, bool, S_IRUGO | S_IWUSR); |
238 | MODULE_PARM_DESC(debug, "Debug enabled or not"); | |
239 | ||
240 | static int sabi_get_command(u8 command, struct sabi_retval *sretval) | |
241 | { | |
242 | int retval = 0; | |
243 | u16 port = readw(sabi + sabi_config->header_offsets.port); | |
244 | u8 complete, iface_data; | |
245 | ||
246 | mutex_lock(&sabi_mutex); | |
247 | ||
248 | /* enable memory to be able to write to it */ | |
249 | outb(readb(sabi + sabi_config->header_offsets.en_mem), port); | |
250 | ||
251 | /* write out the command */ | |
252 | writew(sabi_config->main_function, sabi_iface + SABI_IFACE_MAIN); | |
253 | writew(command, sabi_iface + SABI_IFACE_SUB); | |
254 | writeb(0, sabi_iface + SABI_IFACE_COMPLETE); | |
255 | outb(readb(sabi + sabi_config->header_offsets.iface_func), port); | |
256 | ||
257 | /* write protect memory to make it safe */ | |
258 | outb(readb(sabi + sabi_config->header_offsets.re_mem), port); | |
259 | ||
260 | /* see if the command actually succeeded */ | |
261 | complete = readb(sabi_iface + SABI_IFACE_COMPLETE); | |
262 | iface_data = readb(sabi_iface + SABI_IFACE_DATA); | |
263 | if (complete != 0xaa || iface_data == 0xff) { | |
264 | pr_warn("SABI get command 0x%02x failed with completion flag 0x%02x and data 0x%02x\n", | |
265 | command, complete, iface_data); | |
266 | retval = -EINVAL; | |
267 | goto exit; | |
268 | } | |
269 | /* | |
270 | * Save off the data into a structure so the caller use it. | |
271 | * Right now we only want the first 4 bytes, | |
272 | * There are commands that need more, but not for the ones we | |
273 | * currently care about. | |
274 | */ | |
275 | sretval->retval[0] = readb(sabi_iface + SABI_IFACE_DATA); | |
276 | sretval->retval[1] = readb(sabi_iface + SABI_IFACE_DATA + 1); | |
277 | sretval->retval[2] = readb(sabi_iface + SABI_IFACE_DATA + 2); | |
278 | sretval->retval[3] = readb(sabi_iface + SABI_IFACE_DATA + 3); | |
279 | ||
280 | exit: | |
281 | mutex_unlock(&sabi_mutex); | |
282 | return retval; | |
283 | ||
284 | } | |
285 | ||
286 | static int sabi_set_command(u8 command, u8 data) | |
287 | { | |
288 | int retval = 0; | |
289 | u16 port = readw(sabi + sabi_config->header_offsets.port); | |
290 | u8 complete, iface_data; | |
291 | ||
292 | mutex_lock(&sabi_mutex); | |
293 | ||
294 | /* enable memory to be able to write to it */ | |
295 | outb(readb(sabi + sabi_config->header_offsets.en_mem), port); | |
296 | ||
297 | /* write out the command */ | |
298 | writew(sabi_config->main_function, sabi_iface + SABI_IFACE_MAIN); | |
299 | writew(command, sabi_iface + SABI_IFACE_SUB); | |
300 | writeb(0, sabi_iface + SABI_IFACE_COMPLETE); | |
301 | writeb(data, sabi_iface + SABI_IFACE_DATA); | |
302 | outb(readb(sabi + sabi_config->header_offsets.iface_func), port); | |
303 | ||
304 | /* write protect memory to make it safe */ | |
305 | outb(readb(sabi + sabi_config->header_offsets.re_mem), port); | |
306 | ||
307 | /* see if the command actually succeeded */ | |
308 | complete = readb(sabi_iface + SABI_IFACE_COMPLETE); | |
309 | iface_data = readb(sabi_iface + SABI_IFACE_DATA); | |
310 | if (complete != 0xaa || iface_data == 0xff) { | |
311 | pr_warn("SABI set command 0x%02x failed with completion flag 0x%02x and data 0x%02x\n", | |
312 | command, complete, iface_data); | |
313 | retval = -EINVAL; | |
314 | } | |
315 | ||
316 | mutex_unlock(&sabi_mutex); | |
317 | return retval; | |
318 | } | |
319 | ||
320 | static void test_backlight(void) | |
321 | { | |
322 | struct sabi_retval sretval; | |
323 | ||
324 | sabi_get_command(sabi_config->commands.get_backlight, &sretval); | |
325 | printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]); | |
326 | ||
327 | sabi_set_command(sabi_config->commands.set_backlight, 0); | |
328 | printk(KERN_DEBUG "backlight should be off\n"); | |
329 | ||
330 | sabi_get_command(sabi_config->commands.get_backlight, &sretval); | |
331 | printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]); | |
332 | ||
333 | msleep(1000); | |
334 | ||
335 | sabi_set_command(sabi_config->commands.set_backlight, 1); | |
336 | printk(KERN_DEBUG "backlight should be on\n"); | |
337 | ||
338 | sabi_get_command(sabi_config->commands.get_backlight, &sretval); | |
339 | printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]); | |
340 | } | |
341 | ||
342 | static void test_wireless(void) | |
343 | { | |
344 | struct sabi_retval sretval; | |
345 | ||
346 | sabi_get_command(sabi_config->commands.get_wireless_button, &sretval); | |
347 | printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]); | |
348 | ||
349 | sabi_set_command(sabi_config->commands.set_wireless_button, 0); | |
350 | printk(KERN_DEBUG "wireless led should be off\n"); | |
351 | ||
352 | sabi_get_command(sabi_config->commands.get_wireless_button, &sretval); | |
353 | printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]); | |
354 | ||
355 | msleep(1000); | |
356 | ||
357 | sabi_set_command(sabi_config->commands.set_wireless_button, 1); | |
358 | printk(KERN_DEBUG "wireless led should be on\n"); | |
359 | ||
360 | sabi_get_command(sabi_config->commands.get_wireless_button, &sretval); | |
361 | printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]); | |
362 | } | |
363 | ||
364 | static u8 read_brightness(void) | |
365 | { | |
366 | struct sabi_retval sretval; | |
367 | int user_brightness = 0; | |
368 | int retval; | |
369 | ||
370 | retval = sabi_get_command(sabi_config->commands.get_brightness, | |
371 | &sretval); | |
372 | if (!retval) { | |
373 | user_brightness = sretval.retval[0]; | |
bee460be | 374 | if (user_brightness > sabi_config->min_brightness) |
2d70b73a | 375 | user_brightness -= sabi_config->min_brightness; |
bee460be JS |
376 | else |
377 | user_brightness = 0; | |
2d70b73a GKH |
378 | } |
379 | return user_brightness; | |
380 | } | |
381 | ||
382 | static void set_brightness(u8 user_brightness) | |
383 | { | |
bee460be | 384 | u8 user_level = user_brightness + sabi_config->min_brightness; |
2d70b73a | 385 | |
ac080523 JS |
386 | if (has_stepping_quirk && user_level != 0) { |
387 | /* | |
388 | * short circuit if the specified level is what's already set | |
389 | * to prevent the screen from flickering needlessly | |
390 | */ | |
391 | if (user_brightness == read_brightness()) | |
392 | return; | |
393 | ||
394 | sabi_set_command(sabi_config->commands.set_brightness, 0); | |
395 | } | |
396 | ||
2d70b73a GKH |
397 | sabi_set_command(sabi_config->commands.set_brightness, user_level); |
398 | } | |
399 | ||
400 | static int get_brightness(struct backlight_device *bd) | |
401 | { | |
402 | return (int)read_brightness(); | |
403 | } | |
404 | ||
ac080523 JS |
405 | static void check_for_stepping_quirk(void) |
406 | { | |
ba05b237 | 407 | u8 initial_level; |
ac080523 | 408 | u8 check_level; |
ba05b237 | 409 | u8 orig_level = read_brightness(); |
ac080523 JS |
410 | |
411 | /* | |
412 | * Some laptops exhibit the strange behaviour of stepping toward | |
413 | * (rather than setting) the brightness except when changing to/from | |
414 | * brightness level 0. This behaviour is checked for here and worked | |
415 | * around in set_brightness. | |
416 | */ | |
417 | ||
ba05b237 JS |
418 | if (orig_level == 0) |
419 | set_brightness(1); | |
420 | ||
421 | initial_level = read_brightness(); | |
422 | ||
ac080523 JS |
423 | if (initial_level <= 2) |
424 | check_level = initial_level + 2; | |
425 | else | |
426 | check_level = initial_level - 2; | |
427 | ||
428 | has_stepping_quirk = false; | |
429 | set_brightness(check_level); | |
430 | ||
431 | if (read_brightness() != check_level) { | |
432 | has_stepping_quirk = true; | |
433 | pr_info("enabled workaround for brightness stepping quirk\n"); | |
434 | } | |
435 | ||
ba05b237 | 436 | set_brightness(orig_level); |
ac080523 JS |
437 | } |
438 | ||
2d70b73a GKH |
439 | static int update_status(struct backlight_device *bd) |
440 | { | |
441 | set_brightness(bd->props.brightness); | |
442 | ||
443 | if (bd->props.power == FB_BLANK_UNBLANK) | |
444 | sabi_set_command(sabi_config->commands.set_backlight, 1); | |
445 | else | |
446 | sabi_set_command(sabi_config->commands.set_backlight, 0); | |
447 | return 0; | |
448 | } | |
449 | ||
450 | static const struct backlight_ops backlight_ops = { | |
451 | .get_brightness = get_brightness, | |
452 | .update_status = update_status, | |
453 | }; | |
454 | ||
455 | static int rfkill_set(void *data, bool blocked) | |
456 | { | |
457 | /* Do something with blocked...*/ | |
458 | /* | |
459 | * blocked == false is on | |
460 | * blocked == true is off | |
461 | */ | |
462 | if (blocked) | |
463 | sabi_set_command(sabi_config->commands.set_wireless_button, 0); | |
464 | else | |
465 | sabi_set_command(sabi_config->commands.set_wireless_button, 1); | |
466 | ||
467 | return 0; | |
468 | } | |
469 | ||
470 | static struct rfkill_ops rfkill_ops = { | |
471 | .set_block = rfkill_set, | |
472 | }; | |
473 | ||
474 | static int init_wireless(struct platform_device *sdev) | |
475 | { | |
476 | int retval; | |
477 | ||
478 | rfk = rfkill_alloc("samsung-wifi", &sdev->dev, RFKILL_TYPE_WLAN, | |
479 | &rfkill_ops, NULL); | |
480 | if (!rfk) | |
481 | return -ENOMEM; | |
482 | ||
483 | retval = rfkill_register(rfk); | |
484 | if (retval) { | |
485 | rfkill_destroy(rfk); | |
486 | return -ENODEV; | |
487 | } | |
488 | ||
489 | return 0; | |
490 | } | |
491 | ||
492 | static void destroy_wireless(void) | |
493 | { | |
494 | rfkill_unregister(rfk); | |
495 | rfkill_destroy(rfk); | |
496 | } | |
497 | ||
498 | static ssize_t get_performance_level(struct device *dev, | |
499 | struct device_attribute *attr, char *buf) | |
500 | { | |
501 | struct sabi_retval sretval; | |
502 | int retval; | |
503 | int i; | |
504 | ||
505 | /* Read the state */ | |
506 | retval = sabi_get_command(sabi_config->commands.get_performance_level, | |
507 | &sretval); | |
508 | if (retval) | |
509 | return retval; | |
510 | ||
511 | /* The logic is backwards, yeah, lots of fun... */ | |
512 | for (i = 0; sabi_config->performance_levels[i].name; ++i) { | |
513 | if (sretval.retval[0] == sabi_config->performance_levels[i].value) | |
514 | return sprintf(buf, "%s\n", sabi_config->performance_levels[i].name); | |
515 | } | |
516 | return sprintf(buf, "%s\n", "unknown"); | |
517 | } | |
518 | ||
519 | static ssize_t set_performance_level(struct device *dev, | |
520 | struct device_attribute *attr, const char *buf, | |
521 | size_t count) | |
522 | { | |
523 | if (count >= 1) { | |
524 | int i; | |
525 | for (i = 0; sabi_config->performance_levels[i].name; ++i) { | |
526 | const struct sabi_performance_level *level = | |
527 | &sabi_config->performance_levels[i]; | |
528 | if (!strncasecmp(level->name, buf, strlen(level->name))) { | |
529 | sabi_set_command(sabi_config->commands.set_performance_level, | |
530 | level->value); | |
531 | break; | |
532 | } | |
533 | } | |
534 | if (!sabi_config->performance_levels[i].name) | |
535 | return -EINVAL; | |
536 | } | |
537 | return count; | |
538 | } | |
539 | static DEVICE_ATTR(performance_level, S_IWUSR | S_IRUGO, | |
540 | get_performance_level, set_performance_level); | |
541 | ||
542 | ||
543 | static int __init dmi_check_cb(const struct dmi_system_id *id) | |
544 | { | |
545 | pr_info("found laptop model '%s'\n", | |
546 | id->ident); | |
27836584 | 547 | return 1; |
2d70b73a GKH |
548 | } |
549 | ||
550 | static struct dmi_system_id __initdata samsung_dmi_table[] = { | |
551 | { | |
552 | .ident = "N128", | |
553 | .matches = { | |
554 | DMI_MATCH(DMI_SYS_VENDOR, | |
555 | "SAMSUNG ELECTRONICS CO., LTD."), | |
556 | DMI_MATCH(DMI_PRODUCT_NAME, "N128"), | |
557 | DMI_MATCH(DMI_BOARD_NAME, "N128"), | |
558 | }, | |
559 | .callback = dmi_check_cb, | |
560 | }, | |
561 | { | |
562 | .ident = "N130", | |
563 | .matches = { | |
564 | DMI_MATCH(DMI_SYS_VENDOR, | |
565 | "SAMSUNG ELECTRONICS CO., LTD."), | |
566 | DMI_MATCH(DMI_PRODUCT_NAME, "N130"), | |
567 | DMI_MATCH(DMI_BOARD_NAME, "N130"), | |
568 | }, | |
569 | .callback = dmi_check_cb, | |
4e2441c0 W |
570 | }, |
571 | { | |
572 | .ident = "N510", | |
573 | .matches = { | |
574 | DMI_MATCH(DMI_SYS_VENDOR, | |
575 | "SAMSUNG ELECTRONICS CO., LTD."), | |
576 | DMI_MATCH(DMI_PRODUCT_NAME, "N510"), | |
577 | DMI_MATCH(DMI_BOARD_NAME, "N510"), | |
578 | }, | |
579 | .callback = dmi_check_cb, | |
2d70b73a GKH |
580 | }, |
581 | { | |
582 | .ident = "X125", | |
583 | .matches = { | |
584 | DMI_MATCH(DMI_SYS_VENDOR, | |
585 | "SAMSUNG ELECTRONICS CO., LTD."), | |
586 | DMI_MATCH(DMI_PRODUCT_NAME, "X125"), | |
587 | DMI_MATCH(DMI_BOARD_NAME, "X125"), | |
588 | }, | |
589 | .callback = dmi_check_cb, | |
590 | }, | |
591 | { | |
592 | .ident = "X120/X170", | |
593 | .matches = { | |
594 | DMI_MATCH(DMI_SYS_VENDOR, | |
595 | "SAMSUNG ELECTRONICS CO., LTD."), | |
596 | DMI_MATCH(DMI_PRODUCT_NAME, "X120/X170"), | |
597 | DMI_MATCH(DMI_BOARD_NAME, "X120/X170"), | |
598 | }, | |
599 | .callback = dmi_check_cb, | |
600 | }, | |
601 | { | |
602 | .ident = "NC10", | |
603 | .matches = { | |
604 | DMI_MATCH(DMI_SYS_VENDOR, | |
605 | "SAMSUNG ELECTRONICS CO., LTD."), | |
606 | DMI_MATCH(DMI_PRODUCT_NAME, "NC10"), | |
607 | DMI_MATCH(DMI_BOARD_NAME, "NC10"), | |
608 | }, | |
609 | .callback = dmi_check_cb, | |
610 | }, | |
611 | { | |
612 | .ident = "NP-Q45", | |
613 | .matches = { | |
614 | DMI_MATCH(DMI_SYS_VENDOR, | |
615 | "SAMSUNG ELECTRONICS CO., LTD."), | |
616 | DMI_MATCH(DMI_PRODUCT_NAME, "SQ45S70S"), | |
617 | DMI_MATCH(DMI_BOARD_NAME, "SQ45S70S"), | |
618 | }, | |
619 | .callback = dmi_check_cb, | |
620 | }, | |
621 | { | |
622 | .ident = "X360", | |
623 | .matches = { | |
624 | DMI_MATCH(DMI_SYS_VENDOR, | |
625 | "SAMSUNG ELECTRONICS CO., LTD."), | |
626 | DMI_MATCH(DMI_PRODUCT_NAME, "X360"), | |
627 | DMI_MATCH(DMI_BOARD_NAME, "X360"), | |
628 | }, | |
629 | .callback = dmi_check_cb, | |
630 | }, | |
3d536ed4 AM |
631 | { |
632 | .ident = "R410 Plus", | |
633 | .matches = { | |
634 | DMI_MATCH(DMI_SYS_VENDOR, | |
635 | "SAMSUNG ELECTRONICS CO., LTD."), | |
636 | DMI_MATCH(DMI_PRODUCT_NAME, "R410P"), | |
637 | DMI_MATCH(DMI_BOARD_NAME, "R460"), | |
638 | }, | |
639 | .callback = dmi_check_cb, | |
640 | }, | |
2d70b73a GKH |
641 | { |
642 | .ident = "R518", | |
643 | .matches = { | |
644 | DMI_MATCH(DMI_SYS_VENDOR, | |
645 | "SAMSUNG ELECTRONICS CO., LTD."), | |
646 | DMI_MATCH(DMI_PRODUCT_NAME, "R518"), | |
647 | DMI_MATCH(DMI_BOARD_NAME, "R518"), | |
648 | }, | |
649 | .callback = dmi_check_cb, | |
650 | }, | |
651 | { | |
652 | .ident = "R519/R719", | |
653 | .matches = { | |
654 | DMI_MATCH(DMI_SYS_VENDOR, | |
655 | "SAMSUNG ELECTRONICS CO., LTD."), | |
656 | DMI_MATCH(DMI_PRODUCT_NAME, "R519/R719"), | |
657 | DMI_MATCH(DMI_BOARD_NAME, "R519/R719"), | |
658 | }, | |
659 | .callback = dmi_check_cb, | |
660 | }, | |
78a7539b TC |
661 | { |
662 | .ident = "N150/N210/N220", | |
663 | .matches = { | |
664 | DMI_MATCH(DMI_SYS_VENDOR, | |
665 | "SAMSUNG ELECTRONICS CO., LTD."), | |
666 | DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220"), | |
667 | DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220"), | |
668 | }, | |
669 | .callback = dmi_check_cb, | |
670 | }, | |
f689c875 RGS |
671 | { |
672 | .ident = "N220", | |
673 | .matches = { | |
674 | DMI_MATCH(DMI_SYS_VENDOR, | |
675 | "SAMSUNG ELECTRONICS CO., LTD."), | |
676 | DMI_MATCH(DMI_PRODUCT_NAME, "N220"), | |
677 | DMI_MATCH(DMI_BOARD_NAME, "N220"), | |
678 | }, | |
679 | .callback = dmi_check_cb, | |
680 | }, | |
2d70b73a | 681 | { |
10165072 | 682 | .ident = "N150/N210/N220/N230", |
2d70b73a GKH |
683 | .matches = { |
684 | DMI_MATCH(DMI_SYS_VENDOR, | |
685 | "SAMSUNG ELECTRONICS CO., LTD."), | |
10165072 GKH |
686 | DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220/N230"), |
687 | DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220/N230"), | |
2d70b73a GKH |
688 | }, |
689 | .callback = dmi_check_cb, | |
690 | }, | |
691 | { | |
692 | .ident = "N150P/N210P/N220P", | |
693 | .matches = { | |
694 | DMI_MATCH(DMI_SYS_VENDOR, | |
695 | "SAMSUNG ELECTRONICS CO., LTD."), | |
696 | DMI_MATCH(DMI_PRODUCT_NAME, "N150P/N210P/N220P"), | |
697 | DMI_MATCH(DMI_BOARD_NAME, "N150P/N210P/N220P"), | |
698 | }, | |
699 | .callback = dmi_check_cb, | |
700 | }, | |
f87d0299 SB |
701 | { |
702 | .ident = "R700", | |
703 | .matches = { | |
704 | DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | |
705 | DMI_MATCH(DMI_PRODUCT_NAME, "SR700"), | |
706 | DMI_MATCH(DMI_BOARD_NAME, "SR700"), | |
707 | }, | |
708 | .callback = dmi_check_cb, | |
709 | }, | |
2d70b73a GKH |
710 | { |
711 | .ident = "R530/R730", | |
712 | .matches = { | |
713 | DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | |
714 | DMI_MATCH(DMI_PRODUCT_NAME, "R530/R730"), | |
715 | DMI_MATCH(DMI_BOARD_NAME, "R530/R730"), | |
716 | }, | |
717 | .callback = dmi_check_cb, | |
718 | }, | |
719 | { | |
720 | .ident = "NF110/NF210/NF310", | |
721 | .matches = { | |
722 | DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | |
723 | DMI_MATCH(DMI_PRODUCT_NAME, "NF110/NF210/NF310"), | |
724 | DMI_MATCH(DMI_BOARD_NAME, "NF110/NF210/NF310"), | |
725 | }, | |
726 | .callback = dmi_check_cb, | |
727 | }, | |
728 | { | |
729 | .ident = "N145P/N250P/N260P", | |
730 | .matches = { | |
731 | DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | |
732 | DMI_MATCH(DMI_PRODUCT_NAME, "N145P/N250P/N260P"), | |
733 | DMI_MATCH(DMI_BOARD_NAME, "N145P/N250P/N260P"), | |
734 | }, | |
735 | .callback = dmi_check_cb, | |
736 | }, | |
737 | { | |
738 | .ident = "R70/R71", | |
739 | .matches = { | |
740 | DMI_MATCH(DMI_SYS_VENDOR, | |
741 | "SAMSUNG ELECTRONICS CO., LTD."), | |
742 | DMI_MATCH(DMI_PRODUCT_NAME, "R70/R71"), | |
743 | DMI_MATCH(DMI_BOARD_NAME, "R70/R71"), | |
744 | }, | |
745 | .callback = dmi_check_cb, | |
746 | }, | |
747 | { | |
748 | .ident = "P460", | |
749 | .matches = { | |
750 | DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | |
751 | DMI_MATCH(DMI_PRODUCT_NAME, "P460"), | |
752 | DMI_MATCH(DMI_BOARD_NAME, "P460"), | |
753 | }, | |
754 | .callback = dmi_check_cb, | |
755 | }, | |
093ed561 SA |
756 | { |
757 | .ident = "R528/R728", | |
758 | .matches = { | |
759 | DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | |
760 | DMI_MATCH(DMI_PRODUCT_NAME, "R528/R728"), | |
761 | DMI_MATCH(DMI_BOARD_NAME, "R528/R728"), | |
762 | }, | |
763 | .callback = dmi_check_cb, | |
764 | }, | |
7b3c257c JS |
765 | { |
766 | .ident = "NC210/NC110", | |
767 | .matches = { | |
768 | DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | |
769 | DMI_MATCH(DMI_PRODUCT_NAME, "NC210/NC110"), | |
770 | DMI_MATCH(DMI_BOARD_NAME, "NC210/NC110"), | |
771 | }, | |
772 | .callback = dmi_check_cb, | |
7500eeb0 TM |
773 | }, |
774 | { | |
775 | .ident = "X520", | |
776 | .matches = { | |
777 | DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | |
778 | DMI_MATCH(DMI_PRODUCT_NAME, "X520"), | |
779 | DMI_MATCH(DMI_BOARD_NAME, "X520"), | |
780 | }, | |
781 | .callback = dmi_check_cb, | |
7b3c257c | 782 | }, |
2d70b73a GKH |
783 | { }, |
784 | }; | |
785 | MODULE_DEVICE_TABLE(dmi, samsung_dmi_table); | |
786 | ||
787 | static int find_signature(void __iomem *memcheck, const char *testStr) | |
788 | { | |
789 | int i = 0; | |
790 | int loca; | |
791 | ||
792 | for (loca = 0; loca < 0xffff; loca++) { | |
793 | char temp = readb(memcheck + loca); | |
794 | ||
795 | if (temp == testStr[i]) { | |
796 | if (i == strlen(testStr)-1) | |
797 | break; | |
798 | ++i; | |
799 | } else { | |
800 | i = 0; | |
801 | } | |
802 | } | |
803 | return loca; | |
804 | } | |
805 | ||
806 | static int __init samsung_init(void) | |
807 | { | |
808 | struct backlight_properties props; | |
809 | struct sabi_retval sretval; | |
810 | unsigned int ifaceP; | |
811 | int i; | |
812 | int loca; | |
813 | int retval; | |
814 | ||
815 | mutex_init(&sabi_mutex); | |
816 | ||
817 | if (!force && !dmi_check_system(samsung_dmi_table)) | |
818 | return -ENODEV; | |
819 | ||
820 | f0000_segment = ioremap_nocache(0xf0000, 0xffff); | |
821 | if (!f0000_segment) { | |
822 | pr_err("Can't map the segment at 0xf0000\n"); | |
823 | return -EINVAL; | |
824 | } | |
825 | ||
826 | /* Try to find one of the signatures in memory to find the header */ | |
827 | for (i = 0; sabi_configs[i].test_string != 0; ++i) { | |
828 | sabi_config = &sabi_configs[i]; | |
829 | loca = find_signature(f0000_segment, sabi_config->test_string); | |
830 | if (loca != 0xffff) | |
831 | break; | |
832 | } | |
833 | ||
834 | if (loca == 0xffff) { | |
835 | pr_err("This computer does not support SABI\n"); | |
836 | goto error_no_signature; | |
837 | } | |
838 | ||
839 | /* point to the SMI port Number */ | |
840 | loca += 1; | |
841 | sabi = (f0000_segment + loca); | |
842 | ||
843 | if (debug) { | |
844 | printk(KERN_DEBUG "This computer supports SABI==%x\n", | |
845 | loca + 0xf0000 - 6); | |
846 | printk(KERN_DEBUG "SABI header:\n"); | |
847 | printk(KERN_DEBUG " SMI Port Number = 0x%04x\n", | |
848 | readw(sabi + sabi_config->header_offsets.port)); | |
849 | printk(KERN_DEBUG " SMI Interface Function = 0x%02x\n", | |
850 | readb(sabi + sabi_config->header_offsets.iface_func)); | |
851 | printk(KERN_DEBUG " SMI enable memory buffer = 0x%02x\n", | |
852 | readb(sabi + sabi_config->header_offsets.en_mem)); | |
853 | printk(KERN_DEBUG " SMI restore memory buffer = 0x%02x\n", | |
854 | readb(sabi + sabi_config->header_offsets.re_mem)); | |
855 | printk(KERN_DEBUG " SABI data offset = 0x%04x\n", | |
856 | readw(sabi + sabi_config->header_offsets.data_offset)); | |
857 | printk(KERN_DEBUG " SABI data segment = 0x%04x\n", | |
858 | readw(sabi + sabi_config->header_offsets.data_segment)); | |
859 | } | |
860 | ||
861 | /* Get a pointer to the SABI Interface */ | |
862 | ifaceP = (readw(sabi + sabi_config->header_offsets.data_segment) & 0x0ffff) << 4; | |
863 | ifaceP += readw(sabi + sabi_config->header_offsets.data_offset) & 0x0ffff; | |
864 | sabi_iface = ioremap_nocache(ifaceP, 16); | |
865 | if (!sabi_iface) { | |
866 | pr_err("Can't remap %x\n", ifaceP); | |
a7ea1992 | 867 | goto error_no_signature; |
2d70b73a GKH |
868 | } |
869 | if (debug) { | |
870 | printk(KERN_DEBUG "ifaceP = 0x%08x\n", ifaceP); | |
871 | printk(KERN_DEBUG "sabi_iface = %p\n", sabi_iface); | |
872 | ||
873 | test_backlight(); | |
874 | test_wireless(); | |
875 | ||
876 | retval = sabi_get_command(sabi_config->commands.get_brightness, | |
877 | &sretval); | |
878 | printk(KERN_DEBUG "brightness = 0x%02x\n", sretval.retval[0]); | |
879 | } | |
880 | ||
881 | /* Turn on "Linux" mode in the BIOS */ | |
882 | if (sabi_config->commands.set_linux != 0xff) { | |
883 | retval = sabi_set_command(sabi_config->commands.set_linux, | |
884 | 0x81); | |
885 | if (retval) { | |
886 | pr_warn("Linux mode was not set!\n"); | |
887 | goto error_no_platform; | |
888 | } | |
889 | } | |
890 | ||
ac080523 JS |
891 | /* Check for stepping quirk */ |
892 | check_for_stepping_quirk(); | |
893 | ||
2d70b73a GKH |
894 | /* knock up a platform device to hang stuff off of */ |
895 | sdev = platform_device_register_simple("samsung", -1, NULL, 0); | |
896 | if (IS_ERR(sdev)) | |
897 | goto error_no_platform; | |
898 | ||
899 | /* create a backlight device to talk to this one */ | |
900 | memset(&props, 0, sizeof(struct backlight_properties)); | |
8713b04a | 901 | props.type = BACKLIGHT_PLATFORM; |
bee460be JS |
902 | props.max_brightness = sabi_config->max_brightness - |
903 | sabi_config->min_brightness; | |
2d70b73a GKH |
904 | backlight_device = backlight_device_register("samsung", &sdev->dev, |
905 | NULL, &backlight_ops, | |
906 | &props); | |
907 | if (IS_ERR(backlight_device)) | |
908 | goto error_no_backlight; | |
909 | ||
910 | backlight_device->props.brightness = read_brightness(); | |
911 | backlight_device->props.power = FB_BLANK_UNBLANK; | |
912 | backlight_update_status(backlight_device); | |
913 | ||
914 | retval = init_wireless(sdev); | |
915 | if (retval) | |
916 | goto error_no_rfk; | |
917 | ||
918 | retval = device_create_file(&sdev->dev, &dev_attr_performance_level); | |
919 | if (retval) | |
920 | goto error_file_create; | |
921 | ||
2d70b73a GKH |
922 | return 0; |
923 | ||
924 | error_file_create: | |
925 | destroy_wireless(); | |
926 | ||
927 | error_no_rfk: | |
928 | backlight_device_unregister(backlight_device); | |
929 | ||
930 | error_no_backlight: | |
931 | platform_device_unregister(sdev); | |
932 | ||
933 | error_no_platform: | |
934 | iounmap(sabi_iface); | |
935 | ||
936 | error_no_signature: | |
937 | iounmap(f0000_segment); | |
938 | return -EINVAL; | |
939 | } | |
940 | ||
941 | static void __exit samsung_exit(void) | |
942 | { | |
943 | /* Turn off "Linux" mode in the BIOS */ | |
944 | if (sabi_config->commands.set_linux != 0xff) | |
945 | sabi_set_command(sabi_config->commands.set_linux, 0x80); | |
946 | ||
947 | device_remove_file(&sdev->dev, &dev_attr_performance_level); | |
948 | backlight_device_unregister(backlight_device); | |
949 | destroy_wireless(); | |
950 | iounmap(sabi_iface); | |
951 | iounmap(f0000_segment); | |
952 | platform_device_unregister(sdev); | |
953 | } | |
954 | ||
955 | module_init(samsung_init); | |
956 | module_exit(samsung_exit); | |
957 | ||
958 | MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@suse.de>"); | |
959 | MODULE_DESCRIPTION("Samsung Backlight driver"); | |
960 | MODULE_LICENSE("GPL"); |