]>
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> | |
f34cd9ca | 24 | #include <linux/acpi.h> |
5b80fc40 CC |
25 | #include <linux/seq_file.h> |
26 | #include <linux/debugfs.h> | |
2d70b73a GKH |
27 | |
28 | /* | |
29 | * This driver is needed because a number of Samsung laptops do not hook | |
30 | * their control settings through ACPI. So we have to poke around in the | |
31 | * BIOS to do things like brightness values, and "special" key controls. | |
32 | */ | |
33 | ||
34 | /* | |
35 | * We have 0 - 8 as valid brightness levels. The specs say that level 0 should | |
36 | * be reserved by the BIOS (which really doesn't make much sense), we tell | |
37 | * userspace that the value is 0 - 7 and then just tell the hardware 1 - 8 | |
38 | */ | |
39 | #define MAX_BRIGHT 0x07 | |
40 | ||
41 | ||
42 | #define SABI_IFACE_MAIN 0x00 | |
43 | #define SABI_IFACE_SUB 0x02 | |
44 | #define SABI_IFACE_COMPLETE 0x04 | |
45 | #define SABI_IFACE_DATA 0x05 | |
46 | ||
7e960711 CC |
47 | /* Structure get/set data using sabi */ |
48 | struct sabi_data { | |
49 | union { | |
50 | struct { | |
51 | u32 d0; | |
52 | u32 d1; | |
53 | u16 d2; | |
54 | u8 d3; | |
55 | }; | |
56 | u8 data[11]; | |
57 | }; | |
2d70b73a GKH |
58 | }; |
59 | ||
60 | struct sabi_header_offsets { | |
61 | u8 port; | |
62 | u8 re_mem; | |
63 | u8 iface_func; | |
64 | u8 en_mem; | |
65 | u8 data_offset; | |
66 | u8 data_segment; | |
67 | }; | |
68 | ||
69 | struct sabi_commands { | |
70 | /* | |
71 | * Brightness is 0 - 8, as described above. | |
72 | * Value 0 is for the BIOS to use | |
73 | */ | |
7e960711 CC |
74 | u16 get_brightness; |
75 | u16 set_brightness; | |
2d70b73a GKH |
76 | |
77 | /* | |
78 | * first byte: | |
79 | * 0x00 - wireless is off | |
80 | * 0x01 - wireless is on | |
81 | * second byte: | |
82 | * 0x02 - 3G is off | |
83 | * 0x03 - 3G is on | |
84 | * TODO, verify 3G is correct, that doesn't seem right... | |
85 | */ | |
7e960711 CC |
86 | u16 get_wireless_button; |
87 | u16 set_wireless_button; | |
2d70b73a GKH |
88 | |
89 | /* 0 is off, 1 is on */ | |
7e960711 CC |
90 | u16 get_backlight; |
91 | u16 set_backlight; | |
2d70b73a GKH |
92 | |
93 | /* | |
94 | * 0x80 or 0x00 - no action | |
95 | * 0x81 - recovery key pressed | |
96 | */ | |
7e960711 CC |
97 | u16 get_recovery_mode; |
98 | u16 set_recovery_mode; | |
2d70b73a GKH |
99 | |
100 | /* | |
101 | * on seclinux: 0 is low, 1 is high, | |
102 | * on swsmi: 0 is normal, 1 is silent, 2 is turbo | |
103 | */ | |
7e960711 CC |
104 | u16 get_performance_level; |
105 | u16 set_performance_level; | |
2d70b73a GKH |
106 | |
107 | /* | |
108 | * Tell the BIOS that Linux is running on this machine. | |
109 | * 81 is on, 80 is off | |
110 | */ | |
7e960711 | 111 | u16 set_linux; |
2d70b73a GKH |
112 | }; |
113 | ||
114 | struct sabi_performance_level { | |
115 | const char *name; | |
7e960711 | 116 | u16 value; |
2d70b73a GKH |
117 | }; |
118 | ||
119 | struct sabi_config { | |
120 | const char *test_string; | |
121 | u16 main_function; | |
122 | const struct sabi_header_offsets header_offsets; | |
123 | const struct sabi_commands commands; | |
124 | const struct sabi_performance_level performance_levels[4]; | |
125 | u8 min_brightness; | |
126 | u8 max_brightness; | |
127 | }; | |
128 | ||
129 | static const struct sabi_config sabi_configs[] = { | |
130 | { | |
131 | .test_string = "SECLINUX", | |
132 | ||
133 | .main_function = 0x4c49, | |
134 | ||
135 | .header_offsets = { | |
136 | .port = 0x00, | |
137 | .re_mem = 0x02, | |
138 | .iface_func = 0x03, | |
139 | .en_mem = 0x04, | |
140 | .data_offset = 0x05, | |
141 | .data_segment = 0x07, | |
142 | }, | |
143 | ||
144 | .commands = { | |
145 | .get_brightness = 0x00, | |
146 | .set_brightness = 0x01, | |
147 | ||
148 | .get_wireless_button = 0x02, | |
149 | .set_wireless_button = 0x03, | |
150 | ||
151 | .get_backlight = 0x04, | |
152 | .set_backlight = 0x05, | |
153 | ||
154 | .get_recovery_mode = 0x06, | |
155 | .set_recovery_mode = 0x07, | |
156 | ||
157 | .get_performance_level = 0x08, | |
158 | .set_performance_level = 0x09, | |
159 | ||
160 | .set_linux = 0x0a, | |
161 | }, | |
162 | ||
163 | .performance_levels = { | |
164 | { | |
165 | .name = "silent", | |
166 | .value = 0, | |
167 | }, | |
168 | { | |
169 | .name = "normal", | |
170 | .value = 1, | |
171 | }, | |
172 | { }, | |
173 | }, | |
174 | .min_brightness = 1, | |
175 | .max_brightness = 8, | |
176 | }, | |
177 | { | |
178 | .test_string = "SwSmi@", | |
179 | ||
180 | .main_function = 0x5843, | |
181 | ||
182 | .header_offsets = { | |
183 | .port = 0x00, | |
184 | .re_mem = 0x04, | |
185 | .iface_func = 0x02, | |
186 | .en_mem = 0x03, | |
187 | .data_offset = 0x05, | |
188 | .data_segment = 0x07, | |
189 | }, | |
190 | ||
191 | .commands = { | |
192 | .get_brightness = 0x10, | |
193 | .set_brightness = 0x11, | |
194 | ||
195 | .get_wireless_button = 0x12, | |
196 | .set_wireless_button = 0x13, | |
197 | ||
198 | .get_backlight = 0x2d, | |
199 | .set_backlight = 0x2e, | |
200 | ||
201 | .get_recovery_mode = 0xff, | |
202 | .set_recovery_mode = 0xff, | |
203 | ||
204 | .get_performance_level = 0x31, | |
205 | .set_performance_level = 0x32, | |
206 | ||
207 | .set_linux = 0xff, | |
208 | }, | |
209 | ||
210 | .performance_levels = { | |
211 | { | |
212 | .name = "normal", | |
213 | .value = 0, | |
214 | }, | |
215 | { | |
216 | .name = "silent", | |
217 | .value = 1, | |
218 | }, | |
219 | { | |
220 | .name = "overclock", | |
221 | .value = 2, | |
222 | }, | |
223 | { }, | |
224 | }, | |
225 | .min_brightness = 0, | |
226 | .max_brightness = 8, | |
227 | }, | |
228 | { }, | |
229 | }; | |
230 | ||
5b80fc40 CC |
231 | /* |
232 | * samsung-laptop/ - debugfs root directory | |
233 | * f0000_segment - dump f0000 segment | |
234 | * command - current command | |
235 | * data - current data | |
236 | * d0, d1, d2, d3 - data fields | |
237 | * call - call SABI using command and data | |
238 | * | |
239 | * This allow to call arbitrary sabi commands wihout | |
240 | * modifying the driver at all. | |
241 | * For example, setting the keyboard backlight brightness to 5 | |
242 | * | |
243 | * echo 0x78 > command | |
244 | * echo 0x0582 > d0 | |
245 | * echo 0 > d1 | |
246 | * echo 0 > d2 | |
247 | * echo 0 > d3 | |
248 | * cat call | |
249 | */ | |
250 | ||
251 | struct samsung_laptop_debug { | |
252 | struct dentry *root; | |
253 | struct sabi_data data; | |
254 | u16 command; | |
255 | ||
256 | struct debugfs_blob_wrapper f0000_wrapper; | |
257 | struct debugfs_blob_wrapper data_wrapper; | |
258 | }; | |
259 | ||
a6df4894 CC |
260 | struct samsung_laptop { |
261 | const struct sabi_config *config; | |
2d70b73a | 262 | |
a6df4894 CC |
263 | void __iomem *sabi; |
264 | void __iomem *sabi_iface; | |
265 | void __iomem *f0000_segment; | |
266 | ||
267 | struct mutex sabi_mutex; | |
268 | ||
5dea7a20 | 269 | struct platform_device *platform_device; |
a6df4894 CC |
270 | struct backlight_device *backlight_device; |
271 | struct rfkill *rfk; | |
272 | ||
5b80fc40 CC |
273 | struct samsung_laptop_debug debug; |
274 | ||
f34cd9ca | 275 | bool handle_backlight; |
a6df4894 CC |
276 | bool has_stepping_quirk; |
277 | }; | |
278 | ||
5dea7a20 | 279 | |
2d70b73a | 280 | |
90ab5ee9 | 281 | static bool force; |
2d70b73a GKH |
282 | module_param(force, bool, 0); |
283 | MODULE_PARM_DESC(force, | |
284 | "Disable the DMI check and forces the driver to be loaded"); | |
285 | ||
90ab5ee9 | 286 | static bool debug; |
2d70b73a GKH |
287 | module_param(debug, bool, S_IRUGO | S_IWUSR); |
288 | MODULE_PARM_DESC(debug, "Debug enabled or not"); | |
289 | ||
7e960711 CC |
290 | static int sabi_command(struct samsung_laptop *samsung, u16 command, |
291 | struct sabi_data *in, | |
292 | struct sabi_data *out) | |
2d70b73a | 293 | { |
a6df4894 | 294 | const struct sabi_config *config = samsung->config; |
7e960711 | 295 | int ret = 0; |
a6df4894 | 296 | u16 port = readw(samsung->sabi + config->header_offsets.port); |
2d70b73a GKH |
297 | u8 complete, iface_data; |
298 | ||
a6df4894 | 299 | mutex_lock(&samsung->sabi_mutex); |
2d70b73a | 300 | |
7e960711 CC |
301 | if (debug) { |
302 | if (in) | |
303 | pr_info("SABI 0x%04x {0x%08x, 0x%08x, 0x%04x, 0x%02x}", | |
304 | command, in->d0, in->d1, in->d2, in->d3); | |
305 | else | |
306 | pr_info("SABI 0x%04x", command); | |
307 | } | |
308 | ||
2d70b73a | 309 | /* enable memory to be able to write to it */ |
a6df4894 | 310 | outb(readb(samsung->sabi + config->header_offsets.en_mem), port); |
2d70b73a GKH |
311 | |
312 | /* write out the command */ | |
a6df4894 CC |
313 | writew(config->main_function, samsung->sabi_iface + SABI_IFACE_MAIN); |
314 | writew(command, samsung->sabi_iface + SABI_IFACE_SUB); | |
315 | writeb(0, samsung->sabi_iface + SABI_IFACE_COMPLETE); | |
7e960711 CC |
316 | if (in) { |
317 | writel(in->d0, samsung->sabi_iface + SABI_IFACE_DATA); | |
318 | writel(in->d1, samsung->sabi_iface + SABI_IFACE_DATA + 4); | |
319 | writew(in->d2, samsung->sabi_iface + SABI_IFACE_DATA + 8); | |
320 | writeb(in->d3, samsung->sabi_iface + SABI_IFACE_DATA + 10); | |
321 | } | |
a6df4894 | 322 | outb(readb(samsung->sabi + config->header_offsets.iface_func), port); |
2d70b73a GKH |
323 | |
324 | /* write protect memory to make it safe */ | |
a6df4894 | 325 | outb(readb(samsung->sabi + config->header_offsets.re_mem), port); |
2d70b73a GKH |
326 | |
327 | /* see if the command actually succeeded */ | |
a6df4894 CC |
328 | complete = readb(samsung->sabi_iface + SABI_IFACE_COMPLETE); |
329 | iface_data = readb(samsung->sabi_iface + SABI_IFACE_DATA); | |
2d70b73a | 330 | if (complete != 0xaa || iface_data == 0xff) { |
7e960711 CC |
331 | pr_warn("SABI command 0x%04x failed with" |
332 | " completion flag 0x%02x and interface data 0x%02x", | |
333 | command, complete, iface_data); | |
334 | ret = -EINVAL; | |
2d70b73a GKH |
335 | goto exit; |
336 | } | |
7e960711 CC |
337 | |
338 | if (out) { | |
339 | out->d0 = readl(samsung->sabi_iface + SABI_IFACE_DATA); | |
340 | out->d1 = readl(samsung->sabi_iface + SABI_IFACE_DATA + 4); | |
341 | out->d2 = readw(samsung->sabi_iface + SABI_IFACE_DATA + 2); | |
342 | out->d3 = readb(samsung->sabi_iface + SABI_IFACE_DATA + 1); | |
343 | } | |
344 | ||
345 | if (debug && out) { | |
346 | pr_info("SABI {0x%08x, 0x%08x, 0x%04x, 0x%02x}", | |
347 | out->d0, out->d1, out->d2, out->d3); | |
348 | } | |
2d70b73a GKH |
349 | |
350 | exit: | |
a6df4894 | 351 | mutex_unlock(&samsung->sabi_mutex); |
7e960711 | 352 | return ret; |
2d70b73a GKH |
353 | } |
354 | ||
7e960711 CC |
355 | /* simple wrappers usable with most commands */ |
356 | static int sabi_set_commandb(struct samsung_laptop *samsung, | |
357 | u16 command, u8 data) | |
2d70b73a | 358 | { |
7e960711 | 359 | struct sabi_data in = { .d0 = 0, .d1 = 0, .d2 = 0, .d3 = 0 }; |
2d70b73a | 360 | |
7e960711 CC |
361 | in.data[0] = data; |
362 | return sabi_command(samsung, command, &in, NULL); | |
2d70b73a GKH |
363 | } |
364 | ||
5dea7a20 | 365 | static void test_backlight(struct samsung_laptop *samsung) |
2d70b73a | 366 | { |
a6df4894 | 367 | const struct sabi_commands *commands = &samsung->config->commands; |
7e960711 | 368 | struct sabi_data sretval; |
2d70b73a | 369 | |
7e960711 CC |
370 | sabi_command(samsung, commands->get_backlight, NULL, &sretval); |
371 | printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.data[0]); | |
2d70b73a | 372 | |
7e960711 | 373 | sabi_set_commandb(samsung, commands->set_backlight, 0); |
2d70b73a GKH |
374 | printk(KERN_DEBUG "backlight should be off\n"); |
375 | ||
7e960711 CC |
376 | sabi_command(samsung, commands->get_backlight, NULL, &sretval); |
377 | printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.data[0]); | |
2d70b73a GKH |
378 | |
379 | msleep(1000); | |
380 | ||
7e960711 | 381 | sabi_set_commandb(samsung, commands->set_backlight, 1); |
2d70b73a GKH |
382 | printk(KERN_DEBUG "backlight should be on\n"); |
383 | ||
7e960711 CC |
384 | sabi_command(samsung, commands->get_backlight, NULL, &sretval); |
385 | printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.data[0]); | |
2d70b73a GKH |
386 | } |
387 | ||
5dea7a20 | 388 | static void test_wireless(struct samsung_laptop *samsung) |
2d70b73a | 389 | { |
a6df4894 | 390 | const struct sabi_commands *commands = &samsung->config->commands; |
7e960711 | 391 | struct sabi_data sretval; |
2d70b73a | 392 | |
7e960711 CC |
393 | sabi_command(samsung, commands->get_wireless_button, NULL, &sretval); |
394 | printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.data[0]); | |
2d70b73a | 395 | |
7e960711 | 396 | sabi_set_commandb(samsung, commands->set_wireless_button, 0); |
2d70b73a GKH |
397 | printk(KERN_DEBUG "wireless led should be off\n"); |
398 | ||
7e960711 CC |
399 | sabi_command(samsung, commands->get_wireless_button, NULL, &sretval); |
400 | printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.data[0]); | |
2d70b73a GKH |
401 | |
402 | msleep(1000); | |
403 | ||
7e960711 | 404 | sabi_set_commandb(samsung, commands->set_wireless_button, 1); |
2d70b73a GKH |
405 | printk(KERN_DEBUG "wireless led should be on\n"); |
406 | ||
7e960711 CC |
407 | sabi_command(samsung, commands->get_wireless_button, NULL, &sretval); |
408 | printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.data[0]); | |
2d70b73a GKH |
409 | } |
410 | ||
5dea7a20 | 411 | static int read_brightness(struct samsung_laptop *samsung) |
2d70b73a | 412 | { |
a6df4894 CC |
413 | const struct sabi_config *config = samsung->config; |
414 | const struct sabi_commands *commands = &samsung->config->commands; | |
7e960711 | 415 | struct sabi_data sretval; |
2d70b73a GKH |
416 | int user_brightness = 0; |
417 | int retval; | |
418 | ||
7e960711 CC |
419 | retval = sabi_command(samsung, commands->get_brightness, |
420 | NULL, &sretval); | |
421 | if (retval) | |
422 | return retval; | |
423 | ||
424 | user_brightness = sretval.data[0]; | |
425 | if (user_brightness > config->min_brightness) | |
426 | user_brightness -= config->min_brightness; | |
427 | else | |
428 | user_brightness = 0; | |
429 | ||
2d70b73a GKH |
430 | return user_brightness; |
431 | } | |
432 | ||
5dea7a20 | 433 | static void set_brightness(struct samsung_laptop *samsung, u8 user_brightness) |
2d70b73a | 434 | { |
a6df4894 CC |
435 | const struct sabi_config *config = samsung->config; |
436 | const struct sabi_commands *commands = &samsung->config->commands; | |
437 | u8 user_level = user_brightness + config->min_brightness; | |
2d70b73a | 438 | |
a6df4894 | 439 | if (samsung->has_stepping_quirk && user_level != 0) { |
ac080523 JS |
440 | /* |
441 | * short circuit if the specified level is what's already set | |
442 | * to prevent the screen from flickering needlessly | |
443 | */ | |
5dea7a20 | 444 | if (user_brightness == read_brightness(samsung)) |
ac080523 JS |
445 | return; |
446 | ||
7e960711 | 447 | sabi_set_commandb(samsung, commands->set_brightness, 0); |
ac080523 JS |
448 | } |
449 | ||
7e960711 | 450 | sabi_set_commandb(samsung, commands->set_brightness, user_level); |
2d70b73a GKH |
451 | } |
452 | ||
453 | static int get_brightness(struct backlight_device *bd) | |
454 | { | |
5dea7a20 CC |
455 | struct samsung_laptop *samsung = bl_get_data(bd); |
456 | ||
457 | return read_brightness(samsung); | |
2d70b73a GKH |
458 | } |
459 | ||
5dea7a20 | 460 | static void check_for_stepping_quirk(struct samsung_laptop *samsung) |
ac080523 | 461 | { |
5dea7a20 CC |
462 | int initial_level; |
463 | int check_level; | |
464 | int orig_level = read_brightness(samsung); | |
ac080523 JS |
465 | |
466 | /* | |
467 | * Some laptops exhibit the strange behaviour of stepping toward | |
468 | * (rather than setting) the brightness except when changing to/from | |
469 | * brightness level 0. This behaviour is checked for here and worked | |
470 | * around in set_brightness. | |
471 | */ | |
472 | ||
ba05b237 | 473 | if (orig_level == 0) |
5dea7a20 | 474 | set_brightness(samsung, 1); |
ba05b237 | 475 | |
5dea7a20 | 476 | initial_level = read_brightness(samsung); |
ba05b237 | 477 | |
ac080523 JS |
478 | if (initial_level <= 2) |
479 | check_level = initial_level + 2; | |
480 | else | |
481 | check_level = initial_level - 2; | |
482 | ||
a6df4894 | 483 | samsung->has_stepping_quirk = false; |
5dea7a20 | 484 | set_brightness(samsung, check_level); |
ac080523 | 485 | |
5dea7a20 | 486 | if (read_brightness(samsung) != check_level) { |
a6df4894 | 487 | samsung->has_stepping_quirk = true; |
ac080523 JS |
488 | pr_info("enabled workaround for brightness stepping quirk\n"); |
489 | } | |
490 | ||
5dea7a20 | 491 | set_brightness(samsung, orig_level); |
ac080523 JS |
492 | } |
493 | ||
2d70b73a GKH |
494 | static int update_status(struct backlight_device *bd) |
495 | { | |
5dea7a20 | 496 | struct samsung_laptop *samsung = bl_get_data(bd); |
a6df4894 CC |
497 | const struct sabi_commands *commands = &samsung->config->commands; |
498 | ||
5dea7a20 | 499 | set_brightness(samsung, bd->props.brightness); |
2d70b73a GKH |
500 | |
501 | if (bd->props.power == FB_BLANK_UNBLANK) | |
7e960711 | 502 | sabi_set_commandb(samsung, commands->set_backlight, 1); |
2d70b73a | 503 | else |
7e960711 | 504 | sabi_set_commandb(samsung, commands->set_backlight, 0); |
5dea7a20 | 505 | |
2d70b73a GKH |
506 | return 0; |
507 | } | |
508 | ||
509 | static const struct backlight_ops backlight_ops = { | |
510 | .get_brightness = get_brightness, | |
511 | .update_status = update_status, | |
512 | }; | |
513 | ||
514 | static int rfkill_set(void *data, bool blocked) | |
515 | { | |
5dea7a20 | 516 | struct samsung_laptop *samsung = data; |
a6df4894 CC |
517 | const struct sabi_commands *commands = &samsung->config->commands; |
518 | ||
2d70b73a GKH |
519 | /* Do something with blocked...*/ |
520 | /* | |
521 | * blocked == false is on | |
522 | * blocked == true is off | |
523 | */ | |
524 | if (blocked) | |
7e960711 | 525 | sabi_set_commandb(samsung, commands->set_wireless_button, 0); |
2d70b73a | 526 | else |
7e960711 | 527 | sabi_set_commandb(samsung, commands->set_wireless_button, 1); |
2d70b73a GKH |
528 | |
529 | return 0; | |
530 | } | |
531 | ||
532 | static struct rfkill_ops rfkill_ops = { | |
533 | .set_block = rfkill_set, | |
534 | }; | |
535 | ||
2d70b73a GKH |
536 | static ssize_t get_performance_level(struct device *dev, |
537 | struct device_attribute *attr, char *buf) | |
538 | { | |
5dea7a20 | 539 | struct samsung_laptop *samsung = dev_get_drvdata(dev); |
a6df4894 | 540 | const struct sabi_config *config = samsung->config; |
5dea7a20 | 541 | const struct sabi_commands *commands = &config->commands; |
7e960711 | 542 | struct sabi_data sretval; |
2d70b73a GKH |
543 | int retval; |
544 | int i; | |
545 | ||
546 | /* Read the state */ | |
7e960711 CC |
547 | retval = sabi_command(samsung, commands->get_performance_level, |
548 | NULL, &sretval); | |
2d70b73a GKH |
549 | if (retval) |
550 | return retval; | |
551 | ||
552 | /* The logic is backwards, yeah, lots of fun... */ | |
a6df4894 | 553 | for (i = 0; config->performance_levels[i].name; ++i) { |
7e960711 | 554 | if (sretval.data[0] == config->performance_levels[i].value) |
a6df4894 | 555 | return sprintf(buf, "%s\n", config->performance_levels[i].name); |
2d70b73a GKH |
556 | } |
557 | return sprintf(buf, "%s\n", "unknown"); | |
558 | } | |
559 | ||
560 | static ssize_t set_performance_level(struct device *dev, | |
561 | struct device_attribute *attr, const char *buf, | |
562 | size_t count) | |
563 | { | |
5dea7a20 | 564 | struct samsung_laptop *samsung = dev_get_drvdata(dev); |
a6df4894 | 565 | const struct sabi_config *config = samsung->config; |
5dea7a20 CC |
566 | const struct sabi_commands *commands = &config->commands; |
567 | int i; | |
a6df4894 | 568 | |
5dea7a20 CC |
569 | if (count < 1) |
570 | return count; | |
571 | ||
572 | for (i = 0; config->performance_levels[i].name; ++i) { | |
573 | const struct sabi_performance_level *level = | |
574 | &config->performance_levels[i]; | |
575 | if (!strncasecmp(level->name, buf, strlen(level->name))) { | |
7e960711 CC |
576 | sabi_set_commandb(samsung, |
577 | commands->set_performance_level, | |
578 | level->value); | |
5dea7a20 | 579 | break; |
2d70b73a | 580 | } |
2d70b73a | 581 | } |
5dea7a20 CC |
582 | |
583 | if (!config->performance_levels[i].name) | |
584 | return -EINVAL; | |
585 | ||
2d70b73a GKH |
586 | return count; |
587 | } | |
5dea7a20 | 588 | |
2d70b73a GKH |
589 | static DEVICE_ATTR(performance_level, S_IWUSR | S_IRUGO, |
590 | get_performance_level, set_performance_level); | |
591 | ||
a66c1662 CC |
592 | static struct attribute *platform_attributes[] = { |
593 | &dev_attr_performance_level.attr, | |
594 | NULL | |
595 | }; | |
2d70b73a | 596 | |
5dea7a20 CC |
597 | static int find_signature(void __iomem *memcheck, const char *testStr) |
598 | { | |
599 | int i = 0; | |
600 | int loca; | |
601 | ||
602 | for (loca = 0; loca < 0xffff; loca++) { | |
603 | char temp = readb(memcheck + loca); | |
604 | ||
605 | if (temp == testStr[i]) { | |
606 | if (i == strlen(testStr)-1) | |
607 | break; | |
608 | ++i; | |
609 | } else { | |
610 | i = 0; | |
611 | } | |
612 | } | |
613 | return loca; | |
614 | } | |
615 | ||
616 | static void samsung_rfkill_exit(struct samsung_laptop *samsung) | |
617 | { | |
618 | if (samsung->rfk) { | |
619 | rfkill_unregister(samsung->rfk); | |
620 | rfkill_destroy(samsung->rfk); | |
621 | samsung->rfk = NULL; | |
622 | } | |
623 | } | |
624 | ||
625 | static int __init samsung_rfkill_init(struct samsung_laptop *samsung) | |
626 | { | |
627 | int retval; | |
628 | ||
629 | samsung->rfk = rfkill_alloc("samsung-wifi", | |
630 | &samsung->platform_device->dev, | |
631 | RFKILL_TYPE_WLAN, | |
632 | &rfkill_ops, samsung); | |
633 | if (!samsung->rfk) | |
634 | return -ENOMEM; | |
635 | ||
636 | retval = rfkill_register(samsung->rfk); | |
637 | if (retval) { | |
638 | rfkill_destroy(samsung->rfk); | |
639 | samsung->rfk = NULL; | |
640 | return -ENODEV; | |
641 | } | |
642 | ||
643 | return 0; | |
644 | } | |
645 | ||
646 | static void samsung_backlight_exit(struct samsung_laptop *samsung) | |
647 | { | |
648 | if (samsung->backlight_device) { | |
649 | backlight_device_unregister(samsung->backlight_device); | |
650 | samsung->backlight_device = NULL; | |
651 | } | |
652 | } | |
653 | ||
654 | static int __init samsung_backlight_init(struct samsung_laptop *samsung) | |
655 | { | |
656 | struct backlight_device *bd; | |
657 | struct backlight_properties props; | |
658 | ||
f34cd9ca CC |
659 | if (!samsung->handle_backlight) |
660 | return 0; | |
661 | ||
5dea7a20 CC |
662 | memset(&props, 0, sizeof(struct backlight_properties)); |
663 | props.type = BACKLIGHT_PLATFORM; | |
664 | props.max_brightness = samsung->config->max_brightness - | |
665 | samsung->config->min_brightness; | |
666 | ||
667 | bd = backlight_device_register("samsung", | |
668 | &samsung->platform_device->dev, | |
669 | samsung, &backlight_ops, | |
670 | &props); | |
671 | if (IS_ERR(bd)) | |
672 | return PTR_ERR(bd); | |
673 | ||
674 | samsung->backlight_device = bd; | |
675 | samsung->backlight_device->props.brightness = read_brightness(samsung); | |
676 | samsung->backlight_device->props.power = FB_BLANK_UNBLANK; | |
677 | backlight_update_status(samsung->backlight_device); | |
678 | ||
679 | return 0; | |
680 | } | |
681 | ||
a66c1662 CC |
682 | static mode_t samsung_sysfs_is_visible(struct kobject *kobj, |
683 | struct attribute *attr, int idx) | |
684 | { | |
685 | struct device *dev = container_of(kobj, struct device, kobj); | |
686 | struct platform_device *pdev = to_platform_device(dev); | |
687 | struct samsung_laptop *samsung = platform_get_drvdata(pdev); | |
688 | bool ok = true; | |
689 | ||
690 | if (attr == &dev_attr_performance_level.attr) | |
691 | ok = !!samsung->config->performance_levels[0].name; | |
692 | ||
693 | return ok ? attr->mode : 0; | |
694 | } | |
695 | ||
696 | static struct attribute_group platform_attribute_group = { | |
697 | .is_visible = samsung_sysfs_is_visible, | |
698 | .attrs = platform_attributes | |
699 | }; | |
700 | ||
5dea7a20 CC |
701 | static void samsung_sysfs_exit(struct samsung_laptop *samsung) |
702 | { | |
a66c1662 CC |
703 | struct platform_device *device = samsung->platform_device; |
704 | ||
705 | sysfs_remove_group(&device->dev.kobj, &platform_attribute_group); | |
5dea7a20 CC |
706 | } |
707 | ||
708 | static int __init samsung_sysfs_init(struct samsung_laptop *samsung) | |
709 | { | |
a66c1662 CC |
710 | struct platform_device *device = samsung->platform_device; |
711 | ||
712 | return sysfs_create_group(&device->dev.kobj, &platform_attribute_group); | |
713 | ||
5dea7a20 CC |
714 | } |
715 | ||
5b80fc40 CC |
716 | static int show_call(struct seq_file *m, void *data) |
717 | { | |
718 | struct samsung_laptop *samsung = m->private; | |
719 | struct sabi_data *sdata = &samsung->debug.data; | |
720 | int ret; | |
721 | ||
722 | seq_printf(m, "SABI 0x%04x {0x%08x, 0x%08x, 0x%04x, 0x%02x}\n", | |
723 | samsung->debug.command, | |
724 | sdata->d0, sdata->d1, sdata->d2, sdata->d3); | |
725 | ||
726 | ret = sabi_command(samsung, samsung->debug.command, sdata, sdata); | |
727 | ||
728 | if (ret) { | |
729 | seq_printf(m, "SABI command 0x%04x failed\n", | |
730 | samsung->debug.command); | |
731 | return ret; | |
732 | } | |
733 | ||
734 | seq_printf(m, "SABI {0x%08x, 0x%08x, 0x%04x, 0x%02x}\n", | |
735 | sdata->d0, sdata->d1, sdata->d2, sdata->d3); | |
736 | return 0; | |
737 | } | |
738 | ||
739 | static int samsung_debugfs_open(struct inode *inode, struct file *file) | |
740 | { | |
741 | return single_open(file, show_call, inode->i_private); | |
742 | } | |
743 | ||
744 | static const struct file_operations samsung_laptop_call_io_ops = { | |
745 | .owner = THIS_MODULE, | |
746 | .open = samsung_debugfs_open, | |
747 | .read = seq_read, | |
748 | .llseek = seq_lseek, | |
749 | .release = single_release, | |
750 | }; | |
751 | ||
752 | static void samsung_debugfs_exit(struct samsung_laptop *samsung) | |
753 | { | |
754 | debugfs_remove_recursive(samsung->debug.root); | |
755 | } | |
756 | ||
757 | static int samsung_debugfs_init(struct samsung_laptop *samsung) | |
758 | { | |
759 | struct dentry *dent; | |
760 | ||
761 | samsung->debug.root = debugfs_create_dir("samsung-laptop", NULL); | |
762 | if (!samsung->debug.root) { | |
763 | pr_err("failed to create debugfs directory"); | |
764 | goto error_debugfs; | |
765 | } | |
766 | ||
767 | samsung->debug.f0000_wrapper.data = samsung->f0000_segment; | |
768 | samsung->debug.f0000_wrapper.size = 0xffff; | |
769 | ||
770 | samsung->debug.data_wrapper.data = &samsung->debug.data; | |
771 | samsung->debug.data_wrapper.size = sizeof(samsung->debug.data); | |
772 | ||
773 | dent = debugfs_create_u16("command", S_IRUGO | S_IWUSR, | |
774 | samsung->debug.root, &samsung->debug.command); | |
775 | if (!dent) | |
776 | goto error_debugfs; | |
777 | ||
778 | dent = debugfs_create_u32("d0", S_IRUGO | S_IWUSR, samsung->debug.root, | |
779 | &samsung->debug.data.d0); | |
780 | if (!dent) | |
781 | goto error_debugfs; | |
782 | ||
783 | dent = debugfs_create_u32("d1", S_IRUGO | S_IWUSR, samsung->debug.root, | |
784 | &samsung->debug.data.d1); | |
785 | if (!dent) | |
786 | goto error_debugfs; | |
787 | ||
788 | dent = debugfs_create_u16("d2", S_IRUGO | S_IWUSR, samsung->debug.root, | |
789 | &samsung->debug.data.d2); | |
790 | if (!dent) | |
791 | goto error_debugfs; | |
792 | ||
793 | dent = debugfs_create_u8("d3", S_IRUGO | S_IWUSR, samsung->debug.root, | |
794 | &samsung->debug.data.d3); | |
795 | if (!dent) | |
796 | goto error_debugfs; | |
797 | ||
798 | dent = debugfs_create_blob("data", S_IRUGO | S_IWUSR, | |
799 | samsung->debug.root, | |
800 | &samsung->debug.data_wrapper); | |
801 | if (!dent) | |
802 | goto error_debugfs; | |
803 | ||
804 | dent = debugfs_create_blob("f0000_segment", S_IRUSR | S_IWUSR, | |
805 | samsung->debug.root, | |
806 | &samsung->debug.f0000_wrapper); | |
807 | if (!dent) | |
808 | goto error_debugfs; | |
809 | ||
810 | dent = debugfs_create_file("call", S_IFREG | S_IRUGO, | |
811 | samsung->debug.root, samsung, | |
812 | &samsung_laptop_call_io_ops); | |
813 | if (!dent) | |
814 | goto error_debugfs; | |
815 | ||
816 | return 0; | |
817 | ||
818 | error_debugfs: | |
819 | samsung_debugfs_exit(samsung); | |
820 | return -ENOMEM; | |
821 | } | |
822 | ||
5dea7a20 CC |
823 | static void samsung_sabi_exit(struct samsung_laptop *samsung) |
824 | { | |
825 | const struct sabi_config *config = samsung->config; | |
826 | ||
827 | /* Turn off "Linux" mode in the BIOS */ | |
828 | if (config && config->commands.set_linux != 0xff) | |
7e960711 | 829 | sabi_set_commandb(samsung, config->commands.set_linux, 0x80); |
5dea7a20 CC |
830 | |
831 | if (samsung->sabi_iface) { | |
832 | iounmap(samsung->sabi_iface); | |
833 | samsung->sabi_iface = NULL; | |
834 | } | |
835 | if (samsung->f0000_segment) { | |
836 | iounmap(samsung->f0000_segment); | |
837 | samsung->f0000_segment = NULL; | |
838 | } | |
839 | ||
840 | samsung->config = NULL; | |
841 | } | |
842 | ||
843 | static __init void samsung_sabi_infos(struct samsung_laptop *samsung, int loca) | |
844 | { | |
845 | const struct sabi_config *config = samsung->config; | |
846 | ||
847 | printk(KERN_DEBUG "This computer supports SABI==%x\n", | |
848 | loca + 0xf0000 - 6); | |
849 | printk(KERN_DEBUG "SABI header:\n"); | |
850 | printk(KERN_DEBUG " SMI Port Number = 0x%04x\n", | |
851 | readw(samsung->sabi + config->header_offsets.port)); | |
852 | printk(KERN_DEBUG " SMI Interface Function = 0x%02x\n", | |
853 | readb(samsung->sabi + config->header_offsets.iface_func)); | |
854 | printk(KERN_DEBUG " SMI enable memory buffer = 0x%02x\n", | |
855 | readb(samsung->sabi + config->header_offsets.en_mem)); | |
856 | printk(KERN_DEBUG " SMI restore memory buffer = 0x%02x\n", | |
857 | readb(samsung->sabi + config->header_offsets.re_mem)); | |
858 | printk(KERN_DEBUG " SABI data offset = 0x%04x\n", | |
859 | readw(samsung->sabi + config->header_offsets.data_offset)); | |
860 | printk(KERN_DEBUG " SABI data segment = 0x%04x\n", | |
861 | readw(samsung->sabi + config->header_offsets.data_segment)); | |
862 | } | |
863 | ||
864 | static void __init samsung_sabi_selftest(struct samsung_laptop *samsung, | |
865 | unsigned int ifaceP) | |
866 | { | |
867 | const struct sabi_config *config = samsung->config; | |
7e960711 | 868 | struct sabi_data sretval; |
5dea7a20 CC |
869 | |
870 | printk(KERN_DEBUG "ifaceP = 0x%08x\n", ifaceP); | |
871 | printk(KERN_DEBUG "sabi_iface = %p\n", samsung->sabi_iface); | |
872 | ||
f34cd9ca CC |
873 | if (samsung->handle_backlight) |
874 | test_backlight(samsung); | |
5dea7a20 CC |
875 | test_wireless(samsung); |
876 | ||
7e960711 CC |
877 | sabi_command(samsung, config->commands.get_brightness, NULL, &sretval); |
878 | printk(KERN_DEBUG "brightness = 0x%02x\n", sretval.data[0]); | |
5dea7a20 CC |
879 | } |
880 | ||
881 | static int __init samsung_sabi_init(struct samsung_laptop *samsung) | |
882 | { | |
883 | const struct sabi_config *config = NULL; | |
884 | const struct sabi_commands *commands; | |
885 | unsigned int ifaceP; | |
886 | int ret = 0; | |
887 | int i; | |
888 | int loca; | |
889 | ||
890 | samsung->f0000_segment = ioremap_nocache(0xf0000, 0xffff); | |
891 | if (!samsung->f0000_segment) { | |
892 | pr_err("Can't map the segment at 0xf0000\n"); | |
893 | ret = -EINVAL; | |
894 | goto exit; | |
895 | } | |
896 | ||
897 | /* Try to find one of the signatures in memory to find the header */ | |
898 | for (i = 0; sabi_configs[i].test_string != 0; ++i) { | |
899 | samsung->config = &sabi_configs[i]; | |
900 | loca = find_signature(samsung->f0000_segment, | |
901 | samsung->config->test_string); | |
902 | if (loca != 0xffff) | |
903 | break; | |
904 | } | |
905 | ||
906 | if (loca == 0xffff) { | |
907 | pr_err("This computer does not support SABI\n"); | |
908 | ret = -ENODEV; | |
909 | goto exit; | |
910 | } | |
911 | ||
912 | config = samsung->config; | |
913 | commands = &config->commands; | |
914 | ||
915 | /* point to the SMI port Number */ | |
916 | loca += 1; | |
917 | samsung->sabi = (samsung->f0000_segment + loca); | |
918 | ||
919 | if (debug) | |
920 | samsung_sabi_infos(samsung, loca); | |
921 | ||
922 | /* Get a pointer to the SABI Interface */ | |
923 | ifaceP = (readw(samsung->sabi + config->header_offsets.data_segment) & 0x0ffff) << 4; | |
924 | ifaceP += readw(samsung->sabi + config->header_offsets.data_offset) & 0x0ffff; | |
925 | samsung->sabi_iface = ioremap_nocache(ifaceP, 16); | |
926 | if (!samsung->sabi_iface) { | |
927 | pr_err("Can't remap %x\n", ifaceP); | |
928 | ret = -EINVAL; | |
929 | goto exit; | |
930 | } | |
931 | ||
932 | if (debug) | |
933 | samsung_sabi_selftest(samsung, ifaceP); | |
934 | ||
935 | /* Turn on "Linux" mode in the BIOS */ | |
936 | if (commands->set_linux != 0xff) { | |
7e960711 CC |
937 | int retval = sabi_set_commandb(samsung, |
938 | commands->set_linux, 0x81); | |
5dea7a20 CC |
939 | if (retval) { |
940 | pr_warn("Linux mode was not set!\n"); | |
941 | ret = -ENODEV; | |
942 | goto exit; | |
943 | } | |
944 | } | |
945 | ||
946 | /* Check for stepping quirk */ | |
f34cd9ca CC |
947 | if (samsung->handle_backlight) |
948 | check_for_stepping_quirk(samsung); | |
5dea7a20 CC |
949 | |
950 | exit: | |
951 | if (ret) | |
952 | samsung_sabi_exit(samsung); | |
953 | ||
954 | return ret; | |
955 | } | |
956 | ||
957 | static void samsung_platform_exit(struct samsung_laptop *samsung) | |
958 | { | |
959 | if (samsung->platform_device) { | |
960 | platform_device_unregister(samsung->platform_device); | |
961 | samsung->platform_device = NULL; | |
962 | } | |
963 | } | |
964 | ||
965 | static int __init samsung_platform_init(struct samsung_laptop *samsung) | |
966 | { | |
967 | struct platform_device *pdev; | |
968 | ||
969 | pdev = platform_device_register_simple("samsung", -1, NULL, 0); | |
970 | if (IS_ERR(pdev)) | |
971 | return PTR_ERR(pdev); | |
972 | ||
973 | samsung->platform_device = pdev; | |
974 | platform_set_drvdata(samsung->platform_device, samsung); | |
975 | return 0; | |
976 | } | |
977 | ||
2d70b73a GKH |
978 | static int __init dmi_check_cb(const struct dmi_system_id *id) |
979 | { | |
5dea7a20 | 980 | pr_info("found laptop model '%s'\n", id->ident); |
27836584 | 981 | return 1; |
2d70b73a GKH |
982 | } |
983 | ||
984 | static struct dmi_system_id __initdata samsung_dmi_table[] = { | |
985 | { | |
986 | .ident = "N128", | |
987 | .matches = { | |
988 | DMI_MATCH(DMI_SYS_VENDOR, | |
989 | "SAMSUNG ELECTRONICS CO., LTD."), | |
990 | DMI_MATCH(DMI_PRODUCT_NAME, "N128"), | |
991 | DMI_MATCH(DMI_BOARD_NAME, "N128"), | |
992 | }, | |
993 | .callback = dmi_check_cb, | |
994 | }, | |
995 | { | |
996 | .ident = "N130", | |
997 | .matches = { | |
998 | DMI_MATCH(DMI_SYS_VENDOR, | |
999 | "SAMSUNG ELECTRONICS CO., LTD."), | |
1000 | DMI_MATCH(DMI_PRODUCT_NAME, "N130"), | |
1001 | DMI_MATCH(DMI_BOARD_NAME, "N130"), | |
1002 | }, | |
1003 | .callback = dmi_check_cb, | |
4e2441c0 W |
1004 | }, |
1005 | { | |
1006 | .ident = "N510", | |
1007 | .matches = { | |
1008 | DMI_MATCH(DMI_SYS_VENDOR, | |
1009 | "SAMSUNG ELECTRONICS CO., LTD."), | |
1010 | DMI_MATCH(DMI_PRODUCT_NAME, "N510"), | |
1011 | DMI_MATCH(DMI_BOARD_NAME, "N510"), | |
1012 | }, | |
1013 | .callback = dmi_check_cb, | |
2d70b73a GKH |
1014 | }, |
1015 | { | |
1016 | .ident = "X125", | |
1017 | .matches = { | |
1018 | DMI_MATCH(DMI_SYS_VENDOR, | |
1019 | "SAMSUNG ELECTRONICS CO., LTD."), | |
1020 | DMI_MATCH(DMI_PRODUCT_NAME, "X125"), | |
1021 | DMI_MATCH(DMI_BOARD_NAME, "X125"), | |
1022 | }, | |
1023 | .callback = dmi_check_cb, | |
1024 | }, | |
1025 | { | |
1026 | .ident = "X120/X170", | |
1027 | .matches = { | |
1028 | DMI_MATCH(DMI_SYS_VENDOR, | |
1029 | "SAMSUNG ELECTRONICS CO., LTD."), | |
1030 | DMI_MATCH(DMI_PRODUCT_NAME, "X120/X170"), | |
1031 | DMI_MATCH(DMI_BOARD_NAME, "X120/X170"), | |
1032 | }, | |
1033 | .callback = dmi_check_cb, | |
1034 | }, | |
1035 | { | |
1036 | .ident = "NC10", | |
1037 | .matches = { | |
1038 | DMI_MATCH(DMI_SYS_VENDOR, | |
1039 | "SAMSUNG ELECTRONICS CO., LTD."), | |
1040 | DMI_MATCH(DMI_PRODUCT_NAME, "NC10"), | |
1041 | DMI_MATCH(DMI_BOARD_NAME, "NC10"), | |
1042 | }, | |
1043 | .callback = dmi_check_cb, | |
1044 | }, | |
1045 | { | |
1046 | .ident = "NP-Q45", | |
1047 | .matches = { | |
1048 | DMI_MATCH(DMI_SYS_VENDOR, | |
1049 | "SAMSUNG ELECTRONICS CO., LTD."), | |
1050 | DMI_MATCH(DMI_PRODUCT_NAME, "SQ45S70S"), | |
1051 | DMI_MATCH(DMI_BOARD_NAME, "SQ45S70S"), | |
1052 | }, | |
1053 | .callback = dmi_check_cb, | |
1054 | }, | |
1055 | { | |
1056 | .ident = "X360", | |
1057 | .matches = { | |
1058 | DMI_MATCH(DMI_SYS_VENDOR, | |
1059 | "SAMSUNG ELECTRONICS CO., LTD."), | |
1060 | DMI_MATCH(DMI_PRODUCT_NAME, "X360"), | |
1061 | DMI_MATCH(DMI_BOARD_NAME, "X360"), | |
1062 | }, | |
1063 | .callback = dmi_check_cb, | |
1064 | }, | |
3d536ed4 AM |
1065 | { |
1066 | .ident = "R410 Plus", | |
1067 | .matches = { | |
1068 | DMI_MATCH(DMI_SYS_VENDOR, | |
1069 | "SAMSUNG ELECTRONICS CO., LTD."), | |
1070 | DMI_MATCH(DMI_PRODUCT_NAME, "R410P"), | |
1071 | DMI_MATCH(DMI_BOARD_NAME, "R460"), | |
1072 | }, | |
1073 | .callback = dmi_check_cb, | |
1074 | }, | |
2d70b73a GKH |
1075 | { |
1076 | .ident = "R518", | |
1077 | .matches = { | |
1078 | DMI_MATCH(DMI_SYS_VENDOR, | |
1079 | "SAMSUNG ELECTRONICS CO., LTD."), | |
1080 | DMI_MATCH(DMI_PRODUCT_NAME, "R518"), | |
1081 | DMI_MATCH(DMI_BOARD_NAME, "R518"), | |
1082 | }, | |
1083 | .callback = dmi_check_cb, | |
1084 | }, | |
1085 | { | |
1086 | .ident = "R519/R719", | |
1087 | .matches = { | |
1088 | DMI_MATCH(DMI_SYS_VENDOR, | |
1089 | "SAMSUNG ELECTRONICS CO., LTD."), | |
1090 | DMI_MATCH(DMI_PRODUCT_NAME, "R519/R719"), | |
1091 | DMI_MATCH(DMI_BOARD_NAME, "R519/R719"), | |
1092 | }, | |
1093 | .callback = dmi_check_cb, | |
1094 | }, | |
78a7539b TC |
1095 | { |
1096 | .ident = "N150/N210/N220", | |
1097 | .matches = { | |
1098 | DMI_MATCH(DMI_SYS_VENDOR, | |
1099 | "SAMSUNG ELECTRONICS CO., LTD."), | |
1100 | DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220"), | |
1101 | DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220"), | |
1102 | }, | |
1103 | .callback = dmi_check_cb, | |
1104 | }, | |
f689c875 RGS |
1105 | { |
1106 | .ident = "N220", | |
1107 | .matches = { | |
1108 | DMI_MATCH(DMI_SYS_VENDOR, | |
1109 | "SAMSUNG ELECTRONICS CO., LTD."), | |
1110 | DMI_MATCH(DMI_PRODUCT_NAME, "N220"), | |
1111 | DMI_MATCH(DMI_BOARD_NAME, "N220"), | |
1112 | }, | |
1113 | .callback = dmi_check_cb, | |
1114 | }, | |
2d70b73a | 1115 | { |
10165072 | 1116 | .ident = "N150/N210/N220/N230", |
2d70b73a GKH |
1117 | .matches = { |
1118 | DMI_MATCH(DMI_SYS_VENDOR, | |
1119 | "SAMSUNG ELECTRONICS CO., LTD."), | |
10165072 GKH |
1120 | DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220/N230"), |
1121 | DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220/N230"), | |
2d70b73a GKH |
1122 | }, |
1123 | .callback = dmi_check_cb, | |
1124 | }, | |
1125 | { | |
1126 | .ident = "N150P/N210P/N220P", | |
1127 | .matches = { | |
1128 | DMI_MATCH(DMI_SYS_VENDOR, | |
1129 | "SAMSUNG ELECTRONICS CO., LTD."), | |
1130 | DMI_MATCH(DMI_PRODUCT_NAME, "N150P/N210P/N220P"), | |
1131 | DMI_MATCH(DMI_BOARD_NAME, "N150P/N210P/N220P"), | |
1132 | }, | |
1133 | .callback = dmi_check_cb, | |
1134 | }, | |
f87d0299 SB |
1135 | { |
1136 | .ident = "R700", | |
1137 | .matches = { | |
1138 | DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | |
1139 | DMI_MATCH(DMI_PRODUCT_NAME, "SR700"), | |
1140 | DMI_MATCH(DMI_BOARD_NAME, "SR700"), | |
1141 | }, | |
1142 | .callback = dmi_check_cb, | |
1143 | }, | |
2d70b73a GKH |
1144 | { |
1145 | .ident = "R530/R730", | |
1146 | .matches = { | |
1147 | DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | |
1148 | DMI_MATCH(DMI_PRODUCT_NAME, "R530/R730"), | |
1149 | DMI_MATCH(DMI_BOARD_NAME, "R530/R730"), | |
1150 | }, | |
1151 | .callback = dmi_check_cb, | |
1152 | }, | |
1153 | { | |
1154 | .ident = "NF110/NF210/NF310", | |
1155 | .matches = { | |
1156 | DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | |
1157 | DMI_MATCH(DMI_PRODUCT_NAME, "NF110/NF210/NF310"), | |
1158 | DMI_MATCH(DMI_BOARD_NAME, "NF110/NF210/NF310"), | |
1159 | }, | |
1160 | .callback = dmi_check_cb, | |
1161 | }, | |
1162 | { | |
1163 | .ident = "N145P/N250P/N260P", | |
1164 | .matches = { | |
1165 | DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | |
1166 | DMI_MATCH(DMI_PRODUCT_NAME, "N145P/N250P/N260P"), | |
1167 | DMI_MATCH(DMI_BOARD_NAME, "N145P/N250P/N260P"), | |
1168 | }, | |
1169 | .callback = dmi_check_cb, | |
1170 | }, | |
1171 | { | |
1172 | .ident = "R70/R71", | |
1173 | .matches = { | |
1174 | DMI_MATCH(DMI_SYS_VENDOR, | |
1175 | "SAMSUNG ELECTRONICS CO., LTD."), | |
1176 | DMI_MATCH(DMI_PRODUCT_NAME, "R70/R71"), | |
1177 | DMI_MATCH(DMI_BOARD_NAME, "R70/R71"), | |
1178 | }, | |
1179 | .callback = dmi_check_cb, | |
1180 | }, | |
1181 | { | |
1182 | .ident = "P460", | |
1183 | .matches = { | |
1184 | DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | |
1185 | DMI_MATCH(DMI_PRODUCT_NAME, "P460"), | |
1186 | DMI_MATCH(DMI_BOARD_NAME, "P460"), | |
1187 | }, | |
1188 | .callback = dmi_check_cb, | |
1189 | }, | |
093ed561 SA |
1190 | { |
1191 | .ident = "R528/R728", | |
1192 | .matches = { | |
1193 | DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | |
1194 | DMI_MATCH(DMI_PRODUCT_NAME, "R528/R728"), | |
1195 | DMI_MATCH(DMI_BOARD_NAME, "R528/R728"), | |
1196 | }, | |
1197 | .callback = dmi_check_cb, | |
1198 | }, | |
7b3c257c JS |
1199 | { |
1200 | .ident = "NC210/NC110", | |
1201 | .matches = { | |
1202 | DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | |
1203 | DMI_MATCH(DMI_PRODUCT_NAME, "NC210/NC110"), | |
1204 | DMI_MATCH(DMI_BOARD_NAME, "NC210/NC110"), | |
1205 | }, | |
1206 | .callback = dmi_check_cb, | |
7500eeb0 TM |
1207 | }, |
1208 | { | |
1209 | .ident = "X520", | |
1210 | .matches = { | |
1211 | DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | |
1212 | DMI_MATCH(DMI_PRODUCT_NAME, "X520"), | |
1213 | DMI_MATCH(DMI_BOARD_NAME, "X520"), | |
1214 | }, | |
1215 | .callback = dmi_check_cb, | |
7b3c257c | 1216 | }, |
2d70b73a GKH |
1217 | { }, |
1218 | }; | |
1219 | MODULE_DEVICE_TABLE(dmi, samsung_dmi_table); | |
1220 | ||
5dea7a20 | 1221 | static struct platform_device *samsung_platform_device; |
2d70b73a GKH |
1222 | |
1223 | static int __init samsung_init(void) | |
1224 | { | |
5dea7a20 CC |
1225 | struct samsung_laptop *samsung; |
1226 | int ret; | |
2d70b73a | 1227 | |
2d70b73a GKH |
1228 | if (!force && !dmi_check_system(samsung_dmi_table)) |
1229 | return -ENODEV; | |
1230 | ||
a6df4894 CC |
1231 | samsung = kzalloc(sizeof(*samsung), GFP_KERNEL); |
1232 | if (!samsung) | |
1233 | return -ENOMEM; | |
1234 | ||
1235 | mutex_init(&samsung->sabi_mutex); | |
f34cd9ca CC |
1236 | samsung->handle_backlight = true; |
1237 | ||
1238 | #ifdef CONFIG_ACPI | |
1239 | /* Don't handle backlight here if the acpi video already handle it */ | |
1240 | if (acpi_video_backlight_support()) { | |
1241 | pr_info("Backlight controlled by ACPI video driver\n"); | |
1242 | samsung->handle_backlight = false; | |
1243 | } | |
1244 | #endif | |
a6df4894 | 1245 | |
5dea7a20 CC |
1246 | ret = samsung_platform_init(samsung); |
1247 | if (ret) | |
1248 | goto error_platform; | |
1249 | ||
1250 | ret = samsung_sabi_init(samsung); | |
1251 | if (ret) | |
1252 | goto error_sabi; | |
1253 | ||
1254 | ret = samsung_sysfs_init(samsung); | |
1255 | if (ret) | |
1256 | goto error_sysfs; | |
1257 | ||
1258 | ret = samsung_backlight_init(samsung); | |
1259 | if (ret) | |
1260 | goto error_backlight; | |
1261 | ||
1262 | ret = samsung_rfkill_init(samsung); | |
1263 | if (ret) | |
1264 | goto error_rfkill; | |
1265 | ||
5b80fc40 CC |
1266 | ret = samsung_debugfs_init(samsung); |
1267 | if (ret) | |
1268 | goto error_debugfs; | |
1269 | ||
5dea7a20 CC |
1270 | samsung_platform_device = samsung->platform_device; |
1271 | return ret; | |
1272 | ||
5b80fc40 CC |
1273 | error_debugfs: |
1274 | samsung_rfkill_exit(samsung); | |
5dea7a20 CC |
1275 | error_rfkill: |
1276 | samsung_backlight_exit(samsung); | |
1277 | error_backlight: | |
1278 | samsung_sysfs_exit(samsung); | |
1279 | error_sysfs: | |
1280 | samsung_sabi_exit(samsung); | |
1281 | error_sabi: | |
1282 | samsung_platform_exit(samsung); | |
1283 | error_platform: | |
a6df4894 | 1284 | kfree(samsung); |
5dea7a20 | 1285 | return ret; |
2d70b73a GKH |
1286 | } |
1287 | ||
1288 | static void __exit samsung_exit(void) | |
1289 | { | |
5dea7a20 CC |
1290 | struct samsung_laptop *samsung; |
1291 | ||
1292 | samsung = platform_get_drvdata(samsung_platform_device); | |
1293 | ||
5b80fc40 | 1294 | samsung_debugfs_exit(samsung); |
5dea7a20 CC |
1295 | samsung_rfkill_exit(samsung); |
1296 | samsung_backlight_exit(samsung); | |
1297 | samsung_sysfs_exit(samsung); | |
1298 | samsung_sabi_exit(samsung); | |
1299 | samsung_platform_exit(samsung); | |
a6df4894 | 1300 | |
a6df4894 | 1301 | kfree(samsung); |
5dea7a20 | 1302 | samsung_platform_device = NULL; |
2d70b73a GKH |
1303 | } |
1304 | ||
1305 | module_init(samsung_init); | |
1306 | module_exit(samsung_exit); | |
1307 | ||
1308 | MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@suse.de>"); | |
1309 | MODULE_DESCRIPTION("Samsung Backlight driver"); | |
1310 | MODULE_LICENSE("GPL"); |