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 GKH |
230 | |
231 | static int force; | |
232 | module_param(force, bool, 0); | |
233 | MODULE_PARM_DESC(force, | |
234 | "Disable the DMI check and forces the driver to be loaded"); | |
235 | ||
236 | static int debug; | |
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 | { | |
407 | u8 initial_level = read_brightness(); | |
408 | u8 check_level; | |
409 | ||
410 | /* | |
411 | * Some laptops exhibit the strange behaviour of stepping toward | |
412 | * (rather than setting) the brightness except when changing to/from | |
413 | * brightness level 0. This behaviour is checked for here and worked | |
414 | * around in set_brightness. | |
415 | */ | |
416 | ||
417 | if (initial_level <= 2) | |
418 | check_level = initial_level + 2; | |
419 | else | |
420 | check_level = initial_level - 2; | |
421 | ||
422 | has_stepping_quirk = false; | |
423 | set_brightness(check_level); | |
424 | ||
425 | if (read_brightness() != check_level) { | |
426 | has_stepping_quirk = true; | |
427 | pr_info("enabled workaround for brightness stepping quirk\n"); | |
428 | } | |
429 | ||
430 | set_brightness(initial_level); | |
431 | } | |
432 | ||
2d70b73a GKH |
433 | static int update_status(struct backlight_device *bd) |
434 | { | |
435 | set_brightness(bd->props.brightness); | |
436 | ||
437 | if (bd->props.power == FB_BLANK_UNBLANK) | |
438 | sabi_set_command(sabi_config->commands.set_backlight, 1); | |
439 | else | |
440 | sabi_set_command(sabi_config->commands.set_backlight, 0); | |
441 | return 0; | |
442 | } | |
443 | ||
444 | static const struct backlight_ops backlight_ops = { | |
445 | .get_brightness = get_brightness, | |
446 | .update_status = update_status, | |
447 | }; | |
448 | ||
449 | static int rfkill_set(void *data, bool blocked) | |
450 | { | |
451 | /* Do something with blocked...*/ | |
452 | /* | |
453 | * blocked == false is on | |
454 | * blocked == true is off | |
455 | */ | |
456 | if (blocked) | |
457 | sabi_set_command(sabi_config->commands.set_wireless_button, 0); | |
458 | else | |
459 | sabi_set_command(sabi_config->commands.set_wireless_button, 1); | |
460 | ||
461 | return 0; | |
462 | } | |
463 | ||
464 | static struct rfkill_ops rfkill_ops = { | |
465 | .set_block = rfkill_set, | |
466 | }; | |
467 | ||
468 | static int init_wireless(struct platform_device *sdev) | |
469 | { | |
470 | int retval; | |
471 | ||
472 | rfk = rfkill_alloc("samsung-wifi", &sdev->dev, RFKILL_TYPE_WLAN, | |
473 | &rfkill_ops, NULL); | |
474 | if (!rfk) | |
475 | return -ENOMEM; | |
476 | ||
477 | retval = rfkill_register(rfk); | |
478 | if (retval) { | |
479 | rfkill_destroy(rfk); | |
480 | return -ENODEV; | |
481 | } | |
482 | ||
483 | return 0; | |
484 | } | |
485 | ||
486 | static void destroy_wireless(void) | |
487 | { | |
488 | rfkill_unregister(rfk); | |
489 | rfkill_destroy(rfk); | |
490 | } | |
491 | ||
492 | static ssize_t get_performance_level(struct device *dev, | |
493 | struct device_attribute *attr, char *buf) | |
494 | { | |
495 | struct sabi_retval sretval; | |
496 | int retval; | |
497 | int i; | |
498 | ||
499 | /* Read the state */ | |
500 | retval = sabi_get_command(sabi_config->commands.get_performance_level, | |
501 | &sretval); | |
502 | if (retval) | |
503 | return retval; | |
504 | ||
505 | /* The logic is backwards, yeah, lots of fun... */ | |
506 | for (i = 0; sabi_config->performance_levels[i].name; ++i) { | |
507 | if (sretval.retval[0] == sabi_config->performance_levels[i].value) | |
508 | return sprintf(buf, "%s\n", sabi_config->performance_levels[i].name); | |
509 | } | |
510 | return sprintf(buf, "%s\n", "unknown"); | |
511 | } | |
512 | ||
513 | static ssize_t set_performance_level(struct device *dev, | |
514 | struct device_attribute *attr, const char *buf, | |
515 | size_t count) | |
516 | { | |
517 | if (count >= 1) { | |
518 | int i; | |
519 | for (i = 0; sabi_config->performance_levels[i].name; ++i) { | |
520 | const struct sabi_performance_level *level = | |
521 | &sabi_config->performance_levels[i]; | |
522 | if (!strncasecmp(level->name, buf, strlen(level->name))) { | |
523 | sabi_set_command(sabi_config->commands.set_performance_level, | |
524 | level->value); | |
525 | break; | |
526 | } | |
527 | } | |
528 | if (!sabi_config->performance_levels[i].name) | |
529 | return -EINVAL; | |
530 | } | |
531 | return count; | |
532 | } | |
533 | static DEVICE_ATTR(performance_level, S_IWUSR | S_IRUGO, | |
534 | get_performance_level, set_performance_level); | |
535 | ||
536 | ||
537 | static int __init dmi_check_cb(const struct dmi_system_id *id) | |
538 | { | |
539 | pr_info("found laptop model '%s'\n", | |
540 | id->ident); | |
27836584 | 541 | return 1; |
2d70b73a GKH |
542 | } |
543 | ||
544 | static struct dmi_system_id __initdata samsung_dmi_table[] = { | |
545 | { | |
546 | .ident = "N128", | |
547 | .matches = { | |
548 | DMI_MATCH(DMI_SYS_VENDOR, | |
549 | "SAMSUNG ELECTRONICS CO., LTD."), | |
550 | DMI_MATCH(DMI_PRODUCT_NAME, "N128"), | |
551 | DMI_MATCH(DMI_BOARD_NAME, "N128"), | |
552 | }, | |
553 | .callback = dmi_check_cb, | |
554 | }, | |
555 | { | |
556 | .ident = "N130", | |
557 | .matches = { | |
558 | DMI_MATCH(DMI_SYS_VENDOR, | |
559 | "SAMSUNG ELECTRONICS CO., LTD."), | |
560 | DMI_MATCH(DMI_PRODUCT_NAME, "N130"), | |
561 | DMI_MATCH(DMI_BOARD_NAME, "N130"), | |
562 | }, | |
563 | .callback = dmi_check_cb, | |
4e2441c0 W |
564 | }, |
565 | { | |
566 | .ident = "N510", | |
567 | .matches = { | |
568 | DMI_MATCH(DMI_SYS_VENDOR, | |
569 | "SAMSUNG ELECTRONICS CO., LTD."), | |
570 | DMI_MATCH(DMI_PRODUCT_NAME, "N510"), | |
571 | DMI_MATCH(DMI_BOARD_NAME, "N510"), | |
572 | }, | |
573 | .callback = dmi_check_cb, | |
2d70b73a GKH |
574 | }, |
575 | { | |
576 | .ident = "X125", | |
577 | .matches = { | |
578 | DMI_MATCH(DMI_SYS_VENDOR, | |
579 | "SAMSUNG ELECTRONICS CO., LTD."), | |
580 | DMI_MATCH(DMI_PRODUCT_NAME, "X125"), | |
581 | DMI_MATCH(DMI_BOARD_NAME, "X125"), | |
582 | }, | |
583 | .callback = dmi_check_cb, | |
584 | }, | |
585 | { | |
586 | .ident = "X120/X170", | |
587 | .matches = { | |
588 | DMI_MATCH(DMI_SYS_VENDOR, | |
589 | "SAMSUNG ELECTRONICS CO., LTD."), | |
590 | DMI_MATCH(DMI_PRODUCT_NAME, "X120/X170"), | |
591 | DMI_MATCH(DMI_BOARD_NAME, "X120/X170"), | |
592 | }, | |
593 | .callback = dmi_check_cb, | |
594 | }, | |
595 | { | |
596 | .ident = "NC10", | |
597 | .matches = { | |
598 | DMI_MATCH(DMI_SYS_VENDOR, | |
599 | "SAMSUNG ELECTRONICS CO., LTD."), | |
600 | DMI_MATCH(DMI_PRODUCT_NAME, "NC10"), | |
601 | DMI_MATCH(DMI_BOARD_NAME, "NC10"), | |
602 | }, | |
603 | .callback = dmi_check_cb, | |
604 | }, | |
605 | { | |
606 | .ident = "NP-Q45", | |
607 | .matches = { | |
608 | DMI_MATCH(DMI_SYS_VENDOR, | |
609 | "SAMSUNG ELECTRONICS CO., LTD."), | |
610 | DMI_MATCH(DMI_PRODUCT_NAME, "SQ45S70S"), | |
611 | DMI_MATCH(DMI_BOARD_NAME, "SQ45S70S"), | |
612 | }, | |
613 | .callback = dmi_check_cb, | |
614 | }, | |
615 | { | |
616 | .ident = "X360", | |
617 | .matches = { | |
618 | DMI_MATCH(DMI_SYS_VENDOR, | |
619 | "SAMSUNG ELECTRONICS CO., LTD."), | |
620 | DMI_MATCH(DMI_PRODUCT_NAME, "X360"), | |
621 | DMI_MATCH(DMI_BOARD_NAME, "X360"), | |
622 | }, | |
623 | .callback = dmi_check_cb, | |
624 | }, | |
3d536ed4 AM |
625 | { |
626 | .ident = "R410 Plus", | |
627 | .matches = { | |
628 | DMI_MATCH(DMI_SYS_VENDOR, | |
629 | "SAMSUNG ELECTRONICS CO., LTD."), | |
630 | DMI_MATCH(DMI_PRODUCT_NAME, "R410P"), | |
631 | DMI_MATCH(DMI_BOARD_NAME, "R460"), | |
632 | }, | |
633 | .callback = dmi_check_cb, | |
634 | }, | |
2d70b73a GKH |
635 | { |
636 | .ident = "R518", | |
637 | .matches = { | |
638 | DMI_MATCH(DMI_SYS_VENDOR, | |
639 | "SAMSUNG ELECTRONICS CO., LTD."), | |
640 | DMI_MATCH(DMI_PRODUCT_NAME, "R518"), | |
641 | DMI_MATCH(DMI_BOARD_NAME, "R518"), | |
642 | }, | |
643 | .callback = dmi_check_cb, | |
644 | }, | |
645 | { | |
646 | .ident = "R519/R719", | |
647 | .matches = { | |
648 | DMI_MATCH(DMI_SYS_VENDOR, | |
649 | "SAMSUNG ELECTRONICS CO., LTD."), | |
650 | DMI_MATCH(DMI_PRODUCT_NAME, "R519/R719"), | |
651 | DMI_MATCH(DMI_BOARD_NAME, "R519/R719"), | |
652 | }, | |
653 | .callback = dmi_check_cb, | |
654 | }, | |
78a7539b TC |
655 | { |
656 | .ident = "N150/N210/N220", | |
657 | .matches = { | |
658 | DMI_MATCH(DMI_SYS_VENDOR, | |
659 | "SAMSUNG ELECTRONICS CO., LTD."), | |
660 | DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220"), | |
661 | DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220"), | |
662 | }, | |
663 | .callback = dmi_check_cb, | |
664 | }, | |
f689c875 RGS |
665 | { |
666 | .ident = "N220", | |
667 | .matches = { | |
668 | DMI_MATCH(DMI_SYS_VENDOR, | |
669 | "SAMSUNG ELECTRONICS CO., LTD."), | |
670 | DMI_MATCH(DMI_PRODUCT_NAME, "N220"), | |
671 | DMI_MATCH(DMI_BOARD_NAME, "N220"), | |
672 | }, | |
673 | .callback = dmi_check_cb, | |
674 | }, | |
2d70b73a | 675 | { |
10165072 | 676 | .ident = "N150/N210/N220/N230", |
2d70b73a GKH |
677 | .matches = { |
678 | DMI_MATCH(DMI_SYS_VENDOR, | |
679 | "SAMSUNG ELECTRONICS CO., LTD."), | |
10165072 GKH |
680 | DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220/N230"), |
681 | DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220/N230"), | |
2d70b73a GKH |
682 | }, |
683 | .callback = dmi_check_cb, | |
684 | }, | |
685 | { | |
686 | .ident = "N150P/N210P/N220P", | |
687 | .matches = { | |
688 | DMI_MATCH(DMI_SYS_VENDOR, | |
689 | "SAMSUNG ELECTRONICS CO., LTD."), | |
690 | DMI_MATCH(DMI_PRODUCT_NAME, "N150P/N210P/N220P"), | |
691 | DMI_MATCH(DMI_BOARD_NAME, "N150P/N210P/N220P"), | |
692 | }, | |
693 | .callback = dmi_check_cb, | |
694 | }, | |
695 | { | |
696 | .ident = "R530/R730", | |
697 | .matches = { | |
698 | DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | |
699 | DMI_MATCH(DMI_PRODUCT_NAME, "R530/R730"), | |
700 | DMI_MATCH(DMI_BOARD_NAME, "R530/R730"), | |
701 | }, | |
702 | .callback = dmi_check_cb, | |
703 | }, | |
704 | { | |
705 | .ident = "NF110/NF210/NF310", | |
706 | .matches = { | |
707 | DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | |
708 | DMI_MATCH(DMI_PRODUCT_NAME, "NF110/NF210/NF310"), | |
709 | DMI_MATCH(DMI_BOARD_NAME, "NF110/NF210/NF310"), | |
710 | }, | |
711 | .callback = dmi_check_cb, | |
712 | }, | |
713 | { | |
714 | .ident = "N145P/N250P/N260P", | |
715 | .matches = { | |
716 | DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | |
717 | DMI_MATCH(DMI_PRODUCT_NAME, "N145P/N250P/N260P"), | |
718 | DMI_MATCH(DMI_BOARD_NAME, "N145P/N250P/N260P"), | |
719 | }, | |
720 | .callback = dmi_check_cb, | |
721 | }, | |
722 | { | |
723 | .ident = "R70/R71", | |
724 | .matches = { | |
725 | DMI_MATCH(DMI_SYS_VENDOR, | |
726 | "SAMSUNG ELECTRONICS CO., LTD."), | |
727 | DMI_MATCH(DMI_PRODUCT_NAME, "R70/R71"), | |
728 | DMI_MATCH(DMI_BOARD_NAME, "R70/R71"), | |
729 | }, | |
730 | .callback = dmi_check_cb, | |
731 | }, | |
732 | { | |
733 | .ident = "P460", | |
734 | .matches = { | |
735 | DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | |
736 | DMI_MATCH(DMI_PRODUCT_NAME, "P460"), | |
737 | DMI_MATCH(DMI_BOARD_NAME, "P460"), | |
738 | }, | |
739 | .callback = dmi_check_cb, | |
740 | }, | |
093ed561 SA |
741 | { |
742 | .ident = "R528/R728", | |
743 | .matches = { | |
744 | DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | |
745 | DMI_MATCH(DMI_PRODUCT_NAME, "R528/R728"), | |
746 | DMI_MATCH(DMI_BOARD_NAME, "R528/R728"), | |
747 | }, | |
748 | .callback = dmi_check_cb, | |
749 | }, | |
7b3c257c JS |
750 | { |
751 | .ident = "NC210/NC110", | |
752 | .matches = { | |
753 | DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | |
754 | DMI_MATCH(DMI_PRODUCT_NAME, "NC210/NC110"), | |
755 | DMI_MATCH(DMI_BOARD_NAME, "NC210/NC110"), | |
756 | }, | |
757 | .callback = dmi_check_cb, | |
758 | }, | |
2d70b73a GKH |
759 | { }, |
760 | }; | |
761 | MODULE_DEVICE_TABLE(dmi, samsung_dmi_table); | |
762 | ||
763 | static int find_signature(void __iomem *memcheck, const char *testStr) | |
764 | { | |
765 | int i = 0; | |
766 | int loca; | |
767 | ||
768 | for (loca = 0; loca < 0xffff; loca++) { | |
769 | char temp = readb(memcheck + loca); | |
770 | ||
771 | if (temp == testStr[i]) { | |
772 | if (i == strlen(testStr)-1) | |
773 | break; | |
774 | ++i; | |
775 | } else { | |
776 | i = 0; | |
777 | } | |
778 | } | |
779 | return loca; | |
780 | } | |
781 | ||
782 | static int __init samsung_init(void) | |
783 | { | |
784 | struct backlight_properties props; | |
785 | struct sabi_retval sretval; | |
786 | unsigned int ifaceP; | |
787 | int i; | |
788 | int loca; | |
789 | int retval; | |
790 | ||
791 | mutex_init(&sabi_mutex); | |
792 | ||
793 | if (!force && !dmi_check_system(samsung_dmi_table)) | |
794 | return -ENODEV; | |
795 | ||
796 | f0000_segment = ioremap_nocache(0xf0000, 0xffff); | |
797 | if (!f0000_segment) { | |
798 | pr_err("Can't map the segment at 0xf0000\n"); | |
799 | return -EINVAL; | |
800 | } | |
801 | ||
802 | /* Try to find one of the signatures in memory to find the header */ | |
803 | for (i = 0; sabi_configs[i].test_string != 0; ++i) { | |
804 | sabi_config = &sabi_configs[i]; | |
805 | loca = find_signature(f0000_segment, sabi_config->test_string); | |
806 | if (loca != 0xffff) | |
807 | break; | |
808 | } | |
809 | ||
810 | if (loca == 0xffff) { | |
811 | pr_err("This computer does not support SABI\n"); | |
812 | goto error_no_signature; | |
813 | } | |
814 | ||
815 | /* point to the SMI port Number */ | |
816 | loca += 1; | |
817 | sabi = (f0000_segment + loca); | |
818 | ||
819 | if (debug) { | |
820 | printk(KERN_DEBUG "This computer supports SABI==%x\n", | |
821 | loca + 0xf0000 - 6); | |
822 | printk(KERN_DEBUG "SABI header:\n"); | |
823 | printk(KERN_DEBUG " SMI Port Number = 0x%04x\n", | |
824 | readw(sabi + sabi_config->header_offsets.port)); | |
825 | printk(KERN_DEBUG " SMI Interface Function = 0x%02x\n", | |
826 | readb(sabi + sabi_config->header_offsets.iface_func)); | |
827 | printk(KERN_DEBUG " SMI enable memory buffer = 0x%02x\n", | |
828 | readb(sabi + sabi_config->header_offsets.en_mem)); | |
829 | printk(KERN_DEBUG " SMI restore memory buffer = 0x%02x\n", | |
830 | readb(sabi + sabi_config->header_offsets.re_mem)); | |
831 | printk(KERN_DEBUG " SABI data offset = 0x%04x\n", | |
832 | readw(sabi + sabi_config->header_offsets.data_offset)); | |
833 | printk(KERN_DEBUG " SABI data segment = 0x%04x\n", | |
834 | readw(sabi + sabi_config->header_offsets.data_segment)); | |
835 | } | |
836 | ||
837 | /* Get a pointer to the SABI Interface */ | |
838 | ifaceP = (readw(sabi + sabi_config->header_offsets.data_segment) & 0x0ffff) << 4; | |
839 | ifaceP += readw(sabi + sabi_config->header_offsets.data_offset) & 0x0ffff; | |
840 | sabi_iface = ioremap_nocache(ifaceP, 16); | |
841 | if (!sabi_iface) { | |
842 | pr_err("Can't remap %x\n", ifaceP); | |
a7ea1992 | 843 | goto error_no_signature; |
2d70b73a GKH |
844 | } |
845 | if (debug) { | |
846 | printk(KERN_DEBUG "ifaceP = 0x%08x\n", ifaceP); | |
847 | printk(KERN_DEBUG "sabi_iface = %p\n", sabi_iface); | |
848 | ||
849 | test_backlight(); | |
850 | test_wireless(); | |
851 | ||
852 | retval = sabi_get_command(sabi_config->commands.get_brightness, | |
853 | &sretval); | |
854 | printk(KERN_DEBUG "brightness = 0x%02x\n", sretval.retval[0]); | |
855 | } | |
856 | ||
857 | /* Turn on "Linux" mode in the BIOS */ | |
858 | if (sabi_config->commands.set_linux != 0xff) { | |
859 | retval = sabi_set_command(sabi_config->commands.set_linux, | |
860 | 0x81); | |
861 | if (retval) { | |
862 | pr_warn("Linux mode was not set!\n"); | |
863 | goto error_no_platform; | |
864 | } | |
865 | } | |
866 | ||
ac080523 JS |
867 | /* Check for stepping quirk */ |
868 | check_for_stepping_quirk(); | |
869 | ||
2d70b73a GKH |
870 | /* knock up a platform device to hang stuff off of */ |
871 | sdev = platform_device_register_simple("samsung", -1, NULL, 0); | |
872 | if (IS_ERR(sdev)) | |
873 | goto error_no_platform; | |
874 | ||
875 | /* create a backlight device to talk to this one */ | |
876 | memset(&props, 0, sizeof(struct backlight_properties)); | |
8713b04a | 877 | props.type = BACKLIGHT_PLATFORM; |
bee460be JS |
878 | props.max_brightness = sabi_config->max_brightness - |
879 | sabi_config->min_brightness; | |
2d70b73a GKH |
880 | backlight_device = backlight_device_register("samsung", &sdev->dev, |
881 | NULL, &backlight_ops, | |
882 | &props); | |
883 | if (IS_ERR(backlight_device)) | |
884 | goto error_no_backlight; | |
885 | ||
886 | backlight_device->props.brightness = read_brightness(); | |
887 | backlight_device->props.power = FB_BLANK_UNBLANK; | |
888 | backlight_update_status(backlight_device); | |
889 | ||
890 | retval = init_wireless(sdev); | |
891 | if (retval) | |
892 | goto error_no_rfk; | |
893 | ||
894 | retval = device_create_file(&sdev->dev, &dev_attr_performance_level); | |
895 | if (retval) | |
896 | goto error_file_create; | |
897 | ||
2d70b73a GKH |
898 | return 0; |
899 | ||
900 | error_file_create: | |
901 | destroy_wireless(); | |
902 | ||
903 | error_no_rfk: | |
904 | backlight_device_unregister(backlight_device); | |
905 | ||
906 | error_no_backlight: | |
907 | platform_device_unregister(sdev); | |
908 | ||
909 | error_no_platform: | |
910 | iounmap(sabi_iface); | |
911 | ||
912 | error_no_signature: | |
913 | iounmap(f0000_segment); | |
914 | return -EINVAL; | |
915 | } | |
916 | ||
917 | static void __exit samsung_exit(void) | |
918 | { | |
919 | /* Turn off "Linux" mode in the BIOS */ | |
920 | if (sabi_config->commands.set_linux != 0xff) | |
921 | sabi_set_command(sabi_config->commands.set_linux, 0x80); | |
922 | ||
923 | device_remove_file(&sdev->dev, &dev_attr_performance_level); | |
924 | backlight_device_unregister(backlight_device); | |
925 | destroy_wireless(); | |
926 | iounmap(sabi_iface); | |
927 | iounmap(f0000_segment); | |
928 | platform_device_unregister(sdev); | |
929 | } | |
930 | ||
931 | module_init(samsung_init); | |
932 | module_exit(samsung_exit); | |
933 | ||
934 | MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@suse.de>"); | |
935 | MODULE_DESCRIPTION("Samsung Backlight driver"); | |
936 | MODULE_LICENSE("GPL"); |