Commit | Line | Data |
---|---|---|
d189164a | 1 | /* |
aa72eb07 | 2 | * Samsung Laptop driver |
d189164a | 3 | * |
00bfe272 GKH |
4 | * Copyright (C) 2009,2011 Greg Kroah-Hartman (gregkh@suse.de) |
5 | * Copyright (C) 2009,2011 Novell Inc. | |
d189164a GKH |
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 | */ | |
00bfe272 GKH |
12 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
13 | ||
d189164a GKH |
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 | ||
d189164a GKH |
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 | ||
988a29bf IS |
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 { | |
55bb5f4b GKH |
59 | /* |
60 | * Brightness is 0 - 8, as described above. | |
61 | * Value 0 is for the BIOS to use | |
62 | */ | |
988a29bf IS |
63 | u8 get_brightness; |
64 | u8 set_brightness; | |
65 | ||
55bb5f4b GKH |
66 | /* |
67 | * first byte: | |
988a29bf IS |
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; | |
dc5ecf45 JP |
111 | const struct sabi_header_offsets header_offsets; |
112 | const struct sabi_commands commands; | |
113 | const struct sabi_performance_level performance_levels[4]; | |
50d1ae2f NF |
114 | u8 min_brightness; |
115 | u8 max_brightness; | |
988a29bf IS |
116 | }; |
117 | ||
dc5ecf45 | 118 | static const struct sabi_config sabi_configs[] = { |
988a29bf | 119 | { |
1540e350 | 120 | .test_string = "SECLINUX", |
988a29bf | 121 | |
189b5d55 | 122 | .main_function = 0x4c49, |
27f55e92 | 123 | |
1540e350 GKH |
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, | |
988a29bf IS |
131 | }, |
132 | ||
1540e350 GKH |
133 | .commands = { |
134 | .get_brightness = 0x00, | |
135 | .set_brightness = 0x01, | |
988a29bf | 136 | |
1540e350 GKH |
137 | .get_wireless_button = 0x02, |
138 | .set_wireless_button = 0x03, | |
988a29bf | 139 | |
1540e350 GKH |
140 | .get_backlight = 0x04, |
141 | .set_backlight = 0x05, | |
988a29bf | 142 | |
1540e350 GKH |
143 | .get_recovery_mode = 0x06, |
144 | .set_recovery_mode = 0x07, | |
988a29bf | 145 | |
1540e350 GKH |
146 | .get_performance_level = 0x08, |
147 | .set_performance_level = 0x09, | |
988a29bf | 148 | |
1540e350 | 149 | .set_linux = 0x0a, |
988a29bf IS |
150 | }, |
151 | ||
1540e350 | 152 | .performance_levels = { |
988a29bf | 153 | { |
1540e350 GKH |
154 | .name = "silent", |
155 | .value = 0, | |
988a29bf IS |
156 | }, |
157 | { | |
1540e350 GKH |
158 | .name = "normal", |
159 | .value = 1, | |
988a29bf IS |
160 | }, |
161 | { }, | |
162 | }, | |
50d1ae2f NF |
163 | .min_brightness = 1, |
164 | .max_brightness = 8, | |
988a29bf IS |
165 | }, |
166 | { | |
1540e350 | 167 | .test_string = "SwSmi@", |
988a29bf | 168 | |
1540e350 | 169 | .main_function = 0x5843, |
988a29bf | 170 | |
1540e350 GKH |
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, | |
988a29bf IS |
178 | }, |
179 | ||
1540e350 GKH |
180 | .commands = { |
181 | .get_brightness = 0x10, | |
182 | .set_brightness = 0x11, | |
988a29bf | 183 | |
1540e350 GKH |
184 | .get_wireless_button = 0x12, |
185 | .set_wireless_button = 0x13, | |
988a29bf | 186 | |
1540e350 GKH |
187 | .get_backlight = 0x2d, |
188 | .set_backlight = 0x2e, | |
988a29bf | 189 | |
1540e350 GKH |
190 | .get_recovery_mode = 0xff, |
191 | .set_recovery_mode = 0xff, | |
988a29bf | 192 | |
1540e350 GKH |
193 | .get_performance_level = 0x31, |
194 | .set_performance_level = 0x32, | |
988a29bf | 195 | |
1540e350 | 196 | .set_linux = 0xff, |
988a29bf IS |
197 | }, |
198 | ||
1540e350 | 199 | .performance_levels = { |
988a29bf | 200 | { |
1540e350 GKH |
201 | .name = "normal", |
202 | .value = 0, | |
988a29bf IS |
203 | }, |
204 | { | |
1540e350 GKH |
205 | .name = "silent", |
206 | .value = 1, | |
988a29bf IS |
207 | }, |
208 | { | |
1540e350 GKH |
209 | .name = "overclock", |
210 | .value = 2, | |
988a29bf IS |
211 | }, |
212 | { }, | |
213 | }, | |
50d1ae2f NF |
214 | .min_brightness = 0, |
215 | .max_brightness = 8, | |
988a29bf IS |
216 | }, |
217 | { }, | |
218 | }; | |
219 | ||
dc5ecf45 | 220 | static const struct sabi_config *sabi_config; |
988a29bf | 221 | |
d189164a GKH |
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); | |
d3c796fd CC |
232 | MODULE_PARM_DESC(force, |
233 | "Disable the DMI check and forces the driver to be loaded"); | |
d189164a GKH |
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; | |
988a29bf | 242 | u16 port = readw(sabi + sabi_config->header_offsets.port); |
00bfe272 | 243 | u8 complete, iface_data; |
d189164a GKH |
244 | |
245 | mutex_lock(&sabi_mutex); | |
246 | ||
247 | /* enable memory to be able to write to it */ | |
988a29bf | 248 | outb(readb(sabi + sabi_config->header_offsets.en_mem), port); |
d189164a GKH |
249 | |
250 | /* write out the command */ | |
988a29bf | 251 | writew(sabi_config->main_function, sabi_iface + SABI_IFACE_MAIN); |
d189164a GKH |
252 | writew(command, sabi_iface + SABI_IFACE_SUB); |
253 | writeb(0, sabi_iface + SABI_IFACE_COMPLETE); | |
988a29bf | 254 | outb(readb(sabi + sabi_config->header_offsets.iface_func), port); |
d189164a GKH |
255 | |
256 | /* write protect memory to make it safe */ | |
988a29bf | 257 | outb(readb(sabi + sabi_config->header_offsets.re_mem), port); |
d189164a GKH |
258 | |
259 | /* see if the command actually succeeded */ | |
00bfe272 GKH |
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; | |
d189164a GKH |
266 | goto exit; |
267 | } | |
00bfe272 GKH |
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); | |
d189164a | 278 | |
d189164a GKH |
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; | |
988a29bf | 288 | u16 port = readw(sabi + sabi_config->header_offsets.port); |
00bfe272 | 289 | u8 complete, iface_data; |
d189164a GKH |
290 | |
291 | mutex_lock(&sabi_mutex); | |
292 | ||
293 | /* enable memory to be able to write to it */ | |
988a29bf | 294 | outb(readb(sabi + sabi_config->header_offsets.en_mem), port); |
d189164a GKH |
295 | |
296 | /* write out the command */ | |
988a29bf | 297 | writew(sabi_config->main_function, sabi_iface + SABI_IFACE_MAIN); |
d189164a GKH |
298 | writew(command, sabi_iface + SABI_IFACE_SUB); |
299 | writeb(0, sabi_iface + SABI_IFACE_COMPLETE); | |
300 | writeb(data, sabi_iface + SABI_IFACE_DATA); | |
988a29bf | 301 | outb(readb(sabi + sabi_config->header_offsets.iface_func), port); |
d189164a GKH |
302 | |
303 | /* write protect memory to make it safe */ | |
988a29bf | 304 | outb(readb(sabi + sabi_config->header_offsets.re_mem), port); |
d189164a GKH |
305 | |
306 | /* see if the command actually succeeded */ | |
00bfe272 GKH |
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; | |
d189164a GKH |
313 | } |
314 | ||
d189164a GKH |
315 | mutex_unlock(&sabi_mutex); |
316 | return retval; | |
317 | } | |
318 | ||
319 | static void test_backlight(void) | |
320 | { | |
321 | struct sabi_retval sretval; | |
322 | ||
988a29bf | 323 | sabi_get_command(sabi_config->commands.get_backlight, &sretval); |
d189164a GKH |
324 | printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]); |
325 | ||
988a29bf | 326 | sabi_set_command(sabi_config->commands.set_backlight, 0); |
d189164a GKH |
327 | printk(KERN_DEBUG "backlight should be off\n"); |
328 | ||
988a29bf | 329 | sabi_get_command(sabi_config->commands.get_backlight, &sretval); |
d189164a GKH |
330 | printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]); |
331 | ||
332 | msleep(1000); | |
333 | ||
988a29bf | 334 | sabi_set_command(sabi_config->commands.set_backlight, 1); |
d189164a GKH |
335 | printk(KERN_DEBUG "backlight should be on\n"); |
336 | ||
988a29bf | 337 | sabi_get_command(sabi_config->commands.get_backlight, &sretval); |
d189164a GKH |
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 | ||
988a29bf | 345 | sabi_get_command(sabi_config->commands.get_wireless_button, &sretval); |
d189164a GKH |
346 | printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]); |
347 | ||
988a29bf | 348 | sabi_set_command(sabi_config->commands.set_wireless_button, 0); |
d189164a GKH |
349 | printk(KERN_DEBUG "wireless led should be off\n"); |
350 | ||
988a29bf | 351 | sabi_get_command(sabi_config->commands.get_wireless_button, &sretval); |
d189164a GKH |
352 | printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]); |
353 | ||
354 | msleep(1000); | |
355 | ||
988a29bf | 356 | sabi_set_command(sabi_config->commands.set_wireless_button, 1); |
d189164a GKH |
357 | printk(KERN_DEBUG "wireless led should be on\n"); |
358 | ||
988a29bf | 359 | sabi_get_command(sabi_config->commands.get_wireless_button, &sretval); |
d189164a GKH |
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 | ||
55bb5f4b GKH |
369 | retval = sabi_get_command(sabi_config->commands.get_brightness, |
370 | &sretval); | |
50d1ae2f | 371 | if (!retval) { |
d189164a GKH |
372 | user_brightness = sretval.retval[0]; |
373 | if (user_brightness != 0) | |
50d1ae2f NF |
374 | user_brightness -= sabi_config->min_brightness; |
375 | } | |
d189164a GKH |
376 | return user_brightness; |
377 | } | |
378 | ||
379 | static void set_brightness(u8 user_brightness) | |
380 | { | |
50d1ae2f NF |
381 | u8 user_level = user_brightness - sabi_config->min_brightness; |
382 | ||
383 | sabi_set_command(sabi_config->commands.set_brightness, user_level); | |
d189164a GKH |
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) | |
988a29bf | 396 | sabi_set_command(sabi_config->commands.set_backlight, 1); |
d189164a | 397 | else |
988a29bf | 398 | sabi_set_command(sabi_config->commands.set_backlight, 0); |
d189164a GKH |
399 | return 0; |
400 | } | |
401 | ||
acc2472e | 402 | static const struct backlight_ops backlight_ops = { |
d189164a GKH |
403 | .get_brightness = get_brightness, |
404 | .update_status = update_status, | |
405 | }; | |
406 | ||
d189164a GKH |
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) | |
988a29bf | 415 | sabi_set_command(sabi_config->commands.set_wireless_button, 0); |
d189164a | 416 | else |
988a29bf | 417 | sabi_set_command(sabi_config->commands.set_wireless_button, 1); |
d189164a GKH |
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 | ||
988a29bf IS |
450 | static ssize_t get_performance_level(struct device *dev, |
451 | struct device_attribute *attr, char *buf) | |
d189164a GKH |
452 | { |
453 | struct sabi_retval sretval; | |
454 | int retval; | |
5bf8b724 | 455 | int i; |
d189164a GKH |
456 | |
457 | /* Read the state */ | |
55bb5f4b GKH |
458 | retval = sabi_get_command(sabi_config->commands.get_performance_level, |
459 | &sretval); | |
d189164a GKH |
460 | if (retval) |
461 | return retval; | |
462 | ||
463 | /* The logic is backwards, yeah, lots of fun... */ | |
5bf8b724 GKH |
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); | |
988a29bf IS |
467 | } |
468 | return sprintf(buf, "%s\n", "unknown"); | |
d189164a GKH |
469 | } |
470 | ||
988a29bf | 471 | static ssize_t set_performance_level(struct device *dev, |
d189164a GKH |
472 | struct device_attribute *attr, const char *buf, |
473 | size_t count) | |
474 | { | |
d189164a | 475 | if (count >= 1) { |
5bf8b724 GKH |
476 | int i; |
477 | for (i = 0; sabi_config->performance_levels[i].name; ++i) { | |
dc5ecf45 | 478 | const struct sabi_performance_level *level = |
5bf8b724 | 479 | &sabi_config->performance_levels[i]; |
3e55eb37 | 480 | if (!strncasecmp(level->name, buf, strlen(level->name))) { |
988a29bf | 481 | sabi_set_command(sabi_config->commands.set_performance_level, |
55bb5f4b | 482 | level->value); |
988a29bf IS |
483 | break; |
484 | } | |
d189164a | 485 | } |
5bf8b724 | 486 | if (!sabi_config->performance_levels[i].name) |
988a29bf | 487 | return -EINVAL; |
d189164a GKH |
488 | } |
489 | return count; | |
490 | } | |
988a29bf IS |
491 | static DEVICE_ATTR(performance_level, S_IWUSR | S_IRUGO, |
492 | get_performance_level, set_performance_level); | |
d189164a GKH |
493 | |
494 | ||
495 | static int __init dmi_check_cb(const struct dmi_system_id *id) | |
496 | { | |
00bfe272 | 497 | pr_info("found laptop model '%s'\n", |
d189164a GKH |
498 | id->ident); |
499 | return 0; | |
500 | } | |
501 | ||
502 | static struct dmi_system_id __initdata samsung_dmi_table[] = { | |
503 | { | |
504 | .ident = "N128", | |
505 | .matches = { | |
d3c796fd CC |
506 | DMI_MATCH(DMI_SYS_VENDOR, |
507 | "SAMSUNG ELECTRONICS CO., LTD."), | |
d189164a GKH |
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 = { | |
d3c796fd CC |
516 | DMI_MATCH(DMI_SYS_VENDOR, |
517 | "SAMSUNG ELECTRONICS CO., LTD."), | |
d189164a GKH |
518 | DMI_MATCH(DMI_PRODUCT_NAME, "N130"), |
519 | DMI_MATCH(DMI_BOARD_NAME, "N130"), | |
520 | }, | |
521 | .callback = dmi_check_cb, | |
522 | }, | |
837b03cd IS |
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 | }, | |
db559b07 RG |
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 | }, | |
d3c29116 GKH |
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 | }, | |
78a2fcb4 GKH |
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 | }, | |
aa72eb07 GKH |
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 | }, | |
d542f180 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 | }, | |
aa72eb07 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 | }, | |
9b5040be GKH |
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 | }, | |
aa72eb07 | 603 | { |
0789b003 | 604 | .ident = "N150/N210/N220/N230", |
aa72eb07 GKH |
605 | .matches = { |
606 | DMI_MATCH(DMI_SYS_VENDOR, | |
607 | "SAMSUNG ELECTRONICS CO., LTD."), | |
0789b003 GKH |
608 | DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220/N230"), |
609 | DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220/N230"), | |
aa72eb07 GKH |
610 | }, |
611 | .callback = dmi_check_cb, | |
612 | }, | |
50d1ae2f NF |
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 | }, | |
aa72eb07 GKH |
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 | }, | |
5f5b5c51 RS |
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 | }, | |
e20b6f35 GKH |
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 | }, | |
8aa2bb43 RM |
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 | }, | |
d189164a GKH |
669 | { }, |
670 | }; | |
671 | MODULE_DEVICE_TABLE(dmi, samsung_dmi_table); | |
672 | ||
988a29bf IS |
673 | static int find_signature(void __iomem *memcheck, const char *testStr) |
674 | { | |
5bf8b724 | 675 | int i = 0; |
988a29bf | 676 | int loca; |
5bf8b724 | 677 | |
988a29bf IS |
678 | for (loca = 0; loca < 0xffff; loca++) { |
679 | char temp = readb(memcheck + loca); | |
680 | ||
5bf8b724 GKH |
681 | if (temp == testStr[i]) { |
682 | if (i == strlen(testStr)-1) | |
988a29bf | 683 | break; |
5bf8b724 | 684 | ++i; |
988a29bf | 685 | } else { |
5bf8b724 | 686 | i = 0; |
988a29bf IS |
687 | } |
688 | } | |
689 | return loca; | |
690 | } | |
691 | ||
d189164a GKH |
692 | static int __init samsung_init(void) |
693 | { | |
a19a6ee6 | 694 | struct backlight_properties props; |
d189164a | 695 | struct sabi_retval sretval; |
d189164a | 696 | unsigned int ifaceP; |
5bf8b724 | 697 | int i; |
d189164a GKH |
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 | ||
50d1ae2f | 706 | f0000_segment = ioremap_nocache(0xf0000, 0xffff); |
d189164a | 707 | if (!f0000_segment) { |
00bfe272 | 708 | pr_err("Can't map the segment at 0xf0000\n"); |
d189164a GKH |
709 | return -EINVAL; |
710 | } | |
711 | ||
988a29bf | 712 | /* Try to find one of the signatures in memory to find the header */ |
5bf8b724 GKH |
713 | for (i = 0; sabi_configs[i].test_string != 0; ++i) { |
714 | sabi_config = &sabi_configs[i]; | |
988a29bf IS |
715 | loca = find_signature(f0000_segment, sabi_config->test_string); |
716 | if (loca != 0xffff) | |
717 | break; | |
d189164a | 718 | } |
988a29bf | 719 | |
d189164a | 720 | if (loca == 0xffff) { |
00bfe272 | 721 | pr_err("This computer does not support SABI\n"); |
d189164a | 722 | goto error_no_signature; |
988a29bf | 723 | } |
d189164a GKH |
724 | |
725 | /* point to the SMI port Number */ | |
726 | loca += 1; | |
988a29bf | 727 | sabi = (f0000_segment + loca); |
d189164a GKH |
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", | |
988a29bf | 734 | readw(sabi + sabi_config->header_offsets.port)); |
d189164a | 735 | printk(KERN_DEBUG " SMI Interface Function = 0x%02x\n", |
988a29bf | 736 | readb(sabi + sabi_config->header_offsets.iface_func)); |
d189164a | 737 | printk(KERN_DEBUG " SMI enable memory buffer = 0x%02x\n", |
988a29bf | 738 | readb(sabi + sabi_config->header_offsets.en_mem)); |
d189164a | 739 | printk(KERN_DEBUG " SMI restore memory buffer = 0x%02x\n", |
988a29bf | 740 | readb(sabi + sabi_config->header_offsets.re_mem)); |
d189164a | 741 | printk(KERN_DEBUG " SABI data offset = 0x%04x\n", |
988a29bf | 742 | readw(sabi + sabi_config->header_offsets.data_offset)); |
d189164a | 743 | printk(KERN_DEBUG " SABI data segment = 0x%04x\n", |
988a29bf | 744 | readw(sabi + sabi_config->header_offsets.data_segment)); |
d189164a GKH |
745 | } |
746 | ||
747 | /* Get a pointer to the SABI Interface */ | |
988a29bf IS |
748 | ifaceP = (readw(sabi + sabi_config->header_offsets.data_segment) & 0x0ffff) << 4; |
749 | ifaceP += readw(sabi + sabi_config->header_offsets.data_offset) & 0x0ffff; | |
50d1ae2f | 750 | sabi_iface = ioremap_nocache(ifaceP, 16); |
d189164a | 751 | if (!sabi_iface) { |
00bfe272 | 752 | pr_err("Can't remap %x\n", ifaceP); |
d189164a GKH |
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 | ||
55bb5f4b GKH |
762 | retval = sabi_get_command(sabi_config->commands.get_brightness, |
763 | &sretval); | |
d189164a GKH |
764 | printk(KERN_DEBUG "brightness = 0x%02x\n", sretval.retval[0]); |
765 | } | |
766 | ||
767 | /* Turn on "Linux" mode in the BIOS */ | |
3e55eb37 | 768 | if (sabi_config->commands.set_linux != 0xff) { |
55bb5f4b GKH |
769 | retval = sabi_set_command(sabi_config->commands.set_linux, |
770 | 0x81); | |
988a29bf | 771 | if (retval) { |
00bfe272 | 772 | pr_warn("Linux mode was not set!\n"); |
988a29bf IS |
773 | goto error_no_platform; |
774 | } | |
d189164a GKH |
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 */ | |
a19a6ee6 | 783 | memset(&props, 0, sizeof(struct backlight_properties)); |
bb7ca747 | 784 | props.type = BACKLIGHT_PLATFORM; |
50d1ae2f | 785 | props.max_brightness = sabi_config->max_brightness; |
d189164a | 786 | backlight_device = backlight_device_register("samsung", &sdev->dev, |
a19a6ee6 MG |
787 | NULL, &backlight_ops, |
788 | &props); | |
d189164a GKH |
789 | if (IS_ERR(backlight_device)) |
790 | goto error_no_backlight; | |
791 | ||
d189164a GKH |
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 | ||
988a29bf | 800 | retval = device_create_file(&sdev->dev, &dev_attr_performance_level); |
d189164a GKH |
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 */ | |
988a29bf IS |
827 | if (sabi_config->commands.set_linux != 0xff) |
828 | sabi_set_command(sabi_config->commands.set_linux, 0x80); | |
d189164a | 829 | |
988a29bf | 830 | device_remove_file(&sdev->dev, &dev_attr_performance_level); |
d189164a GKH |
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"); |