]>
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; | |
229 | ||
230 | static int force; | |
231 | module_param(force, bool, 0); | |
232 | MODULE_PARM_DESC(force, | |
233 | "Disable the DMI check and forces the driver to be loaded"); | |
234 | ||
235 | static int debug; | |
236 | module_param(debug, bool, S_IRUGO | S_IWUSR); | |
237 | MODULE_PARM_DESC(debug, "Debug enabled or not"); | |
238 | ||
239 | static int sabi_get_command(u8 command, struct sabi_retval *sretval) | |
240 | { | |
241 | int retval = 0; | |
242 | u16 port = readw(sabi + sabi_config->header_offsets.port); | |
243 | u8 complete, iface_data; | |
244 | ||
245 | mutex_lock(&sabi_mutex); | |
246 | ||
247 | /* enable memory to be able to write to it */ | |
248 | outb(readb(sabi + sabi_config->header_offsets.en_mem), port); | |
249 | ||
250 | /* write out the command */ | |
251 | writew(sabi_config->main_function, sabi_iface + SABI_IFACE_MAIN); | |
252 | writew(command, sabi_iface + SABI_IFACE_SUB); | |
253 | writeb(0, sabi_iface + SABI_IFACE_COMPLETE); | |
254 | outb(readb(sabi + sabi_config->header_offsets.iface_func), port); | |
255 | ||
256 | /* write protect memory to make it safe */ | |
257 | outb(readb(sabi + sabi_config->header_offsets.re_mem), port); | |
258 | ||
259 | /* see if the command actually succeeded */ | |
260 | complete = readb(sabi_iface + SABI_IFACE_COMPLETE); | |
261 | iface_data = readb(sabi_iface + SABI_IFACE_DATA); | |
262 | if (complete != 0xaa || iface_data == 0xff) { | |
263 | pr_warn("SABI get command 0x%02x failed with completion flag 0x%02x and data 0x%02x\n", | |
264 | command, complete, iface_data); | |
265 | retval = -EINVAL; | |
266 | goto exit; | |
267 | } | |
268 | /* | |
269 | * Save off the data into a structure so the caller use it. | |
270 | * Right now we only want the first 4 bytes, | |
271 | * There are commands that need more, but not for the ones we | |
272 | * currently care about. | |
273 | */ | |
274 | sretval->retval[0] = readb(sabi_iface + SABI_IFACE_DATA); | |
275 | sretval->retval[1] = readb(sabi_iface + SABI_IFACE_DATA + 1); | |
276 | sretval->retval[2] = readb(sabi_iface + SABI_IFACE_DATA + 2); | |
277 | sretval->retval[3] = readb(sabi_iface + SABI_IFACE_DATA + 3); | |
278 | ||
279 | exit: | |
280 | mutex_unlock(&sabi_mutex); | |
281 | return retval; | |
282 | ||
283 | } | |
284 | ||
285 | static int sabi_set_command(u8 command, u8 data) | |
286 | { | |
287 | int retval = 0; | |
288 | u16 port = readw(sabi + sabi_config->header_offsets.port); | |
289 | u8 complete, iface_data; | |
290 | ||
291 | mutex_lock(&sabi_mutex); | |
292 | ||
293 | /* enable memory to be able to write to it */ | |
294 | outb(readb(sabi + sabi_config->header_offsets.en_mem), port); | |
295 | ||
296 | /* write out the command */ | |
297 | writew(sabi_config->main_function, sabi_iface + SABI_IFACE_MAIN); | |
298 | writew(command, sabi_iface + SABI_IFACE_SUB); | |
299 | writeb(0, sabi_iface + SABI_IFACE_COMPLETE); | |
300 | writeb(data, sabi_iface + SABI_IFACE_DATA); | |
301 | outb(readb(sabi + sabi_config->header_offsets.iface_func), port); | |
302 | ||
303 | /* write protect memory to make it safe */ | |
304 | outb(readb(sabi + sabi_config->header_offsets.re_mem), port); | |
305 | ||
306 | /* see if the command actually succeeded */ | |
307 | complete = readb(sabi_iface + SABI_IFACE_COMPLETE); | |
308 | iface_data = readb(sabi_iface + SABI_IFACE_DATA); | |
309 | if (complete != 0xaa || iface_data == 0xff) { | |
310 | pr_warn("SABI set command 0x%02x failed with completion flag 0x%02x and data 0x%02x\n", | |
311 | command, complete, iface_data); | |
312 | retval = -EINVAL; | |
313 | } | |
314 | ||
315 | mutex_unlock(&sabi_mutex); | |
316 | return retval; | |
317 | } | |
318 | ||
319 | static void test_backlight(void) | |
320 | { | |
321 | struct sabi_retval sretval; | |
322 | ||
323 | sabi_get_command(sabi_config->commands.get_backlight, &sretval); | |
324 | printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]); | |
325 | ||
326 | sabi_set_command(sabi_config->commands.set_backlight, 0); | |
327 | printk(KERN_DEBUG "backlight should be off\n"); | |
328 | ||
329 | sabi_get_command(sabi_config->commands.get_backlight, &sretval); | |
330 | printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]); | |
331 | ||
332 | msleep(1000); | |
333 | ||
334 | sabi_set_command(sabi_config->commands.set_backlight, 1); | |
335 | printk(KERN_DEBUG "backlight should be on\n"); | |
336 | ||
337 | sabi_get_command(sabi_config->commands.get_backlight, &sretval); | |
338 | printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]); | |
339 | } | |
340 | ||
341 | static void test_wireless(void) | |
342 | { | |
343 | struct sabi_retval sretval; | |
344 | ||
345 | sabi_get_command(sabi_config->commands.get_wireless_button, &sretval); | |
346 | printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]); | |
347 | ||
348 | sabi_set_command(sabi_config->commands.set_wireless_button, 0); | |
349 | printk(KERN_DEBUG "wireless led should be off\n"); | |
350 | ||
351 | sabi_get_command(sabi_config->commands.get_wireless_button, &sretval); | |
352 | printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]); | |
353 | ||
354 | msleep(1000); | |
355 | ||
356 | sabi_set_command(sabi_config->commands.set_wireless_button, 1); | |
357 | printk(KERN_DEBUG "wireless led should be on\n"); | |
358 | ||
359 | sabi_get_command(sabi_config->commands.get_wireless_button, &sretval); | |
360 | printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]); | |
361 | } | |
362 | ||
363 | static u8 read_brightness(void) | |
364 | { | |
365 | struct sabi_retval sretval; | |
366 | int user_brightness = 0; | |
367 | int retval; | |
368 | ||
369 | retval = sabi_get_command(sabi_config->commands.get_brightness, | |
370 | &sretval); | |
371 | if (!retval) { | |
372 | user_brightness = sretval.retval[0]; | |
373 | if (user_brightness != 0) | |
374 | user_brightness -= sabi_config->min_brightness; | |
375 | } | |
376 | return user_brightness; | |
377 | } | |
378 | ||
379 | static void set_brightness(u8 user_brightness) | |
380 | { | |
381 | u8 user_level = user_brightness - sabi_config->min_brightness; | |
382 | ||
383 | sabi_set_command(sabi_config->commands.set_brightness, user_level); | |
384 | } | |
385 | ||
386 | static int get_brightness(struct backlight_device *bd) | |
387 | { | |
388 | return (int)read_brightness(); | |
389 | } | |
390 | ||
391 | static int update_status(struct backlight_device *bd) | |
392 | { | |
393 | set_brightness(bd->props.brightness); | |
394 | ||
395 | if (bd->props.power == FB_BLANK_UNBLANK) | |
396 | sabi_set_command(sabi_config->commands.set_backlight, 1); | |
397 | else | |
398 | sabi_set_command(sabi_config->commands.set_backlight, 0); | |
399 | return 0; | |
400 | } | |
401 | ||
402 | static const struct backlight_ops backlight_ops = { | |
403 | .get_brightness = get_brightness, | |
404 | .update_status = update_status, | |
405 | }; | |
406 | ||
407 | static int rfkill_set(void *data, bool blocked) | |
408 | { | |
409 | /* Do something with blocked...*/ | |
410 | /* | |
411 | * blocked == false is on | |
412 | * blocked == true is off | |
413 | */ | |
414 | if (blocked) | |
415 | sabi_set_command(sabi_config->commands.set_wireless_button, 0); | |
416 | else | |
417 | sabi_set_command(sabi_config->commands.set_wireless_button, 1); | |
418 | ||
419 | return 0; | |
420 | } | |
421 | ||
422 | static struct rfkill_ops rfkill_ops = { | |
423 | .set_block = rfkill_set, | |
424 | }; | |
425 | ||
426 | static int init_wireless(struct platform_device *sdev) | |
427 | { | |
428 | int retval; | |
429 | ||
430 | rfk = rfkill_alloc("samsung-wifi", &sdev->dev, RFKILL_TYPE_WLAN, | |
431 | &rfkill_ops, NULL); | |
432 | if (!rfk) | |
433 | return -ENOMEM; | |
434 | ||
435 | retval = rfkill_register(rfk); | |
436 | if (retval) { | |
437 | rfkill_destroy(rfk); | |
438 | return -ENODEV; | |
439 | } | |
440 | ||
441 | return 0; | |
442 | } | |
443 | ||
444 | static void destroy_wireless(void) | |
445 | { | |
446 | rfkill_unregister(rfk); | |
447 | rfkill_destroy(rfk); | |
448 | } | |
449 | ||
450 | static ssize_t get_performance_level(struct device *dev, | |
451 | struct device_attribute *attr, char *buf) | |
452 | { | |
453 | struct sabi_retval sretval; | |
454 | int retval; | |
455 | int i; | |
456 | ||
457 | /* Read the state */ | |
458 | retval = sabi_get_command(sabi_config->commands.get_performance_level, | |
459 | &sretval); | |
460 | if (retval) | |
461 | return retval; | |
462 | ||
463 | /* The logic is backwards, yeah, lots of fun... */ | |
464 | for (i = 0; sabi_config->performance_levels[i].name; ++i) { | |
465 | if (sretval.retval[0] == sabi_config->performance_levels[i].value) | |
466 | return sprintf(buf, "%s\n", sabi_config->performance_levels[i].name); | |
467 | } | |
468 | return sprintf(buf, "%s\n", "unknown"); | |
469 | } | |
470 | ||
471 | static ssize_t set_performance_level(struct device *dev, | |
472 | struct device_attribute *attr, const char *buf, | |
473 | size_t count) | |
474 | { | |
475 | if (count >= 1) { | |
476 | int i; | |
477 | for (i = 0; sabi_config->performance_levels[i].name; ++i) { | |
478 | const struct sabi_performance_level *level = | |
479 | &sabi_config->performance_levels[i]; | |
480 | if (!strncasecmp(level->name, buf, strlen(level->name))) { | |
481 | sabi_set_command(sabi_config->commands.set_performance_level, | |
482 | level->value); | |
483 | break; | |
484 | } | |
485 | } | |
486 | if (!sabi_config->performance_levels[i].name) | |
487 | return -EINVAL; | |
488 | } | |
489 | return count; | |
490 | } | |
491 | static DEVICE_ATTR(performance_level, S_IWUSR | S_IRUGO, | |
492 | get_performance_level, set_performance_level); | |
493 | ||
494 | ||
495 | static int __init dmi_check_cb(const struct dmi_system_id *id) | |
496 | { | |
497 | pr_info("found laptop model '%s'\n", | |
498 | id->ident); | |
27836584 | 499 | return 1; |
2d70b73a GKH |
500 | } |
501 | ||
502 | static struct dmi_system_id __initdata samsung_dmi_table[] = { | |
503 | { | |
504 | .ident = "N128", | |
505 | .matches = { | |
506 | DMI_MATCH(DMI_SYS_VENDOR, | |
507 | "SAMSUNG ELECTRONICS CO., LTD."), | |
508 | DMI_MATCH(DMI_PRODUCT_NAME, "N128"), | |
509 | DMI_MATCH(DMI_BOARD_NAME, "N128"), | |
510 | }, | |
511 | .callback = dmi_check_cb, | |
512 | }, | |
513 | { | |
514 | .ident = "N130", | |
515 | .matches = { | |
516 | DMI_MATCH(DMI_SYS_VENDOR, | |
517 | "SAMSUNG ELECTRONICS CO., LTD."), | |
518 | DMI_MATCH(DMI_PRODUCT_NAME, "N130"), | |
519 | DMI_MATCH(DMI_BOARD_NAME, "N130"), | |
520 | }, | |
521 | .callback = dmi_check_cb, | |
522 | }, | |
523 | { | |
524 | .ident = "X125", | |
525 | .matches = { | |
526 | DMI_MATCH(DMI_SYS_VENDOR, | |
527 | "SAMSUNG ELECTRONICS CO., LTD."), | |
528 | DMI_MATCH(DMI_PRODUCT_NAME, "X125"), | |
529 | DMI_MATCH(DMI_BOARD_NAME, "X125"), | |
530 | }, | |
531 | .callback = dmi_check_cb, | |
532 | }, | |
533 | { | |
534 | .ident = "X120/X170", | |
535 | .matches = { | |
536 | DMI_MATCH(DMI_SYS_VENDOR, | |
537 | "SAMSUNG ELECTRONICS CO., LTD."), | |
538 | DMI_MATCH(DMI_PRODUCT_NAME, "X120/X170"), | |
539 | DMI_MATCH(DMI_BOARD_NAME, "X120/X170"), | |
540 | }, | |
541 | .callback = dmi_check_cb, | |
542 | }, | |
543 | { | |
544 | .ident = "NC10", | |
545 | .matches = { | |
546 | DMI_MATCH(DMI_SYS_VENDOR, | |
547 | "SAMSUNG ELECTRONICS CO., LTD."), | |
548 | DMI_MATCH(DMI_PRODUCT_NAME, "NC10"), | |
549 | DMI_MATCH(DMI_BOARD_NAME, "NC10"), | |
550 | }, | |
551 | .callback = dmi_check_cb, | |
552 | }, | |
553 | { | |
554 | .ident = "NP-Q45", | |
555 | .matches = { | |
556 | DMI_MATCH(DMI_SYS_VENDOR, | |
557 | "SAMSUNG ELECTRONICS CO., LTD."), | |
558 | DMI_MATCH(DMI_PRODUCT_NAME, "SQ45S70S"), | |
559 | DMI_MATCH(DMI_BOARD_NAME, "SQ45S70S"), | |
560 | }, | |
561 | .callback = dmi_check_cb, | |
562 | }, | |
563 | { | |
564 | .ident = "X360", | |
565 | .matches = { | |
566 | DMI_MATCH(DMI_SYS_VENDOR, | |
567 | "SAMSUNG ELECTRONICS CO., LTD."), | |
568 | DMI_MATCH(DMI_PRODUCT_NAME, "X360"), | |
569 | DMI_MATCH(DMI_BOARD_NAME, "X360"), | |
570 | }, | |
571 | .callback = dmi_check_cb, | |
572 | }, | |
3d536ed4 AM |
573 | { |
574 | .ident = "R410 Plus", | |
575 | .matches = { | |
576 | DMI_MATCH(DMI_SYS_VENDOR, | |
577 | "SAMSUNG ELECTRONICS CO., LTD."), | |
578 | DMI_MATCH(DMI_PRODUCT_NAME, "R410P"), | |
579 | DMI_MATCH(DMI_BOARD_NAME, "R460"), | |
580 | }, | |
581 | .callback = dmi_check_cb, | |
582 | }, | |
2d70b73a GKH |
583 | { |
584 | .ident = "R518", | |
585 | .matches = { | |
586 | DMI_MATCH(DMI_SYS_VENDOR, | |
587 | "SAMSUNG ELECTRONICS CO., LTD."), | |
588 | DMI_MATCH(DMI_PRODUCT_NAME, "R518"), | |
589 | DMI_MATCH(DMI_BOARD_NAME, "R518"), | |
590 | }, | |
591 | .callback = dmi_check_cb, | |
592 | }, | |
593 | { | |
594 | .ident = "R519/R719", | |
595 | .matches = { | |
596 | DMI_MATCH(DMI_SYS_VENDOR, | |
597 | "SAMSUNG ELECTRONICS CO., LTD."), | |
598 | DMI_MATCH(DMI_PRODUCT_NAME, "R519/R719"), | |
599 | DMI_MATCH(DMI_BOARD_NAME, "R519/R719"), | |
600 | }, | |
601 | .callback = dmi_check_cb, | |
602 | }, | |
603 | { | |
10165072 | 604 | .ident = "N150/N210/N220/N230", |
2d70b73a GKH |
605 | .matches = { |
606 | DMI_MATCH(DMI_SYS_VENDOR, | |
607 | "SAMSUNG ELECTRONICS CO., LTD."), | |
10165072 GKH |
608 | DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220/N230"), |
609 | DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220/N230"), | |
2d70b73a GKH |
610 | }, |
611 | .callback = dmi_check_cb, | |
612 | }, | |
613 | { | |
614 | .ident = "N150P/N210P/N220P", | |
615 | .matches = { | |
616 | DMI_MATCH(DMI_SYS_VENDOR, | |
617 | "SAMSUNG ELECTRONICS CO., LTD."), | |
618 | DMI_MATCH(DMI_PRODUCT_NAME, "N150P/N210P/N220P"), | |
619 | DMI_MATCH(DMI_BOARD_NAME, "N150P/N210P/N220P"), | |
620 | }, | |
621 | .callback = dmi_check_cb, | |
622 | }, | |
623 | { | |
624 | .ident = "R530/R730", | |
625 | .matches = { | |
626 | DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | |
627 | DMI_MATCH(DMI_PRODUCT_NAME, "R530/R730"), | |
628 | DMI_MATCH(DMI_BOARD_NAME, "R530/R730"), | |
629 | }, | |
630 | .callback = dmi_check_cb, | |
631 | }, | |
632 | { | |
633 | .ident = "NF110/NF210/NF310", | |
634 | .matches = { | |
635 | DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | |
636 | DMI_MATCH(DMI_PRODUCT_NAME, "NF110/NF210/NF310"), | |
637 | DMI_MATCH(DMI_BOARD_NAME, "NF110/NF210/NF310"), | |
638 | }, | |
639 | .callback = dmi_check_cb, | |
640 | }, | |
641 | { | |
642 | .ident = "N145P/N250P/N260P", | |
643 | .matches = { | |
644 | DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | |
645 | DMI_MATCH(DMI_PRODUCT_NAME, "N145P/N250P/N260P"), | |
646 | DMI_MATCH(DMI_BOARD_NAME, "N145P/N250P/N260P"), | |
647 | }, | |
648 | .callback = dmi_check_cb, | |
649 | }, | |
650 | { | |
651 | .ident = "R70/R71", | |
652 | .matches = { | |
653 | DMI_MATCH(DMI_SYS_VENDOR, | |
654 | "SAMSUNG ELECTRONICS CO., LTD."), | |
655 | DMI_MATCH(DMI_PRODUCT_NAME, "R70/R71"), | |
656 | DMI_MATCH(DMI_BOARD_NAME, "R70/R71"), | |
657 | }, | |
658 | .callback = dmi_check_cb, | |
659 | }, | |
660 | { | |
661 | .ident = "P460", | |
662 | .matches = { | |
663 | DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | |
664 | DMI_MATCH(DMI_PRODUCT_NAME, "P460"), | |
665 | DMI_MATCH(DMI_BOARD_NAME, "P460"), | |
666 | }, | |
667 | .callback = dmi_check_cb, | |
668 | }, | |
669 | { }, | |
670 | }; | |
671 | MODULE_DEVICE_TABLE(dmi, samsung_dmi_table); | |
672 | ||
673 | static int find_signature(void __iomem *memcheck, const char *testStr) | |
674 | { | |
675 | int i = 0; | |
676 | int loca; | |
677 | ||
678 | for (loca = 0; loca < 0xffff; loca++) { | |
679 | char temp = readb(memcheck + loca); | |
680 | ||
681 | if (temp == testStr[i]) { | |
682 | if (i == strlen(testStr)-1) | |
683 | break; | |
684 | ++i; | |
685 | } else { | |
686 | i = 0; | |
687 | } | |
688 | } | |
689 | return loca; | |
690 | } | |
691 | ||
692 | static int __init samsung_init(void) | |
693 | { | |
694 | struct backlight_properties props; | |
695 | struct sabi_retval sretval; | |
696 | unsigned int ifaceP; | |
697 | int i; | |
698 | int loca; | |
699 | int retval; | |
700 | ||
701 | mutex_init(&sabi_mutex); | |
702 | ||
703 | if (!force && !dmi_check_system(samsung_dmi_table)) | |
704 | return -ENODEV; | |
705 | ||
706 | f0000_segment = ioremap_nocache(0xf0000, 0xffff); | |
707 | if (!f0000_segment) { | |
708 | pr_err("Can't map the segment at 0xf0000\n"); | |
709 | return -EINVAL; | |
710 | } | |
711 | ||
712 | /* Try to find one of the signatures in memory to find the header */ | |
713 | for (i = 0; sabi_configs[i].test_string != 0; ++i) { | |
714 | sabi_config = &sabi_configs[i]; | |
715 | loca = find_signature(f0000_segment, sabi_config->test_string); | |
716 | if (loca != 0xffff) | |
717 | break; | |
718 | } | |
719 | ||
720 | if (loca == 0xffff) { | |
721 | pr_err("This computer does not support SABI\n"); | |
722 | goto error_no_signature; | |
723 | } | |
724 | ||
725 | /* point to the SMI port Number */ | |
726 | loca += 1; | |
727 | sabi = (f0000_segment + loca); | |
728 | ||
729 | if (debug) { | |
730 | printk(KERN_DEBUG "This computer supports SABI==%x\n", | |
731 | loca + 0xf0000 - 6); | |
732 | printk(KERN_DEBUG "SABI header:\n"); | |
733 | printk(KERN_DEBUG " SMI Port Number = 0x%04x\n", | |
734 | readw(sabi + sabi_config->header_offsets.port)); | |
735 | printk(KERN_DEBUG " SMI Interface Function = 0x%02x\n", | |
736 | readb(sabi + sabi_config->header_offsets.iface_func)); | |
737 | printk(KERN_DEBUG " SMI enable memory buffer = 0x%02x\n", | |
738 | readb(sabi + sabi_config->header_offsets.en_mem)); | |
739 | printk(KERN_DEBUG " SMI restore memory buffer = 0x%02x\n", | |
740 | readb(sabi + sabi_config->header_offsets.re_mem)); | |
741 | printk(KERN_DEBUG " SABI data offset = 0x%04x\n", | |
742 | readw(sabi + sabi_config->header_offsets.data_offset)); | |
743 | printk(KERN_DEBUG " SABI data segment = 0x%04x\n", | |
744 | readw(sabi + sabi_config->header_offsets.data_segment)); | |
745 | } | |
746 | ||
747 | /* Get a pointer to the SABI Interface */ | |
748 | ifaceP = (readw(sabi + sabi_config->header_offsets.data_segment) & 0x0ffff) << 4; | |
749 | ifaceP += readw(sabi + sabi_config->header_offsets.data_offset) & 0x0ffff; | |
750 | sabi_iface = ioremap_nocache(ifaceP, 16); | |
751 | if (!sabi_iface) { | |
752 | pr_err("Can't remap %x\n", ifaceP); | |
753 | goto exit; | |
754 | } | |
755 | if (debug) { | |
756 | printk(KERN_DEBUG "ifaceP = 0x%08x\n", ifaceP); | |
757 | printk(KERN_DEBUG "sabi_iface = %p\n", sabi_iface); | |
758 | ||
759 | test_backlight(); | |
760 | test_wireless(); | |
761 | ||
762 | retval = sabi_get_command(sabi_config->commands.get_brightness, | |
763 | &sretval); | |
764 | printk(KERN_DEBUG "brightness = 0x%02x\n", sretval.retval[0]); | |
765 | } | |
766 | ||
767 | /* Turn on "Linux" mode in the BIOS */ | |
768 | if (sabi_config->commands.set_linux != 0xff) { | |
769 | retval = sabi_set_command(sabi_config->commands.set_linux, | |
770 | 0x81); | |
771 | if (retval) { | |
772 | pr_warn("Linux mode was not set!\n"); | |
773 | goto error_no_platform; | |
774 | } | |
775 | } | |
776 | ||
777 | /* knock up a platform device to hang stuff off of */ | |
778 | sdev = platform_device_register_simple("samsung", -1, NULL, 0); | |
779 | if (IS_ERR(sdev)) | |
780 | goto error_no_platform; | |
781 | ||
782 | /* create a backlight device to talk to this one */ | |
783 | memset(&props, 0, sizeof(struct backlight_properties)); | |
8713b04a | 784 | props.type = BACKLIGHT_PLATFORM; |
2d70b73a GKH |
785 | props.max_brightness = sabi_config->max_brightness; |
786 | backlight_device = backlight_device_register("samsung", &sdev->dev, | |
787 | NULL, &backlight_ops, | |
788 | &props); | |
789 | if (IS_ERR(backlight_device)) | |
790 | goto error_no_backlight; | |
791 | ||
792 | backlight_device->props.brightness = read_brightness(); | |
793 | backlight_device->props.power = FB_BLANK_UNBLANK; | |
794 | backlight_update_status(backlight_device); | |
795 | ||
796 | retval = init_wireless(sdev); | |
797 | if (retval) | |
798 | goto error_no_rfk; | |
799 | ||
800 | retval = device_create_file(&sdev->dev, &dev_attr_performance_level); | |
801 | if (retval) | |
802 | goto error_file_create; | |
803 | ||
804 | exit: | |
805 | return 0; | |
806 | ||
807 | error_file_create: | |
808 | destroy_wireless(); | |
809 | ||
810 | error_no_rfk: | |
811 | backlight_device_unregister(backlight_device); | |
812 | ||
813 | error_no_backlight: | |
814 | platform_device_unregister(sdev); | |
815 | ||
816 | error_no_platform: | |
817 | iounmap(sabi_iface); | |
818 | ||
819 | error_no_signature: | |
820 | iounmap(f0000_segment); | |
821 | return -EINVAL; | |
822 | } | |
823 | ||
824 | static void __exit samsung_exit(void) | |
825 | { | |
826 | /* Turn off "Linux" mode in the BIOS */ | |
827 | if (sabi_config->commands.set_linux != 0xff) | |
828 | sabi_set_command(sabi_config->commands.set_linux, 0x80); | |
829 | ||
830 | device_remove_file(&sdev->dev, &dev_attr_performance_level); | |
831 | backlight_device_unregister(backlight_device); | |
832 | destroy_wireless(); | |
833 | iounmap(sabi_iface); | |
834 | iounmap(f0000_segment); | |
835 | platform_device_unregister(sdev); | |
836 | } | |
837 | ||
838 | module_init(samsung_init); | |
839 | module_exit(samsung_exit); | |
840 | ||
841 | MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@suse.de>"); | |
842 | MODULE_DESCRIPTION("Samsung Backlight driver"); | |
843 | MODULE_LICENSE("GPL"); |