diff options
Diffstat (limited to 'drivers/platform/x86/samsung-laptop.c')
-rw-r--r-- | drivers/platform/x86/samsung-laptop.c | 1749 |
1 files changed, 1206 insertions, 543 deletions
diff --git a/drivers/platform/x86/samsung-laptop.c b/drivers/platform/x86/samsung-laptop.c index fd73ea89b857..e2a34b42ddc1 100644 --- a/drivers/platform/x86/samsung-laptop.c +++ b/drivers/platform/x86/samsung-laptop.c | |||
@@ -17,10 +17,18 @@ | |||
17 | #include <linux/delay.h> | 17 | #include <linux/delay.h> |
18 | #include <linux/pci.h> | 18 | #include <linux/pci.h> |
19 | #include <linux/backlight.h> | 19 | #include <linux/backlight.h> |
20 | #include <linux/leds.h> | ||
20 | #include <linux/fb.h> | 21 | #include <linux/fb.h> |
21 | #include <linux/dmi.h> | 22 | #include <linux/dmi.h> |
22 | #include <linux/platform_device.h> | 23 | #include <linux/platform_device.h> |
23 | #include <linux/rfkill.h> | 24 | #include <linux/rfkill.h> |
25 | #include <linux/acpi.h> | ||
26 | #include <linux/seq_file.h> | ||
27 | #include <linux/debugfs.h> | ||
28 | #include <linux/ctype.h> | ||
29 | #if (defined CONFIG_ACPI_VIDEO || defined CONFIG_ACPI_VIDEO_MODULE) | ||
30 | #include <acpi/video.h> | ||
31 | #endif | ||
24 | 32 | ||
25 | /* | 33 | /* |
26 | * This driver is needed because a number of Samsung laptops do not hook | 34 | * This driver is needed because a number of Samsung laptops do not hook |
@@ -41,9 +49,20 @@ | |||
41 | #define SABI_IFACE_COMPLETE 0x04 | 49 | #define SABI_IFACE_COMPLETE 0x04 |
42 | #define SABI_IFACE_DATA 0x05 | 50 | #define SABI_IFACE_DATA 0x05 |
43 | 51 | ||
44 | /* Structure to get data back to the calling function */ | 52 | #define WL_STATUS_WLAN 0x0 |
45 | struct sabi_retval { | 53 | #define WL_STATUS_BT 0x2 |
46 | u8 retval[20]; | 54 | |
55 | /* Structure get/set data using sabi */ | ||
56 | struct sabi_data { | ||
57 | union { | ||
58 | struct { | ||
59 | u32 d0; | ||
60 | u32 d1; | ||
61 | u16 d2; | ||
62 | u8 d3; | ||
63 | }; | ||
64 | u8 data[11]; | ||
65 | }; | ||
47 | }; | 66 | }; |
48 | 67 | ||
49 | struct sabi_header_offsets { | 68 | struct sabi_header_offsets { |
@@ -60,8 +79,8 @@ struct sabi_commands { | |||
60 | * Brightness is 0 - 8, as described above. | 79 | * Brightness is 0 - 8, as described above. |
61 | * Value 0 is for the BIOS to use | 80 | * Value 0 is for the BIOS to use |
62 | */ | 81 | */ |
63 | u8 get_brightness; | 82 | u16 get_brightness; |
64 | u8 set_brightness; | 83 | u16 set_brightness; |
65 | 84 | ||
66 | /* | 85 | /* |
67 | * first byte: | 86 | * first byte: |
@@ -72,40 +91,56 @@ struct sabi_commands { | |||
72 | * 0x03 - 3G is on | 91 | * 0x03 - 3G is on |
73 | * TODO, verify 3G is correct, that doesn't seem right... | 92 | * TODO, verify 3G is correct, that doesn't seem right... |
74 | */ | 93 | */ |
75 | u8 get_wireless_button; | 94 | u16 get_wireless_button; |
76 | u8 set_wireless_button; | 95 | u16 set_wireless_button; |
77 | 96 | ||
78 | /* 0 is off, 1 is on */ | 97 | /* 0 is off, 1 is on */ |
79 | u8 get_backlight; | 98 | u16 get_backlight; |
80 | u8 set_backlight; | 99 | u16 set_backlight; |
81 | 100 | ||
82 | /* | 101 | /* |
83 | * 0x80 or 0x00 - no action | 102 | * 0x80 or 0x00 - no action |
84 | * 0x81 - recovery key pressed | 103 | * 0x81 - recovery key pressed |
85 | */ | 104 | */ |
86 | u8 get_recovery_mode; | 105 | u16 get_recovery_mode; |
87 | u8 set_recovery_mode; | 106 | u16 set_recovery_mode; |
88 | 107 | ||
89 | /* | 108 | /* |
90 | * on seclinux: 0 is low, 1 is high, | 109 | * on seclinux: 0 is low, 1 is high, |
91 | * on swsmi: 0 is normal, 1 is silent, 2 is turbo | 110 | * on swsmi: 0 is normal, 1 is silent, 2 is turbo |
92 | */ | 111 | */ |
93 | u8 get_performance_level; | 112 | u16 get_performance_level; |
94 | u8 set_performance_level; | 113 | u16 set_performance_level; |
114 | |||
115 | /* 0x80 is off, 0x81 is on */ | ||
116 | u16 get_battery_life_extender; | ||
117 | u16 set_battery_life_extender; | ||
118 | |||
119 | /* 0x80 is off, 0x81 is on */ | ||
120 | u16 get_usb_charge; | ||
121 | u16 set_usb_charge; | ||
122 | |||
123 | /* the first byte is for bluetooth and the third one is for wlan */ | ||
124 | u16 get_wireless_status; | ||
125 | u16 set_wireless_status; | ||
126 | |||
127 | /* 0x81 to read, (0x82 | level << 8) to set, 0xaabb to enable */ | ||
128 | u16 kbd_backlight; | ||
95 | 129 | ||
96 | /* | 130 | /* |
97 | * Tell the BIOS that Linux is running on this machine. | 131 | * Tell the BIOS that Linux is running on this machine. |
98 | * 81 is on, 80 is off | 132 | * 81 is on, 80 is off |
99 | */ | 133 | */ |
100 | u8 set_linux; | 134 | u16 set_linux; |
101 | }; | 135 | }; |
102 | 136 | ||
103 | struct sabi_performance_level { | 137 | struct sabi_performance_level { |
104 | const char *name; | 138 | const char *name; |
105 | u8 value; | 139 | u16 value; |
106 | }; | 140 | }; |
107 | 141 | ||
108 | struct sabi_config { | 142 | struct sabi_config { |
143 | int sabi_version; | ||
109 | const char *test_string; | 144 | const char *test_string; |
110 | u16 main_function; | 145 | u16 main_function; |
111 | const struct sabi_header_offsets header_offsets; | 146 | const struct sabi_header_offsets header_offsets; |
@@ -117,6 +152,10 @@ struct sabi_config { | |||
117 | 152 | ||
118 | static const struct sabi_config sabi_configs[] = { | 153 | static const struct sabi_config sabi_configs[] = { |
119 | { | 154 | { |
155 | /* I don't know if it is really 2, but it it is | ||
156 | * less than 3 anyway */ | ||
157 | .sabi_version = 2, | ||
158 | |||
120 | .test_string = "SECLINUX", | 159 | .test_string = "SECLINUX", |
121 | 160 | ||
122 | .main_function = 0x4c49, | 161 | .main_function = 0x4c49, |
@@ -146,6 +185,17 @@ static const struct sabi_config sabi_configs[] = { | |||
146 | .get_performance_level = 0x08, | 185 | .get_performance_level = 0x08, |
147 | .set_performance_level = 0x09, | 186 | .set_performance_level = 0x09, |
148 | 187 | ||
188 | .get_battery_life_extender = 0xFFFF, | ||
189 | .set_battery_life_extender = 0xFFFF, | ||
190 | |||
191 | .get_usb_charge = 0xFFFF, | ||
192 | .set_usb_charge = 0xFFFF, | ||
193 | |||
194 | .get_wireless_status = 0xFFFF, | ||
195 | .set_wireless_status = 0xFFFF, | ||
196 | |||
197 | .kbd_backlight = 0xFFFF, | ||
198 | |||
149 | .set_linux = 0x0a, | 199 | .set_linux = 0x0a, |
150 | }, | 200 | }, |
151 | 201 | ||
@@ -164,6 +214,8 @@ static const struct sabi_config sabi_configs[] = { | |||
164 | .max_brightness = 8, | 214 | .max_brightness = 8, |
165 | }, | 215 | }, |
166 | { | 216 | { |
217 | .sabi_version = 3, | ||
218 | |||
167 | .test_string = "SwSmi@", | 219 | .test_string = "SwSmi@", |
168 | 220 | ||
169 | .main_function = 0x5843, | 221 | .main_function = 0x5843, |
@@ -193,6 +245,17 @@ static const struct sabi_config sabi_configs[] = { | |||
193 | .get_performance_level = 0x31, | 245 | .get_performance_level = 0x31, |
194 | .set_performance_level = 0x32, | 246 | .set_performance_level = 0x32, |
195 | 247 | ||
248 | .get_battery_life_extender = 0x65, | ||
249 | .set_battery_life_extender = 0x66, | ||
250 | |||
251 | .get_usb_charge = 0x67, | ||
252 | .set_usb_charge = 0x68, | ||
253 | |||
254 | .get_wireless_status = 0x69, | ||
255 | .set_wireless_status = 0x6a, | ||
256 | |||
257 | .kbd_backlight = 0x78, | ||
258 | |||
196 | .set_linux = 0xff, | 259 | .set_linux = 0xff, |
197 | }, | 260 | }, |
198 | 261 | ||
@@ -217,16 +280,82 @@ static const struct sabi_config sabi_configs[] = { | |||
217 | { }, | 280 | { }, |
218 | }; | 281 | }; |
219 | 282 | ||
220 | static const struct sabi_config *sabi_config; | 283 | /* |
284 | * samsung-laptop/ - debugfs root directory | ||
285 | * f0000_segment - dump f0000 segment | ||
286 | * command - current command | ||
287 | * data - current data | ||
288 | * d0, d1, d2, d3 - data fields | ||
289 | * call - call SABI using command and data | ||
290 | * | ||
291 | * This allow to call arbitrary sabi commands wihout | ||
292 | * modifying the driver at all. | ||
293 | * For example, setting the keyboard backlight brightness to 5 | ||
294 | * | ||
295 | * echo 0x78 > command | ||
296 | * echo 0x0582 > d0 | ||
297 | * echo 0 > d1 | ||
298 | * echo 0 > d2 | ||
299 | * echo 0 > d3 | ||
300 | * cat call | ||
301 | */ | ||
302 | |||
303 | struct samsung_laptop_debug { | ||
304 | struct dentry *root; | ||
305 | struct sabi_data data; | ||
306 | u16 command; | ||
307 | |||
308 | struct debugfs_blob_wrapper f0000_wrapper; | ||
309 | struct debugfs_blob_wrapper data_wrapper; | ||
310 | struct debugfs_blob_wrapper sdiag_wrapper; | ||
311 | }; | ||
312 | |||
313 | struct samsung_laptop; | ||
314 | |||
315 | struct samsung_rfkill { | ||
316 | struct samsung_laptop *samsung; | ||
317 | struct rfkill *rfkill; | ||
318 | enum rfkill_type type; | ||
319 | }; | ||
320 | |||
321 | struct samsung_laptop { | ||
322 | const struct sabi_config *config; | ||
323 | |||
324 | void __iomem *sabi; | ||
325 | void __iomem *sabi_iface; | ||
326 | void __iomem *f0000_segment; | ||
327 | |||
328 | struct mutex sabi_mutex; | ||
329 | |||
330 | struct platform_device *platform_device; | ||
331 | struct backlight_device *backlight_device; | ||
332 | |||
333 | struct samsung_rfkill wlan; | ||
334 | struct samsung_rfkill bluetooth; | ||
335 | |||
336 | struct led_classdev kbd_led; | ||
337 | int kbd_led_wk; | ||
338 | struct workqueue_struct *led_workqueue; | ||
339 | struct work_struct kbd_led_work; | ||
340 | |||
341 | struct samsung_laptop_debug debug; | ||
342 | struct samsung_quirks *quirks; | ||
343 | |||
344 | bool handle_backlight; | ||
345 | bool has_stepping_quirk; | ||
346 | |||
347 | char sdiag[64]; | ||
348 | }; | ||
349 | |||
350 | struct samsung_quirks { | ||
351 | bool broken_acpi_video; | ||
352 | }; | ||
353 | |||
354 | static struct samsung_quirks samsung_unknown = {}; | ||
221 | 355 | ||
222 | static void __iomem *sabi; | 356 | static struct samsung_quirks samsung_broken_acpi_video = { |
223 | static void __iomem *sabi_iface; | 357 | .broken_acpi_video = true, |
224 | static void __iomem *f0000_segment; | 358 | }; |
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 | static bool has_stepping_quirk; | ||
230 | 359 | ||
231 | static bool force; | 360 | static bool force; |
232 | module_param(force, bool, 0); | 361 | module_param(force, bool, 0); |
@@ -237,176 +366,143 @@ static bool debug; | |||
237 | module_param(debug, bool, S_IRUGO | S_IWUSR); | 366 | module_param(debug, bool, S_IRUGO | S_IWUSR); |
238 | MODULE_PARM_DESC(debug, "Debug enabled or not"); | 367 | MODULE_PARM_DESC(debug, "Debug enabled or not"); |
239 | 368 | ||
240 | static int sabi_get_command(u8 command, struct sabi_retval *sretval) | 369 | static int sabi_command(struct samsung_laptop *samsung, u16 command, |
370 | struct sabi_data *in, | ||
371 | struct sabi_data *out) | ||
241 | { | 372 | { |
242 | int retval = 0; | 373 | const struct sabi_config *config = samsung->config; |
243 | u16 port = readw(sabi + sabi_config->header_offsets.port); | 374 | int ret = 0; |
375 | u16 port = readw(samsung->sabi + config->header_offsets.port); | ||
244 | u8 complete, iface_data; | 376 | u8 complete, iface_data; |
245 | 377 | ||
246 | mutex_lock(&sabi_mutex); | 378 | mutex_lock(&samsung->sabi_mutex); |
247 | |||
248 | /* enable memory to be able to write to it */ | ||
249 | outb(readb(sabi + sabi_config->header_offsets.en_mem), port); | ||
250 | |||
251 | /* write out the command */ | ||
252 | writew(sabi_config->main_function, sabi_iface + SABI_IFACE_MAIN); | ||
253 | writew(command, sabi_iface + SABI_IFACE_SUB); | ||
254 | writeb(0, sabi_iface + SABI_IFACE_COMPLETE); | ||
255 | outb(readb(sabi + sabi_config->header_offsets.iface_func), port); | ||
256 | |||
257 | /* write protect memory to make it safe */ | ||
258 | outb(readb(sabi + sabi_config->header_offsets.re_mem), port); | ||
259 | 379 | ||
260 | /* see if the command actually succeeded */ | 380 | if (debug) { |
261 | complete = readb(sabi_iface + SABI_IFACE_COMPLETE); | 381 | if (in) |
262 | iface_data = readb(sabi_iface + SABI_IFACE_DATA); | 382 | pr_info("SABI command:0x%04x " |
263 | if (complete != 0xaa || iface_data == 0xff) { | 383 | "data:{0x%08x, 0x%08x, 0x%04x, 0x%02x}", |
264 | pr_warn("SABI get command 0x%02x failed with completion flag 0x%02x and data 0x%02x\n", | 384 | command, in->d0, in->d1, in->d2, in->d3); |
265 | command, complete, iface_data); | 385 | else |
266 | retval = -EINVAL; | 386 | pr_info("SABI command:0x%04x", command); |
267 | goto exit; | ||
268 | } | 387 | } |
269 | /* | ||
270 | * Save off the data into a structure so the caller use it. | ||
271 | * Right now we only want the first 4 bytes, | ||
272 | * There are commands that need more, but not for the ones we | ||
273 | * currently care about. | ||
274 | */ | ||
275 | sretval->retval[0] = readb(sabi_iface + SABI_IFACE_DATA); | ||
276 | sretval->retval[1] = readb(sabi_iface + SABI_IFACE_DATA + 1); | ||
277 | sretval->retval[2] = readb(sabi_iface + SABI_IFACE_DATA + 2); | ||
278 | sretval->retval[3] = readb(sabi_iface + SABI_IFACE_DATA + 3); | ||
279 | |||
280 | exit: | ||
281 | mutex_unlock(&sabi_mutex); | ||
282 | return retval; | ||
283 | |||
284 | } | ||
285 | |||
286 | static int sabi_set_command(u8 command, u8 data) | ||
287 | { | ||
288 | int retval = 0; | ||
289 | u16 port = readw(sabi + sabi_config->header_offsets.port); | ||
290 | u8 complete, iface_data; | ||
291 | |||
292 | mutex_lock(&sabi_mutex); | ||
293 | 388 | ||
294 | /* enable memory to be able to write to it */ | 389 | /* enable memory to be able to write to it */ |
295 | outb(readb(sabi + sabi_config->header_offsets.en_mem), port); | 390 | outb(readb(samsung->sabi + config->header_offsets.en_mem), port); |
296 | 391 | ||
297 | /* write out the command */ | 392 | /* write out the command */ |
298 | writew(sabi_config->main_function, sabi_iface + SABI_IFACE_MAIN); | 393 | writew(config->main_function, samsung->sabi_iface + SABI_IFACE_MAIN); |
299 | writew(command, sabi_iface + SABI_IFACE_SUB); | 394 | writew(command, samsung->sabi_iface + SABI_IFACE_SUB); |
300 | writeb(0, sabi_iface + SABI_IFACE_COMPLETE); | 395 | writeb(0, samsung->sabi_iface + SABI_IFACE_COMPLETE); |
301 | writeb(data, sabi_iface + SABI_IFACE_DATA); | 396 | if (in) { |
302 | outb(readb(sabi + sabi_config->header_offsets.iface_func), port); | 397 | writel(in->d0, samsung->sabi_iface + SABI_IFACE_DATA); |
398 | writel(in->d1, samsung->sabi_iface + SABI_IFACE_DATA + 4); | ||
399 | writew(in->d2, samsung->sabi_iface + SABI_IFACE_DATA + 8); | ||
400 | writeb(in->d3, samsung->sabi_iface + SABI_IFACE_DATA + 10); | ||
401 | } | ||
402 | outb(readb(samsung->sabi + config->header_offsets.iface_func), port); | ||
303 | 403 | ||
304 | /* write protect memory to make it safe */ | 404 | /* write protect memory to make it safe */ |
305 | outb(readb(sabi + sabi_config->header_offsets.re_mem), port); | 405 | outb(readb(samsung->sabi + config->header_offsets.re_mem), port); |
306 | 406 | ||
307 | /* see if the command actually succeeded */ | 407 | /* see if the command actually succeeded */ |
308 | complete = readb(sabi_iface + SABI_IFACE_COMPLETE); | 408 | complete = readb(samsung->sabi_iface + SABI_IFACE_COMPLETE); |
309 | iface_data = readb(sabi_iface + SABI_IFACE_DATA); | 409 | iface_data = readb(samsung->sabi_iface + SABI_IFACE_DATA); |
410 | |||
411 | /* iface_data = 0xFF happens when a command is not known | ||
412 | * so we only add a warning in debug mode since we will | ||
413 | * probably issue some unknown command at startup to find | ||
414 | * out which features are supported */ | ||
415 | if (complete != 0xaa || (iface_data == 0xff && debug)) | ||
416 | pr_warn("SABI command 0x%04x failed with" | ||
417 | " completion flag 0x%02x and interface data 0x%02x", | ||
418 | command, complete, iface_data); | ||
419 | |||
310 | if (complete != 0xaa || iface_data == 0xff) { | 420 | if (complete != 0xaa || iface_data == 0xff) { |
311 | pr_warn("SABI set command 0x%02x failed with completion flag 0x%02x and data 0x%02x\n", | 421 | ret = -EINVAL; |
312 | command, complete, iface_data); | 422 | goto exit; |
313 | retval = -EINVAL; | ||
314 | } | 423 | } |
315 | 424 | ||
316 | mutex_unlock(&sabi_mutex); | 425 | if (out) { |
317 | return retval; | 426 | out->d0 = readl(samsung->sabi_iface + SABI_IFACE_DATA); |
318 | } | 427 | out->d1 = readl(samsung->sabi_iface + SABI_IFACE_DATA + 4); |
319 | 428 | out->d2 = readw(samsung->sabi_iface + SABI_IFACE_DATA + 2); | |
320 | static void test_backlight(void) | 429 | out->d3 = readb(samsung->sabi_iface + SABI_IFACE_DATA + 1); |
321 | { | 430 | } |
322 | struct sabi_retval sretval; | ||
323 | |||
324 | sabi_get_command(sabi_config->commands.get_backlight, &sretval); | ||
325 | printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]); | ||
326 | |||
327 | sabi_set_command(sabi_config->commands.set_backlight, 0); | ||
328 | printk(KERN_DEBUG "backlight should be off\n"); | ||
329 | |||
330 | sabi_get_command(sabi_config->commands.get_backlight, &sretval); | ||
331 | printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]); | ||
332 | |||
333 | msleep(1000); | ||
334 | 431 | ||
335 | sabi_set_command(sabi_config->commands.set_backlight, 1); | 432 | if (debug && out) { |
336 | printk(KERN_DEBUG "backlight should be on\n"); | 433 | pr_info("SABI return data:{0x%08x, 0x%08x, 0x%04x, 0x%02x}", |
434 | out->d0, out->d1, out->d2, out->d3); | ||
435 | } | ||
337 | 436 | ||
338 | sabi_get_command(sabi_config->commands.get_backlight, &sretval); | 437 | exit: |
339 | printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]); | 438 | mutex_unlock(&samsung->sabi_mutex); |
439 | return ret; | ||
340 | } | 440 | } |
341 | 441 | ||
342 | static void test_wireless(void) | 442 | /* simple wrappers usable with most commands */ |
443 | static int sabi_set_commandb(struct samsung_laptop *samsung, | ||
444 | u16 command, u8 data) | ||
343 | { | 445 | { |
344 | struct sabi_retval sretval; | 446 | struct sabi_data in = { { { .d0 = 0, .d1 = 0, .d2 = 0, .d3 = 0 } } }; |
345 | |||
346 | sabi_get_command(sabi_config->commands.get_wireless_button, &sretval); | ||
347 | printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]); | ||
348 | |||
349 | sabi_set_command(sabi_config->commands.set_wireless_button, 0); | ||
350 | printk(KERN_DEBUG "wireless led should be off\n"); | ||
351 | |||
352 | sabi_get_command(sabi_config->commands.get_wireless_button, &sretval); | ||
353 | printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]); | ||
354 | 447 | ||
355 | msleep(1000); | 448 | in.data[0] = data; |
356 | 449 | return sabi_command(samsung, command, &in, NULL); | |
357 | sabi_set_command(sabi_config->commands.set_wireless_button, 1); | ||
358 | printk(KERN_DEBUG "wireless led should be on\n"); | ||
359 | |||
360 | sabi_get_command(sabi_config->commands.get_wireless_button, &sretval); | ||
361 | printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]); | ||
362 | } | 450 | } |
363 | 451 | ||
364 | static u8 read_brightness(void) | 452 | static int read_brightness(struct samsung_laptop *samsung) |
365 | { | 453 | { |
366 | struct sabi_retval sretval; | 454 | const struct sabi_config *config = samsung->config; |
455 | const struct sabi_commands *commands = &samsung->config->commands; | ||
456 | struct sabi_data sretval; | ||
367 | int user_brightness = 0; | 457 | int user_brightness = 0; |
368 | int retval; | 458 | int retval; |
369 | 459 | ||
370 | retval = sabi_get_command(sabi_config->commands.get_brightness, | 460 | retval = sabi_command(samsung, commands->get_brightness, |
371 | &sretval); | 461 | NULL, &sretval); |
372 | if (!retval) { | 462 | if (retval) |
373 | user_brightness = sretval.retval[0]; | 463 | return retval; |
374 | if (user_brightness > sabi_config->min_brightness) | 464 | |
375 | user_brightness -= sabi_config->min_brightness; | 465 | user_brightness = sretval.data[0]; |
376 | else | 466 | if (user_brightness > config->min_brightness) |
377 | user_brightness = 0; | 467 | user_brightness -= config->min_brightness; |
378 | } | 468 | else |
469 | user_brightness = 0; | ||
470 | |||
379 | return user_brightness; | 471 | return user_brightness; |
380 | } | 472 | } |
381 | 473 | ||
382 | static void set_brightness(u8 user_brightness) | 474 | static void set_brightness(struct samsung_laptop *samsung, u8 user_brightness) |
383 | { | 475 | { |
384 | u8 user_level = user_brightness + sabi_config->min_brightness; | 476 | const struct sabi_config *config = samsung->config; |
477 | const struct sabi_commands *commands = &samsung->config->commands; | ||
478 | u8 user_level = user_brightness + config->min_brightness; | ||
385 | 479 | ||
386 | if (has_stepping_quirk && user_level != 0) { | 480 | if (samsung->has_stepping_quirk && user_level != 0) { |
387 | /* | 481 | /* |
388 | * short circuit if the specified level is what's already set | 482 | * short circuit if the specified level is what's already set |
389 | * to prevent the screen from flickering needlessly | 483 | * to prevent the screen from flickering needlessly |
390 | */ | 484 | */ |
391 | if (user_brightness == read_brightness()) | 485 | if (user_brightness == read_brightness(samsung)) |
392 | return; | 486 | return; |
393 | 487 | ||
394 | sabi_set_command(sabi_config->commands.set_brightness, 0); | 488 | sabi_set_commandb(samsung, commands->set_brightness, 0); |
395 | } | 489 | } |
396 | 490 | ||
397 | sabi_set_command(sabi_config->commands.set_brightness, user_level); | 491 | sabi_set_commandb(samsung, commands->set_brightness, user_level); |
398 | } | 492 | } |
399 | 493 | ||
400 | static int get_brightness(struct backlight_device *bd) | 494 | static int get_brightness(struct backlight_device *bd) |
401 | { | 495 | { |
402 | return (int)read_brightness(); | 496 | struct samsung_laptop *samsung = bl_get_data(bd); |
497 | |||
498 | return read_brightness(samsung); | ||
403 | } | 499 | } |
404 | 500 | ||
405 | static void check_for_stepping_quirk(void) | 501 | static void check_for_stepping_quirk(struct samsung_laptop *samsung) |
406 | { | 502 | { |
407 | u8 initial_level; | 503 | int initial_level; |
408 | u8 check_level; | 504 | int check_level; |
409 | u8 orig_level = read_brightness(); | 505 | int orig_level = read_brightness(samsung); |
410 | 506 | ||
411 | /* | 507 | /* |
412 | * Some laptops exhibit the strange behaviour of stepping toward | 508 | * Some laptops exhibit the strange behaviour of stepping toward |
@@ -416,34 +512,38 @@ static void check_for_stepping_quirk(void) | |||
416 | */ | 512 | */ |
417 | 513 | ||
418 | if (orig_level == 0) | 514 | if (orig_level == 0) |
419 | set_brightness(1); | 515 | set_brightness(samsung, 1); |
420 | 516 | ||
421 | initial_level = read_brightness(); | 517 | initial_level = read_brightness(samsung); |
422 | 518 | ||
423 | if (initial_level <= 2) | 519 | if (initial_level <= 2) |
424 | check_level = initial_level + 2; | 520 | check_level = initial_level + 2; |
425 | else | 521 | else |
426 | check_level = initial_level - 2; | 522 | check_level = initial_level - 2; |
427 | 523 | ||
428 | has_stepping_quirk = false; | 524 | samsung->has_stepping_quirk = false; |
429 | set_brightness(check_level); | 525 | set_brightness(samsung, check_level); |
430 | 526 | ||
431 | if (read_brightness() != check_level) { | 527 | if (read_brightness(samsung) != check_level) { |
432 | has_stepping_quirk = true; | 528 | samsung->has_stepping_quirk = true; |
433 | pr_info("enabled workaround for brightness stepping quirk\n"); | 529 | pr_info("enabled workaround for brightness stepping quirk\n"); |
434 | } | 530 | } |
435 | 531 | ||
436 | set_brightness(orig_level); | 532 | set_brightness(samsung, orig_level); |
437 | } | 533 | } |
438 | 534 | ||
439 | static int update_status(struct backlight_device *bd) | 535 | static int update_status(struct backlight_device *bd) |
440 | { | 536 | { |
441 | set_brightness(bd->props.brightness); | 537 | struct samsung_laptop *samsung = bl_get_data(bd); |
538 | const struct sabi_commands *commands = &samsung->config->commands; | ||
539 | |||
540 | set_brightness(samsung, bd->props.brightness); | ||
442 | 541 | ||
443 | if (bd->props.power == FB_BLANK_UNBLANK) | 542 | if (bd->props.power == FB_BLANK_UNBLANK) |
444 | sabi_set_command(sabi_config->commands.set_backlight, 1); | 543 | sabi_set_commandb(samsung, commands->set_backlight, 1); |
445 | else | 544 | else |
446 | sabi_set_command(sabi_config->commands.set_backlight, 0); | 545 | sabi_set_commandb(samsung, commands->set_backlight, 0); |
546 | |||
447 | return 0; | 547 | return 0; |
448 | } | 548 | } |
449 | 549 | ||
@@ -452,66 +552,101 @@ static const struct backlight_ops backlight_ops = { | |||
452 | .update_status = update_status, | 552 | .update_status = update_status, |
453 | }; | 553 | }; |
454 | 554 | ||
455 | static int rfkill_set(void *data, bool blocked) | 555 | static int seclinux_rfkill_set(void *data, bool blocked) |
456 | { | 556 | { |
457 | /* Do something with blocked...*/ | 557 | struct samsung_rfkill *srfkill = data; |
458 | /* | 558 | struct samsung_laptop *samsung = srfkill->samsung; |
459 | * blocked == false is on | 559 | const struct sabi_commands *commands = &samsung->config->commands; |
460 | * blocked == true is off | ||
461 | */ | ||
462 | if (blocked) | ||
463 | sabi_set_command(sabi_config->commands.set_wireless_button, 0); | ||
464 | else | ||
465 | sabi_set_command(sabi_config->commands.set_wireless_button, 1); | ||
466 | 560 | ||
467 | return 0; | 561 | return sabi_set_commandb(samsung, commands->set_wireless_button, |
562 | !blocked); | ||
468 | } | 563 | } |
469 | 564 | ||
470 | static struct rfkill_ops rfkill_ops = { | 565 | static struct rfkill_ops seclinux_rfkill_ops = { |
471 | .set_block = rfkill_set, | 566 | .set_block = seclinux_rfkill_set, |
472 | }; | 567 | }; |
473 | 568 | ||
474 | static int init_wireless(struct platform_device *sdev) | 569 | static int swsmi_wireless_status(struct samsung_laptop *samsung, |
570 | struct sabi_data *data) | ||
475 | { | 571 | { |
476 | int retval; | 572 | const struct sabi_commands *commands = &samsung->config->commands; |
477 | 573 | ||
478 | rfk = rfkill_alloc("samsung-wifi", &sdev->dev, RFKILL_TYPE_WLAN, | 574 | return sabi_command(samsung, commands->get_wireless_status, |
479 | &rfkill_ops, NULL); | 575 | NULL, data); |
480 | if (!rfk) | 576 | } |
481 | return -ENOMEM; | ||
482 | |||
483 | retval = rfkill_register(rfk); | ||
484 | if (retval) { | ||
485 | rfkill_destroy(rfk); | ||
486 | return -ENODEV; | ||
487 | } | ||
488 | 577 | ||
489 | return 0; | 578 | static int swsmi_rfkill_set(void *priv, bool blocked) |
579 | { | ||
580 | struct samsung_rfkill *srfkill = priv; | ||
581 | struct samsung_laptop *samsung = srfkill->samsung; | ||
582 | const struct sabi_commands *commands = &samsung->config->commands; | ||
583 | struct sabi_data data; | ||
584 | int ret, i; | ||
585 | |||
586 | ret = swsmi_wireless_status(samsung, &data); | ||
587 | if (ret) | ||
588 | return ret; | ||
589 | |||
590 | /* Don't set the state for non-present devices */ | ||
591 | for (i = 0; i < 4; i++) | ||
592 | if (data.data[i] == 0x02) | ||
593 | data.data[1] = 0; | ||
594 | |||
595 | if (srfkill->type == RFKILL_TYPE_WLAN) | ||
596 | data.data[WL_STATUS_WLAN] = !blocked; | ||
597 | else if (srfkill->type == RFKILL_TYPE_BLUETOOTH) | ||
598 | data.data[WL_STATUS_BT] = !blocked; | ||
599 | |||
600 | return sabi_command(samsung, commands->set_wireless_status, | ||
601 | &data, &data); | ||
490 | } | 602 | } |
491 | 603 | ||
492 | static void destroy_wireless(void) | 604 | static void swsmi_rfkill_query(struct rfkill *rfkill, void *priv) |
493 | { | 605 | { |
494 | rfkill_unregister(rfk); | 606 | struct samsung_rfkill *srfkill = priv; |
495 | rfkill_destroy(rfk); | 607 | struct samsung_laptop *samsung = srfkill->samsung; |
608 | struct sabi_data data; | ||
609 | int ret; | ||
610 | |||
611 | ret = swsmi_wireless_status(samsung, &data); | ||
612 | if (ret) | ||
613 | return ; | ||
614 | |||
615 | if (srfkill->type == RFKILL_TYPE_WLAN) | ||
616 | ret = data.data[WL_STATUS_WLAN]; | ||
617 | else if (srfkill->type == RFKILL_TYPE_BLUETOOTH) | ||
618 | ret = data.data[WL_STATUS_BT]; | ||
619 | else | ||
620 | return ; | ||
621 | |||
622 | rfkill_set_sw_state(rfkill, !ret); | ||
496 | } | 623 | } |
497 | 624 | ||
625 | static struct rfkill_ops swsmi_rfkill_ops = { | ||
626 | .set_block = swsmi_rfkill_set, | ||
627 | .query = swsmi_rfkill_query, | ||
628 | }; | ||
629 | |||
498 | static ssize_t get_performance_level(struct device *dev, | 630 | static ssize_t get_performance_level(struct device *dev, |
499 | struct device_attribute *attr, char *buf) | 631 | struct device_attribute *attr, char *buf) |
500 | { | 632 | { |
501 | struct sabi_retval sretval; | 633 | struct samsung_laptop *samsung = dev_get_drvdata(dev); |
634 | const struct sabi_config *config = samsung->config; | ||
635 | const struct sabi_commands *commands = &config->commands; | ||
636 | struct sabi_data sretval; | ||
502 | int retval; | 637 | int retval; |
503 | int i; | 638 | int i; |
504 | 639 | ||
505 | /* Read the state */ | 640 | /* Read the state */ |
506 | retval = sabi_get_command(sabi_config->commands.get_performance_level, | 641 | retval = sabi_command(samsung, commands->get_performance_level, |
507 | &sretval); | 642 | NULL, &sretval); |
508 | if (retval) | 643 | if (retval) |
509 | return retval; | 644 | return retval; |
510 | 645 | ||
511 | /* The logic is backwards, yeah, lots of fun... */ | 646 | /* The logic is backwards, yeah, lots of fun... */ |
512 | for (i = 0; sabi_config->performance_levels[i].name; ++i) { | 647 | for (i = 0; config->performance_levels[i].name; ++i) { |
513 | if (sretval.retval[0] == sabi_config->performance_levels[i].value) | 648 | if (sretval.data[0] == config->performance_levels[i].value) |
514 | return sprintf(buf, "%s\n", sabi_config->performance_levels[i].name); | 649 | return sprintf(buf, "%s\n", config->performance_levels[i].name); |
515 | } | 650 | } |
516 | return sprintf(buf, "%s\n", "unknown"); | 651 | return sprintf(buf, "%s\n", "unknown"); |
517 | } | 652 | } |
@@ -520,269 +655,178 @@ static ssize_t set_performance_level(struct device *dev, | |||
520 | struct device_attribute *attr, const char *buf, | 655 | struct device_attribute *attr, const char *buf, |
521 | size_t count) | 656 | size_t count) |
522 | { | 657 | { |
523 | if (count >= 1) { | 658 | struct samsung_laptop *samsung = dev_get_drvdata(dev); |
524 | int i; | 659 | const struct sabi_config *config = samsung->config; |
525 | for (i = 0; sabi_config->performance_levels[i].name; ++i) { | 660 | const struct sabi_commands *commands = &config->commands; |
526 | const struct sabi_performance_level *level = | 661 | int i; |
527 | &sabi_config->performance_levels[i]; | 662 | |
528 | if (!strncasecmp(level->name, buf, strlen(level->name))) { | 663 | if (count < 1) |
529 | sabi_set_command(sabi_config->commands.set_performance_level, | 664 | return count; |
530 | level->value); | 665 | |
531 | break; | 666 | for (i = 0; config->performance_levels[i].name; ++i) { |
532 | } | 667 | const struct sabi_performance_level *level = |
668 | &config->performance_levels[i]; | ||
669 | if (!strncasecmp(level->name, buf, strlen(level->name))) { | ||
670 | sabi_set_commandb(samsung, | ||
671 | commands->set_performance_level, | ||
672 | level->value); | ||
673 | break; | ||
533 | } | 674 | } |
534 | if (!sabi_config->performance_levels[i].name) | ||
535 | return -EINVAL; | ||
536 | } | 675 | } |
676 | |||
677 | if (!config->performance_levels[i].name) | ||
678 | return -EINVAL; | ||
679 | |||
537 | return count; | 680 | return count; |
538 | } | 681 | } |
682 | |||
539 | static DEVICE_ATTR(performance_level, S_IWUSR | S_IRUGO, | 683 | static DEVICE_ATTR(performance_level, S_IWUSR | S_IRUGO, |
540 | get_performance_level, set_performance_level); | 684 | get_performance_level, set_performance_level); |
541 | 685 | ||
686 | static int read_battery_life_extender(struct samsung_laptop *samsung) | ||
687 | { | ||
688 | const struct sabi_commands *commands = &samsung->config->commands; | ||
689 | struct sabi_data data; | ||
690 | int retval; | ||
691 | |||
692 | if (commands->get_battery_life_extender == 0xFFFF) | ||
693 | return -ENODEV; | ||
694 | |||
695 | memset(&data, 0, sizeof(data)); | ||
696 | data.data[0] = 0x80; | ||
697 | retval = sabi_command(samsung, commands->get_battery_life_extender, | ||
698 | &data, &data); | ||
542 | 699 | ||
543 | static int __init dmi_check_cb(const struct dmi_system_id *id) | 700 | if (retval) |
701 | return retval; | ||
702 | |||
703 | if (data.data[0] != 0 && data.data[0] != 1) | ||
704 | return -ENODEV; | ||
705 | |||
706 | return data.data[0]; | ||
707 | } | ||
708 | |||
709 | static int write_battery_life_extender(struct samsung_laptop *samsung, | ||
710 | int enabled) | ||
544 | { | 711 | { |
545 | pr_info("found laptop model '%s'\n", | 712 | const struct sabi_commands *commands = &samsung->config->commands; |
546 | id->ident); | 713 | struct sabi_data data; |
547 | return 1; | 714 | |
715 | memset(&data, 0, sizeof(data)); | ||
716 | data.data[0] = 0x80 | enabled; | ||
717 | return sabi_command(samsung, commands->set_battery_life_extender, | ||
718 | &data, NULL); | ||
548 | } | 719 | } |
549 | 720 | ||
550 | static struct dmi_system_id __initdata samsung_dmi_table[] = { | 721 | static ssize_t get_battery_life_extender(struct device *dev, |
551 | { | 722 | struct device_attribute *attr, |
552 | .ident = "N128", | 723 | char *buf) |
553 | .matches = { | 724 | { |
554 | DMI_MATCH(DMI_SYS_VENDOR, | 725 | struct samsung_laptop *samsung = dev_get_drvdata(dev); |
555 | "SAMSUNG ELECTRONICS CO., LTD."), | 726 | int ret; |
556 | DMI_MATCH(DMI_PRODUCT_NAME, "N128"), | 727 | |
557 | DMI_MATCH(DMI_BOARD_NAME, "N128"), | 728 | ret = read_battery_life_extender(samsung); |
558 | }, | 729 | if (ret < 0) |
559 | .callback = dmi_check_cb, | 730 | return ret; |
560 | }, | 731 | |
561 | { | 732 | return sprintf(buf, "%d\n", ret); |
562 | .ident = "N130", | 733 | } |
563 | .matches = { | 734 | |
564 | DMI_MATCH(DMI_SYS_VENDOR, | 735 | static ssize_t set_battery_life_extender(struct device *dev, |
565 | "SAMSUNG ELECTRONICS CO., LTD."), | 736 | struct device_attribute *attr, |
566 | DMI_MATCH(DMI_PRODUCT_NAME, "N130"), | 737 | const char *buf, size_t count) |
567 | DMI_MATCH(DMI_BOARD_NAME, "N130"), | 738 | { |
568 | }, | 739 | struct samsung_laptop *samsung = dev_get_drvdata(dev); |
569 | .callback = dmi_check_cb, | 740 | int ret, value; |
570 | }, | 741 | |
571 | { | 742 | if (!count || sscanf(buf, "%i", &value) != 1) |
572 | .ident = "N510", | 743 | return -EINVAL; |
573 | .matches = { | 744 | |
574 | DMI_MATCH(DMI_SYS_VENDOR, | 745 | ret = write_battery_life_extender(samsung, !!value); |
575 | "SAMSUNG ELECTRONICS CO., LTD."), | 746 | if (ret < 0) |
576 | DMI_MATCH(DMI_PRODUCT_NAME, "N510"), | 747 | return ret; |
577 | DMI_MATCH(DMI_BOARD_NAME, "N510"), | 748 | |
578 | }, | 749 | return count; |
579 | .callback = dmi_check_cb, | 750 | } |
580 | }, | 751 | |
581 | { | 752 | static DEVICE_ATTR(battery_life_extender, S_IWUSR | S_IRUGO, |
582 | .ident = "X125", | 753 | get_battery_life_extender, set_battery_life_extender); |
583 | .matches = { | 754 | |
584 | DMI_MATCH(DMI_SYS_VENDOR, | 755 | static int read_usb_charge(struct samsung_laptop *samsung) |
585 | "SAMSUNG ELECTRONICS CO., LTD."), | 756 | { |
586 | DMI_MATCH(DMI_PRODUCT_NAME, "X125"), | 757 | const struct sabi_commands *commands = &samsung->config->commands; |
587 | DMI_MATCH(DMI_BOARD_NAME, "X125"), | 758 | struct sabi_data data; |
588 | }, | 759 | int retval; |
589 | .callback = dmi_check_cb, | 760 | |
590 | }, | 761 | if (commands->get_usb_charge == 0xFFFF) |
591 | { | 762 | return -ENODEV; |
592 | .ident = "X120/X170", | 763 | |
593 | .matches = { | 764 | memset(&data, 0, sizeof(data)); |
594 | DMI_MATCH(DMI_SYS_VENDOR, | 765 | data.data[0] = 0x80; |
595 | "SAMSUNG ELECTRONICS CO., LTD."), | 766 | retval = sabi_command(samsung, commands->get_usb_charge, |
596 | DMI_MATCH(DMI_PRODUCT_NAME, "X120/X170"), | 767 | &data, &data); |
597 | DMI_MATCH(DMI_BOARD_NAME, "X120/X170"), | 768 | |
598 | }, | 769 | if (retval) |
599 | .callback = dmi_check_cb, | 770 | return retval; |
600 | }, | 771 | |
601 | { | 772 | if (data.data[0] != 0 && data.data[0] != 1) |
602 | .ident = "NC10", | 773 | return -ENODEV; |
603 | .matches = { | 774 | |
604 | DMI_MATCH(DMI_SYS_VENDOR, | 775 | return data.data[0]; |
605 | "SAMSUNG ELECTRONICS CO., LTD."), | 776 | } |
606 | DMI_MATCH(DMI_PRODUCT_NAME, "NC10"), | 777 | |
607 | DMI_MATCH(DMI_BOARD_NAME, "NC10"), | 778 | static int write_usb_charge(struct samsung_laptop *samsung, |
608 | }, | 779 | int enabled) |
609 | .callback = dmi_check_cb, | 780 | { |
610 | }, | 781 | const struct sabi_commands *commands = &samsung->config->commands; |
611 | { | 782 | struct sabi_data data; |
612 | .ident = "NP-Q45", | 783 | |
613 | .matches = { | 784 | memset(&data, 0, sizeof(data)); |
614 | DMI_MATCH(DMI_SYS_VENDOR, | 785 | data.data[0] = 0x80 | enabled; |
615 | "SAMSUNG ELECTRONICS CO., LTD."), | 786 | return sabi_command(samsung, commands->set_usb_charge, |
616 | DMI_MATCH(DMI_PRODUCT_NAME, "SQ45S70S"), | 787 | &data, NULL); |
617 | DMI_MATCH(DMI_BOARD_NAME, "SQ45S70S"), | 788 | } |
618 | }, | 789 | |
619 | .callback = dmi_check_cb, | 790 | static ssize_t get_usb_charge(struct device *dev, |
620 | }, | 791 | struct device_attribute *attr, |
621 | { | 792 | char *buf) |
622 | .ident = "X360", | 793 | { |
623 | .matches = { | 794 | struct samsung_laptop *samsung = dev_get_drvdata(dev); |
624 | DMI_MATCH(DMI_SYS_VENDOR, | 795 | int ret; |
625 | "SAMSUNG ELECTRONICS CO., LTD."), | 796 | |
626 | DMI_MATCH(DMI_PRODUCT_NAME, "X360"), | 797 | ret = read_usb_charge(samsung); |
627 | DMI_MATCH(DMI_BOARD_NAME, "X360"), | 798 | if (ret < 0) |
628 | }, | 799 | return ret; |
629 | .callback = dmi_check_cb, | 800 | |
630 | }, | 801 | return sprintf(buf, "%d\n", ret); |
631 | { | 802 | } |
632 | .ident = "R410 Plus", | 803 | |
633 | .matches = { | 804 | static ssize_t set_usb_charge(struct device *dev, |
634 | DMI_MATCH(DMI_SYS_VENDOR, | 805 | struct device_attribute *attr, |
635 | "SAMSUNG ELECTRONICS CO., LTD."), | 806 | const char *buf, size_t count) |
636 | DMI_MATCH(DMI_PRODUCT_NAME, "R410P"), | 807 | { |
637 | DMI_MATCH(DMI_BOARD_NAME, "R460"), | 808 | struct samsung_laptop *samsung = dev_get_drvdata(dev); |
638 | }, | 809 | int ret, value; |
639 | .callback = dmi_check_cb, | 810 | |
640 | }, | 811 | if (!count || sscanf(buf, "%i", &value) != 1) |
641 | { | 812 | return -EINVAL; |
642 | .ident = "R518", | 813 | |
643 | .matches = { | 814 | ret = write_usb_charge(samsung, !!value); |
644 | DMI_MATCH(DMI_SYS_VENDOR, | 815 | if (ret < 0) |
645 | "SAMSUNG ELECTRONICS CO., LTD."), | 816 | return ret; |
646 | DMI_MATCH(DMI_PRODUCT_NAME, "R518"), | 817 | |
647 | DMI_MATCH(DMI_BOARD_NAME, "R518"), | 818 | return count; |
648 | }, | 819 | } |
649 | .callback = dmi_check_cb, | 820 | |
650 | }, | 821 | static DEVICE_ATTR(usb_charge, S_IWUSR | S_IRUGO, |
651 | { | 822 | get_usb_charge, set_usb_charge); |
652 | .ident = "R519/R719", | 823 | |
653 | .matches = { | 824 | static struct attribute *platform_attributes[] = { |
654 | DMI_MATCH(DMI_SYS_VENDOR, | 825 | &dev_attr_performance_level.attr, |
655 | "SAMSUNG ELECTRONICS CO., LTD."), | 826 | &dev_attr_battery_life_extender.attr, |
656 | DMI_MATCH(DMI_PRODUCT_NAME, "R519/R719"), | 827 | &dev_attr_usb_charge.attr, |
657 | DMI_MATCH(DMI_BOARD_NAME, "R519/R719"), | 828 | NULL |
658 | }, | ||
659 | .callback = dmi_check_cb, | ||
660 | }, | ||
661 | { | ||
662 | .ident = "N150/N210/N220", | ||
663 | .matches = { | ||
664 | DMI_MATCH(DMI_SYS_VENDOR, | ||
665 | "SAMSUNG ELECTRONICS CO., LTD."), | ||
666 | DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220"), | ||
667 | DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220"), | ||
668 | }, | ||
669 | .callback = dmi_check_cb, | ||
670 | }, | ||
671 | { | ||
672 | .ident = "N220", | ||
673 | .matches = { | ||
674 | DMI_MATCH(DMI_SYS_VENDOR, | ||
675 | "SAMSUNG ELECTRONICS CO., LTD."), | ||
676 | DMI_MATCH(DMI_PRODUCT_NAME, "N220"), | ||
677 | DMI_MATCH(DMI_BOARD_NAME, "N220"), | ||
678 | }, | ||
679 | .callback = dmi_check_cb, | ||
680 | }, | ||
681 | { | ||
682 | .ident = "N150/N210/N220/N230", | ||
683 | .matches = { | ||
684 | DMI_MATCH(DMI_SYS_VENDOR, | ||
685 | "SAMSUNG ELECTRONICS CO., LTD."), | ||
686 | DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220/N230"), | ||
687 | DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220/N230"), | ||
688 | }, | ||
689 | .callback = dmi_check_cb, | ||
690 | }, | ||
691 | { | ||
692 | .ident = "N150P/N210P/N220P", | ||
693 | .matches = { | ||
694 | DMI_MATCH(DMI_SYS_VENDOR, | ||
695 | "SAMSUNG ELECTRONICS CO., LTD."), | ||
696 | DMI_MATCH(DMI_PRODUCT_NAME, "N150P/N210P/N220P"), | ||
697 | DMI_MATCH(DMI_BOARD_NAME, "N150P/N210P/N220P"), | ||
698 | }, | ||
699 | .callback = dmi_check_cb, | ||
700 | }, | ||
701 | { | ||
702 | .ident = "R700", | ||
703 | .matches = { | ||
704 | DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | ||
705 | DMI_MATCH(DMI_PRODUCT_NAME, "SR700"), | ||
706 | DMI_MATCH(DMI_BOARD_NAME, "SR700"), | ||
707 | }, | ||
708 | .callback = dmi_check_cb, | ||
709 | }, | ||
710 | { | ||
711 | .ident = "R530/R730", | ||
712 | .matches = { | ||
713 | DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | ||
714 | DMI_MATCH(DMI_PRODUCT_NAME, "R530/R730"), | ||
715 | DMI_MATCH(DMI_BOARD_NAME, "R530/R730"), | ||
716 | }, | ||
717 | .callback = dmi_check_cb, | ||
718 | }, | ||
719 | { | ||
720 | .ident = "NF110/NF210/NF310", | ||
721 | .matches = { | ||
722 | DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | ||
723 | DMI_MATCH(DMI_PRODUCT_NAME, "NF110/NF210/NF310"), | ||
724 | DMI_MATCH(DMI_BOARD_NAME, "NF110/NF210/NF310"), | ||
725 | }, | ||
726 | .callback = dmi_check_cb, | ||
727 | }, | ||
728 | { | ||
729 | .ident = "N145P/N250P/N260P", | ||
730 | .matches = { | ||
731 | DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | ||
732 | DMI_MATCH(DMI_PRODUCT_NAME, "N145P/N250P/N260P"), | ||
733 | DMI_MATCH(DMI_BOARD_NAME, "N145P/N250P/N260P"), | ||
734 | }, | ||
735 | .callback = dmi_check_cb, | ||
736 | }, | ||
737 | { | ||
738 | .ident = "R70/R71", | ||
739 | .matches = { | ||
740 | DMI_MATCH(DMI_SYS_VENDOR, | ||
741 | "SAMSUNG ELECTRONICS CO., LTD."), | ||
742 | DMI_MATCH(DMI_PRODUCT_NAME, "R70/R71"), | ||
743 | DMI_MATCH(DMI_BOARD_NAME, "R70/R71"), | ||
744 | }, | ||
745 | .callback = dmi_check_cb, | ||
746 | }, | ||
747 | { | ||
748 | .ident = "P460", | ||
749 | .matches = { | ||
750 | DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | ||
751 | DMI_MATCH(DMI_PRODUCT_NAME, "P460"), | ||
752 | DMI_MATCH(DMI_BOARD_NAME, "P460"), | ||
753 | }, | ||
754 | .callback = dmi_check_cb, | ||
755 | }, | ||
756 | { | ||
757 | .ident = "R528/R728", | ||
758 | .matches = { | ||
759 | DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | ||
760 | DMI_MATCH(DMI_PRODUCT_NAME, "R528/R728"), | ||
761 | DMI_MATCH(DMI_BOARD_NAME, "R528/R728"), | ||
762 | }, | ||
763 | .callback = dmi_check_cb, | ||
764 | }, | ||
765 | { | ||
766 | .ident = "NC210/NC110", | ||
767 | .matches = { | ||
768 | DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | ||
769 | DMI_MATCH(DMI_PRODUCT_NAME, "NC210/NC110"), | ||
770 | DMI_MATCH(DMI_BOARD_NAME, "NC210/NC110"), | ||
771 | }, | ||
772 | .callback = dmi_check_cb, | ||
773 | }, | ||
774 | { | ||
775 | .ident = "X520", | ||
776 | .matches = { | ||
777 | DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | ||
778 | DMI_MATCH(DMI_PRODUCT_NAME, "X520"), | ||
779 | DMI_MATCH(DMI_BOARD_NAME, "X520"), | ||
780 | }, | ||
781 | .callback = dmi_check_cb, | ||
782 | }, | ||
783 | { }, | ||
784 | }; | 829 | }; |
785 | MODULE_DEVICE_TABLE(dmi, samsung_dmi_table); | ||
786 | 830 | ||
787 | static int find_signature(void __iomem *memcheck, const char *testStr) | 831 | static int find_signature(void __iomem *memcheck, const char *testStr) |
788 | { | 832 | { |
@@ -803,153 +847,772 @@ static int find_signature(void __iomem *memcheck, const char *testStr) | |||
803 | return loca; | 847 | return loca; |
804 | } | 848 | } |
805 | 849 | ||
806 | static int __init samsung_init(void) | 850 | static void samsung_rfkill_exit(struct samsung_laptop *samsung) |
807 | { | 851 | { |
808 | struct backlight_properties props; | 852 | if (samsung->wlan.rfkill) { |
809 | struct sabi_retval sretval; | 853 | rfkill_unregister(samsung->wlan.rfkill); |
810 | unsigned int ifaceP; | 854 | rfkill_destroy(samsung->wlan.rfkill); |
811 | int i; | 855 | samsung->wlan.rfkill = NULL; |
812 | int loca; | 856 | } |
857 | if (samsung->bluetooth.rfkill) { | ||
858 | rfkill_unregister(samsung->bluetooth.rfkill); | ||
859 | rfkill_destroy(samsung->bluetooth.rfkill); | ||
860 | samsung->bluetooth.rfkill = NULL; | ||
861 | } | ||
862 | } | ||
863 | |||
864 | static int samsung_new_rfkill(struct samsung_laptop *samsung, | ||
865 | struct samsung_rfkill *arfkill, | ||
866 | const char *name, enum rfkill_type type, | ||
867 | const struct rfkill_ops *ops, | ||
868 | int blocked) | ||
869 | { | ||
870 | struct rfkill **rfkill = &arfkill->rfkill; | ||
871 | int ret; | ||
872 | |||
873 | arfkill->type = type; | ||
874 | arfkill->samsung = samsung; | ||
875 | |||
876 | *rfkill = rfkill_alloc(name, &samsung->platform_device->dev, | ||
877 | type, ops, arfkill); | ||
878 | |||
879 | if (!*rfkill) | ||
880 | return -EINVAL; | ||
881 | |||
882 | if (blocked != -1) | ||
883 | rfkill_init_sw_state(*rfkill, blocked); | ||
884 | |||
885 | ret = rfkill_register(*rfkill); | ||
886 | if (ret) { | ||
887 | rfkill_destroy(*rfkill); | ||
888 | *rfkill = NULL; | ||
889 | return ret; | ||
890 | } | ||
891 | return 0; | ||
892 | } | ||
893 | |||
894 | static int __init samsung_rfkill_init_seclinux(struct samsung_laptop *samsung) | ||
895 | { | ||
896 | return samsung_new_rfkill(samsung, &samsung->wlan, "samsung-wlan", | ||
897 | RFKILL_TYPE_WLAN, &seclinux_rfkill_ops, -1); | ||
898 | } | ||
899 | |||
900 | static int __init samsung_rfkill_init_swsmi(struct samsung_laptop *samsung) | ||
901 | { | ||
902 | struct sabi_data data; | ||
903 | int ret; | ||
904 | |||
905 | ret = swsmi_wireless_status(samsung, &data); | ||
906 | if (ret) { | ||
907 | /* Some swsmi laptops use the old seclinux way to control | ||
908 | * wireless devices */ | ||
909 | if (ret == -EINVAL) | ||
910 | ret = samsung_rfkill_init_seclinux(samsung); | ||
911 | return ret; | ||
912 | } | ||
913 | |||
914 | /* 0x02 seems to mean that the device is no present/available */ | ||
915 | |||
916 | if (data.data[WL_STATUS_WLAN] != 0x02) | ||
917 | ret = samsung_new_rfkill(samsung, &samsung->wlan, | ||
918 | "samsung-wlan", | ||
919 | RFKILL_TYPE_WLAN, | ||
920 | &swsmi_rfkill_ops, | ||
921 | !data.data[WL_STATUS_WLAN]); | ||
922 | if (ret) | ||
923 | goto exit; | ||
924 | |||
925 | if (data.data[WL_STATUS_BT] != 0x02) | ||
926 | ret = samsung_new_rfkill(samsung, &samsung->bluetooth, | ||
927 | "samsung-bluetooth", | ||
928 | RFKILL_TYPE_BLUETOOTH, | ||
929 | &swsmi_rfkill_ops, | ||
930 | !data.data[WL_STATUS_BT]); | ||
931 | if (ret) | ||
932 | goto exit; | ||
933 | |||
934 | exit: | ||
935 | if (ret) | ||
936 | samsung_rfkill_exit(samsung); | ||
937 | |||
938 | return ret; | ||
939 | } | ||
940 | |||
941 | static int __init samsung_rfkill_init(struct samsung_laptop *samsung) | ||
942 | { | ||
943 | if (samsung->config->sabi_version == 2) | ||
944 | return samsung_rfkill_init_seclinux(samsung); | ||
945 | if (samsung->config->sabi_version == 3) | ||
946 | return samsung_rfkill_init_swsmi(samsung); | ||
947 | return 0; | ||
948 | } | ||
949 | |||
950 | static int kbd_backlight_enable(struct samsung_laptop *samsung) | ||
951 | { | ||
952 | const struct sabi_commands *commands = &samsung->config->commands; | ||
953 | struct sabi_data data; | ||
813 | int retval; | 954 | int retval; |
814 | 955 | ||
815 | mutex_init(&sabi_mutex); | 956 | if (commands->kbd_backlight == 0xFFFF) |
957 | return -ENODEV; | ||
958 | |||
959 | memset(&data, 0, sizeof(data)); | ||
960 | data.d0 = 0xaabb; | ||
961 | retval = sabi_command(samsung, commands->kbd_backlight, | ||
962 | &data, &data); | ||
816 | 963 | ||
817 | if (!force && !dmi_check_system(samsung_dmi_table)) | 964 | if (retval) |
965 | return retval; | ||
966 | |||
967 | if (data.d0 != 0xccdd) | ||
818 | return -ENODEV; | 968 | return -ENODEV; |
969 | return 0; | ||
970 | } | ||
819 | 971 | ||
820 | f0000_segment = ioremap_nocache(0xf0000, 0xffff); | 972 | static int kbd_backlight_read(struct samsung_laptop *samsung) |
821 | if (!f0000_segment) { | 973 | { |
822 | pr_err("Can't map the segment at 0xf0000\n"); | 974 | const struct sabi_commands *commands = &samsung->config->commands; |
823 | return -EINVAL; | 975 | struct sabi_data data; |
976 | int retval; | ||
977 | |||
978 | memset(&data, 0, sizeof(data)); | ||
979 | data.data[0] = 0x81; | ||
980 | retval = sabi_command(samsung, commands->kbd_backlight, | ||
981 | &data, &data); | ||
982 | |||
983 | if (retval) | ||
984 | return retval; | ||
985 | |||
986 | return data.data[0]; | ||
987 | } | ||
988 | |||
989 | static int kbd_backlight_write(struct samsung_laptop *samsung, int brightness) | ||
990 | { | ||
991 | const struct sabi_commands *commands = &samsung->config->commands; | ||
992 | struct sabi_data data; | ||
993 | |||
994 | memset(&data, 0, sizeof(data)); | ||
995 | data.d0 = 0x82 | ((brightness & 0xFF) << 8); | ||
996 | return sabi_command(samsung, commands->kbd_backlight, | ||
997 | &data, NULL); | ||
998 | } | ||
999 | |||
1000 | static void kbd_led_update(struct work_struct *work) | ||
1001 | { | ||
1002 | struct samsung_laptop *samsung; | ||
1003 | |||
1004 | samsung = container_of(work, struct samsung_laptop, kbd_led_work); | ||
1005 | kbd_backlight_write(samsung, samsung->kbd_led_wk); | ||
1006 | } | ||
1007 | |||
1008 | static void kbd_led_set(struct led_classdev *led_cdev, | ||
1009 | enum led_brightness value) | ||
1010 | { | ||
1011 | struct samsung_laptop *samsung; | ||
1012 | |||
1013 | samsung = container_of(led_cdev, struct samsung_laptop, kbd_led); | ||
1014 | |||
1015 | if (value > samsung->kbd_led.max_brightness) | ||
1016 | value = samsung->kbd_led.max_brightness; | ||
1017 | else if (value < 0) | ||
1018 | value = 0; | ||
1019 | |||
1020 | samsung->kbd_led_wk = value; | ||
1021 | queue_work(samsung->led_workqueue, &samsung->kbd_led_work); | ||
1022 | } | ||
1023 | |||
1024 | static enum led_brightness kbd_led_get(struct led_classdev *led_cdev) | ||
1025 | { | ||
1026 | struct samsung_laptop *samsung; | ||
1027 | |||
1028 | samsung = container_of(led_cdev, struct samsung_laptop, kbd_led); | ||
1029 | return kbd_backlight_read(samsung); | ||
1030 | } | ||
1031 | |||
1032 | static void samsung_leds_exit(struct samsung_laptop *samsung) | ||
1033 | { | ||
1034 | if (!IS_ERR_OR_NULL(samsung->kbd_led.dev)) | ||
1035 | led_classdev_unregister(&samsung->kbd_led); | ||
1036 | if (samsung->led_workqueue) | ||
1037 | destroy_workqueue(samsung->led_workqueue); | ||
1038 | } | ||
1039 | |||
1040 | static int __init samsung_leds_init(struct samsung_laptop *samsung) | ||
1041 | { | ||
1042 | int ret = 0; | ||
1043 | |||
1044 | samsung->led_workqueue = create_singlethread_workqueue("led_workqueue"); | ||
1045 | if (!samsung->led_workqueue) | ||
1046 | return -ENOMEM; | ||
1047 | |||
1048 | if (kbd_backlight_enable(samsung) >= 0) { | ||
1049 | INIT_WORK(&samsung->kbd_led_work, kbd_led_update); | ||
1050 | |||
1051 | samsung->kbd_led.name = "samsung::kbd_backlight"; | ||
1052 | samsung->kbd_led.brightness_set = kbd_led_set; | ||
1053 | samsung->kbd_led.brightness_get = kbd_led_get; | ||
1054 | samsung->kbd_led.max_brightness = 8; | ||
1055 | |||
1056 | ret = led_classdev_register(&samsung->platform_device->dev, | ||
1057 | &samsung->kbd_led); | ||
1058 | } | ||
1059 | |||
1060 | if (ret) | ||
1061 | samsung_leds_exit(samsung); | ||
1062 | |||
1063 | return ret; | ||
1064 | } | ||
1065 | |||
1066 | static void samsung_backlight_exit(struct samsung_laptop *samsung) | ||
1067 | { | ||
1068 | if (samsung->backlight_device) { | ||
1069 | backlight_device_unregister(samsung->backlight_device); | ||
1070 | samsung->backlight_device = NULL; | ||
1071 | } | ||
1072 | } | ||
1073 | |||
1074 | static int __init samsung_backlight_init(struct samsung_laptop *samsung) | ||
1075 | { | ||
1076 | struct backlight_device *bd; | ||
1077 | struct backlight_properties props; | ||
1078 | |||
1079 | if (!samsung->handle_backlight) | ||
1080 | return 0; | ||
1081 | |||
1082 | memset(&props, 0, sizeof(struct backlight_properties)); | ||
1083 | props.type = BACKLIGHT_PLATFORM; | ||
1084 | props.max_brightness = samsung->config->max_brightness - | ||
1085 | samsung->config->min_brightness; | ||
1086 | |||
1087 | bd = backlight_device_register("samsung", | ||
1088 | &samsung->platform_device->dev, | ||
1089 | samsung, &backlight_ops, | ||
1090 | &props); | ||
1091 | if (IS_ERR(bd)) | ||
1092 | return PTR_ERR(bd); | ||
1093 | |||
1094 | samsung->backlight_device = bd; | ||
1095 | samsung->backlight_device->props.brightness = read_brightness(samsung); | ||
1096 | samsung->backlight_device->props.power = FB_BLANK_UNBLANK; | ||
1097 | backlight_update_status(samsung->backlight_device); | ||
1098 | |||
1099 | return 0; | ||
1100 | } | ||
1101 | |||
1102 | static umode_t samsung_sysfs_is_visible(struct kobject *kobj, | ||
1103 | struct attribute *attr, int idx) | ||
1104 | { | ||
1105 | struct device *dev = container_of(kobj, struct device, kobj); | ||
1106 | struct platform_device *pdev = to_platform_device(dev); | ||
1107 | struct samsung_laptop *samsung = platform_get_drvdata(pdev); | ||
1108 | bool ok = true; | ||
1109 | |||
1110 | if (attr == &dev_attr_performance_level.attr) | ||
1111 | ok = !!samsung->config->performance_levels[0].name; | ||
1112 | if (attr == &dev_attr_battery_life_extender.attr) | ||
1113 | ok = !!(read_battery_life_extender(samsung) >= 0); | ||
1114 | if (attr == &dev_attr_usb_charge.attr) | ||
1115 | ok = !!(read_usb_charge(samsung) >= 0); | ||
1116 | |||
1117 | return ok ? attr->mode : 0; | ||
1118 | } | ||
1119 | |||
1120 | static struct attribute_group platform_attribute_group = { | ||
1121 | .is_visible = samsung_sysfs_is_visible, | ||
1122 | .attrs = platform_attributes | ||
1123 | }; | ||
1124 | |||
1125 | static void samsung_sysfs_exit(struct samsung_laptop *samsung) | ||
1126 | { | ||
1127 | struct platform_device *device = samsung->platform_device; | ||
1128 | |||
1129 | sysfs_remove_group(&device->dev.kobj, &platform_attribute_group); | ||
1130 | } | ||
1131 | |||
1132 | static int __init samsung_sysfs_init(struct samsung_laptop *samsung) | ||
1133 | { | ||
1134 | struct platform_device *device = samsung->platform_device; | ||
1135 | |||
1136 | return sysfs_create_group(&device->dev.kobj, &platform_attribute_group); | ||
1137 | |||
1138 | } | ||
1139 | |||
1140 | static int show_call(struct seq_file *m, void *data) | ||
1141 | { | ||
1142 | struct samsung_laptop *samsung = m->private; | ||
1143 | struct sabi_data *sdata = &samsung->debug.data; | ||
1144 | int ret; | ||
1145 | |||
1146 | seq_printf(m, "SABI 0x%04x {0x%08x, 0x%08x, 0x%04x, 0x%02x}\n", | ||
1147 | samsung->debug.command, | ||
1148 | sdata->d0, sdata->d1, sdata->d2, sdata->d3); | ||
1149 | |||
1150 | ret = sabi_command(samsung, samsung->debug.command, sdata, sdata); | ||
1151 | |||
1152 | if (ret) { | ||
1153 | seq_printf(m, "SABI command 0x%04x failed\n", | ||
1154 | samsung->debug.command); | ||
1155 | return ret; | ||
1156 | } | ||
1157 | |||
1158 | seq_printf(m, "SABI {0x%08x, 0x%08x, 0x%04x, 0x%02x}\n", | ||
1159 | sdata->d0, sdata->d1, sdata->d2, sdata->d3); | ||
1160 | return 0; | ||
1161 | } | ||
1162 | |||
1163 | static int samsung_debugfs_open(struct inode *inode, struct file *file) | ||
1164 | { | ||
1165 | return single_open(file, show_call, inode->i_private); | ||
1166 | } | ||
1167 | |||
1168 | static const struct file_operations samsung_laptop_call_io_ops = { | ||
1169 | .owner = THIS_MODULE, | ||
1170 | .open = samsung_debugfs_open, | ||
1171 | .read = seq_read, | ||
1172 | .llseek = seq_lseek, | ||
1173 | .release = single_release, | ||
1174 | }; | ||
1175 | |||
1176 | static void samsung_debugfs_exit(struct samsung_laptop *samsung) | ||
1177 | { | ||
1178 | debugfs_remove_recursive(samsung->debug.root); | ||
1179 | } | ||
1180 | |||
1181 | static int samsung_debugfs_init(struct samsung_laptop *samsung) | ||
1182 | { | ||
1183 | struct dentry *dent; | ||
1184 | |||
1185 | samsung->debug.root = debugfs_create_dir("samsung-laptop", NULL); | ||
1186 | if (!samsung->debug.root) { | ||
1187 | pr_err("failed to create debugfs directory"); | ||
1188 | goto error_debugfs; | ||
1189 | } | ||
1190 | |||
1191 | samsung->debug.f0000_wrapper.data = samsung->f0000_segment; | ||
1192 | samsung->debug.f0000_wrapper.size = 0xffff; | ||
1193 | |||
1194 | samsung->debug.data_wrapper.data = &samsung->debug.data; | ||
1195 | samsung->debug.data_wrapper.size = sizeof(samsung->debug.data); | ||
1196 | |||
1197 | samsung->debug.sdiag_wrapper.data = samsung->sdiag; | ||
1198 | samsung->debug.sdiag_wrapper.size = strlen(samsung->sdiag); | ||
1199 | |||
1200 | dent = debugfs_create_u16("command", S_IRUGO | S_IWUSR, | ||
1201 | samsung->debug.root, &samsung->debug.command); | ||
1202 | if (!dent) | ||
1203 | goto error_debugfs; | ||
1204 | |||
1205 | dent = debugfs_create_u32("d0", S_IRUGO | S_IWUSR, samsung->debug.root, | ||
1206 | &samsung->debug.data.d0); | ||
1207 | if (!dent) | ||
1208 | goto error_debugfs; | ||
1209 | |||
1210 | dent = debugfs_create_u32("d1", S_IRUGO | S_IWUSR, samsung->debug.root, | ||
1211 | &samsung->debug.data.d1); | ||
1212 | if (!dent) | ||
1213 | goto error_debugfs; | ||
1214 | |||
1215 | dent = debugfs_create_u16("d2", S_IRUGO | S_IWUSR, samsung->debug.root, | ||
1216 | &samsung->debug.data.d2); | ||
1217 | if (!dent) | ||
1218 | goto error_debugfs; | ||
1219 | |||
1220 | dent = debugfs_create_u8("d3", S_IRUGO | S_IWUSR, samsung->debug.root, | ||
1221 | &samsung->debug.data.d3); | ||
1222 | if (!dent) | ||
1223 | goto error_debugfs; | ||
1224 | |||
1225 | dent = debugfs_create_blob("data", S_IRUGO | S_IWUSR, | ||
1226 | samsung->debug.root, | ||
1227 | &samsung->debug.data_wrapper); | ||
1228 | if (!dent) | ||
1229 | goto error_debugfs; | ||
1230 | |||
1231 | dent = debugfs_create_blob("f0000_segment", S_IRUSR | S_IWUSR, | ||
1232 | samsung->debug.root, | ||
1233 | &samsung->debug.f0000_wrapper); | ||
1234 | if (!dent) | ||
1235 | goto error_debugfs; | ||
1236 | |||
1237 | dent = debugfs_create_file("call", S_IFREG | S_IRUGO, | ||
1238 | samsung->debug.root, samsung, | ||
1239 | &samsung_laptop_call_io_ops); | ||
1240 | if (!dent) | ||
1241 | goto error_debugfs; | ||
1242 | |||
1243 | dent = debugfs_create_blob("sdiag", S_IRUGO | S_IWUSR, | ||
1244 | samsung->debug.root, | ||
1245 | &samsung->debug.sdiag_wrapper); | ||
1246 | if (!dent) | ||
1247 | goto error_debugfs; | ||
1248 | |||
1249 | return 0; | ||
1250 | |||
1251 | error_debugfs: | ||
1252 | samsung_debugfs_exit(samsung); | ||
1253 | return -ENOMEM; | ||
1254 | } | ||
1255 | |||
1256 | static void samsung_sabi_exit(struct samsung_laptop *samsung) | ||
1257 | { | ||
1258 | const struct sabi_config *config = samsung->config; | ||
1259 | |||
1260 | /* Turn off "Linux" mode in the BIOS */ | ||
1261 | if (config && config->commands.set_linux != 0xff) | ||
1262 | sabi_set_commandb(samsung, config->commands.set_linux, 0x80); | ||
1263 | |||
1264 | if (samsung->sabi_iface) { | ||
1265 | iounmap(samsung->sabi_iface); | ||
1266 | samsung->sabi_iface = NULL; | ||
1267 | } | ||
1268 | if (samsung->f0000_segment) { | ||
1269 | iounmap(samsung->f0000_segment); | ||
1270 | samsung->f0000_segment = NULL; | ||
1271 | } | ||
1272 | |||
1273 | samsung->config = NULL; | ||
1274 | } | ||
1275 | |||
1276 | static __init void samsung_sabi_infos(struct samsung_laptop *samsung, int loca, | ||
1277 | unsigned int ifaceP) | ||
1278 | { | ||
1279 | const struct sabi_config *config = samsung->config; | ||
1280 | |||
1281 | printk(KERN_DEBUG "This computer supports SABI==%x\n", | ||
1282 | loca + 0xf0000 - 6); | ||
1283 | |||
1284 | printk(KERN_DEBUG "SABI header:\n"); | ||
1285 | printk(KERN_DEBUG " SMI Port Number = 0x%04x\n", | ||
1286 | readw(samsung->sabi + config->header_offsets.port)); | ||
1287 | printk(KERN_DEBUG " SMI Interface Function = 0x%02x\n", | ||
1288 | readb(samsung->sabi + config->header_offsets.iface_func)); | ||
1289 | printk(KERN_DEBUG " SMI enable memory buffer = 0x%02x\n", | ||
1290 | readb(samsung->sabi + config->header_offsets.en_mem)); | ||
1291 | printk(KERN_DEBUG " SMI restore memory buffer = 0x%02x\n", | ||
1292 | readb(samsung->sabi + config->header_offsets.re_mem)); | ||
1293 | printk(KERN_DEBUG " SABI data offset = 0x%04x\n", | ||
1294 | readw(samsung->sabi + config->header_offsets.data_offset)); | ||
1295 | printk(KERN_DEBUG " SABI data segment = 0x%04x\n", | ||
1296 | readw(samsung->sabi + config->header_offsets.data_segment)); | ||
1297 | |||
1298 | printk(KERN_DEBUG " SABI pointer = 0x%08x\n", ifaceP); | ||
1299 | } | ||
1300 | |||
1301 | static void __init samsung_sabi_diag(struct samsung_laptop *samsung) | ||
1302 | { | ||
1303 | int loca = find_signature(samsung->f0000_segment, "SDiaG@"); | ||
1304 | int i; | ||
1305 | |||
1306 | if (loca == 0xffff) | ||
1307 | return ; | ||
1308 | |||
1309 | /* Example: | ||
1310 | * Ident: @SDiaG@686XX-N90X3A/966-SEC-07HL-S90X3A | ||
1311 | * | ||
1312 | * Product name: 90X3A | ||
1313 | * BIOS Version: 07HL | ||
1314 | */ | ||
1315 | loca += 1; | ||
1316 | for (i = 0; loca < 0xffff && i < sizeof(samsung->sdiag) - 1; loca++) { | ||
1317 | char temp = readb(samsung->f0000_segment + loca); | ||
1318 | |||
1319 | if (isalnum(temp) || temp == '/' || temp == '-') | ||
1320 | samsung->sdiag[i++] = temp; | ||
1321 | else | ||
1322 | break ; | ||
824 | } | 1323 | } |
825 | 1324 | ||
1325 | if (debug && samsung->sdiag[0]) | ||
1326 | pr_info("sdiag: %s", samsung->sdiag); | ||
1327 | } | ||
1328 | |||
1329 | static int __init samsung_sabi_init(struct samsung_laptop *samsung) | ||
1330 | { | ||
1331 | const struct sabi_config *config = NULL; | ||
1332 | const struct sabi_commands *commands; | ||
1333 | unsigned int ifaceP; | ||
1334 | int ret = 0; | ||
1335 | int i; | ||
1336 | int loca; | ||
1337 | |||
1338 | samsung->f0000_segment = ioremap_nocache(0xf0000, 0xffff); | ||
1339 | if (!samsung->f0000_segment) { | ||
1340 | if (debug || force) | ||
1341 | pr_err("Can't map the segment at 0xf0000\n"); | ||
1342 | ret = -EINVAL; | ||
1343 | goto exit; | ||
1344 | } | ||
1345 | |||
1346 | samsung_sabi_diag(samsung); | ||
1347 | |||
826 | /* Try to find one of the signatures in memory to find the header */ | 1348 | /* Try to find one of the signatures in memory to find the header */ |
827 | for (i = 0; sabi_configs[i].test_string != 0; ++i) { | 1349 | for (i = 0; sabi_configs[i].test_string != 0; ++i) { |
828 | sabi_config = &sabi_configs[i]; | 1350 | samsung->config = &sabi_configs[i]; |
829 | loca = find_signature(f0000_segment, sabi_config->test_string); | 1351 | loca = find_signature(samsung->f0000_segment, |
1352 | samsung->config->test_string); | ||
830 | if (loca != 0xffff) | 1353 | if (loca != 0xffff) |
831 | break; | 1354 | break; |
832 | } | 1355 | } |
833 | 1356 | ||
834 | if (loca == 0xffff) { | 1357 | if (loca == 0xffff) { |
835 | pr_err("This computer does not support SABI\n"); | 1358 | if (debug || force) |
836 | goto error_no_signature; | 1359 | pr_err("This computer does not support SABI\n"); |
1360 | ret = -ENODEV; | ||
1361 | goto exit; | ||
837 | } | 1362 | } |
838 | 1363 | ||
1364 | config = samsung->config; | ||
1365 | commands = &config->commands; | ||
1366 | |||
839 | /* point to the SMI port Number */ | 1367 | /* point to the SMI port Number */ |
840 | loca += 1; | 1368 | loca += 1; |
841 | sabi = (f0000_segment + loca); | 1369 | samsung->sabi = (samsung->f0000_segment + loca); |
842 | |||
843 | if (debug) { | ||
844 | printk(KERN_DEBUG "This computer supports SABI==%x\n", | ||
845 | loca + 0xf0000 - 6); | ||
846 | printk(KERN_DEBUG "SABI header:\n"); | ||
847 | printk(KERN_DEBUG " SMI Port Number = 0x%04x\n", | ||
848 | readw(sabi + sabi_config->header_offsets.port)); | ||
849 | printk(KERN_DEBUG " SMI Interface Function = 0x%02x\n", | ||
850 | readb(sabi + sabi_config->header_offsets.iface_func)); | ||
851 | printk(KERN_DEBUG " SMI enable memory buffer = 0x%02x\n", | ||
852 | readb(sabi + sabi_config->header_offsets.en_mem)); | ||
853 | printk(KERN_DEBUG " SMI restore memory buffer = 0x%02x\n", | ||
854 | readb(sabi + sabi_config->header_offsets.re_mem)); | ||
855 | printk(KERN_DEBUG " SABI data offset = 0x%04x\n", | ||
856 | readw(sabi + sabi_config->header_offsets.data_offset)); | ||
857 | printk(KERN_DEBUG " SABI data segment = 0x%04x\n", | ||
858 | readw(sabi + sabi_config->header_offsets.data_segment)); | ||
859 | } | ||
860 | 1370 | ||
861 | /* Get a pointer to the SABI Interface */ | 1371 | /* Get a pointer to the SABI Interface */ |
862 | ifaceP = (readw(sabi + sabi_config->header_offsets.data_segment) & 0x0ffff) << 4; | 1372 | ifaceP = (readw(samsung->sabi + config->header_offsets.data_segment) & 0x0ffff) << 4; |
863 | ifaceP += readw(sabi + sabi_config->header_offsets.data_offset) & 0x0ffff; | 1373 | ifaceP += readw(samsung->sabi + config->header_offsets.data_offset) & 0x0ffff; |
864 | sabi_iface = ioremap_nocache(ifaceP, 16); | ||
865 | if (!sabi_iface) { | ||
866 | pr_err("Can't remap %x\n", ifaceP); | ||
867 | goto error_no_signature; | ||
868 | } | ||
869 | if (debug) { | ||
870 | printk(KERN_DEBUG "ifaceP = 0x%08x\n", ifaceP); | ||
871 | printk(KERN_DEBUG "sabi_iface = %p\n", sabi_iface); | ||
872 | 1374 | ||
873 | test_backlight(); | 1375 | if (debug) |
874 | test_wireless(); | 1376 | samsung_sabi_infos(samsung, loca, ifaceP); |
875 | 1377 | ||
876 | retval = sabi_get_command(sabi_config->commands.get_brightness, | 1378 | samsung->sabi_iface = ioremap_nocache(ifaceP, 16); |
877 | &sretval); | 1379 | if (!samsung->sabi_iface) { |
878 | printk(KERN_DEBUG "brightness = 0x%02x\n", sretval.retval[0]); | 1380 | pr_err("Can't remap %x\n", ifaceP); |
1381 | ret = -EINVAL; | ||
1382 | goto exit; | ||
879 | } | 1383 | } |
880 | 1384 | ||
881 | /* Turn on "Linux" mode in the BIOS */ | 1385 | /* Turn on "Linux" mode in the BIOS */ |
882 | if (sabi_config->commands.set_linux != 0xff) { | 1386 | if (commands->set_linux != 0xff) { |
883 | retval = sabi_set_command(sabi_config->commands.set_linux, | 1387 | int retval = sabi_set_commandb(samsung, |
884 | 0x81); | 1388 | commands->set_linux, 0x81); |
885 | if (retval) { | 1389 | if (retval) { |
886 | pr_warn("Linux mode was not set!\n"); | 1390 | pr_warn("Linux mode was not set!\n"); |
887 | goto error_no_platform; | 1391 | ret = -ENODEV; |
1392 | goto exit; | ||
888 | } | 1393 | } |
889 | } | 1394 | } |
890 | 1395 | ||
891 | /* Check for stepping quirk */ | 1396 | /* Check for stepping quirk */ |
892 | check_for_stepping_quirk(); | 1397 | if (samsung->handle_backlight) |
1398 | check_for_stepping_quirk(samsung); | ||
893 | 1399 | ||
894 | /* knock up a platform device to hang stuff off of */ | 1400 | pr_info("detected SABI interface: %s\n", |
895 | sdev = platform_device_register_simple("samsung", -1, NULL, 0); | 1401 | samsung->config->test_string); |
896 | if (IS_ERR(sdev)) | ||
897 | goto error_no_platform; | ||
898 | 1402 | ||
899 | /* create a backlight device to talk to this one */ | 1403 | exit: |
900 | memset(&props, 0, sizeof(struct backlight_properties)); | 1404 | if (ret) |
901 | props.type = BACKLIGHT_PLATFORM; | 1405 | samsung_sabi_exit(samsung); |
902 | props.max_brightness = sabi_config->max_brightness - | ||
903 | sabi_config->min_brightness; | ||
904 | backlight_device = backlight_device_register("samsung", &sdev->dev, | ||
905 | NULL, &backlight_ops, | ||
906 | &props); | ||
907 | if (IS_ERR(backlight_device)) | ||
908 | goto error_no_backlight; | ||
909 | |||
910 | backlight_device->props.brightness = read_brightness(); | ||
911 | backlight_device->props.power = FB_BLANK_UNBLANK; | ||
912 | backlight_update_status(backlight_device); | ||
913 | |||
914 | retval = init_wireless(sdev); | ||
915 | if (retval) | ||
916 | goto error_no_rfk; | ||
917 | 1406 | ||
918 | retval = device_create_file(&sdev->dev, &dev_attr_performance_level); | 1407 | return ret; |
919 | if (retval) | 1408 | } |
920 | goto error_file_create; | 1409 | |
1410 | static void samsung_platform_exit(struct samsung_laptop *samsung) | ||
1411 | { | ||
1412 | if (samsung->platform_device) { | ||
1413 | platform_device_unregister(samsung->platform_device); | ||
1414 | samsung->platform_device = NULL; | ||
1415 | } | ||
1416 | } | ||
1417 | |||
1418 | static int __init samsung_platform_init(struct samsung_laptop *samsung) | ||
1419 | { | ||
1420 | struct platform_device *pdev; | ||
1421 | |||
1422 | pdev = platform_device_register_simple("samsung", -1, NULL, 0); | ||
1423 | if (IS_ERR(pdev)) | ||
1424 | return PTR_ERR(pdev); | ||
921 | 1425 | ||
1426 | samsung->platform_device = pdev; | ||
1427 | platform_set_drvdata(samsung->platform_device, samsung); | ||
922 | return 0; | 1428 | return 0; |
1429 | } | ||
1430 | |||
1431 | static struct samsung_quirks *quirks; | ||
1432 | |||
1433 | static int __init samsung_dmi_matched(const struct dmi_system_id *d) | ||
1434 | { | ||
1435 | quirks = d->driver_data; | ||
1436 | return 0; | ||
1437 | } | ||
1438 | |||
1439 | static struct dmi_system_id __initdata samsung_dmi_table[] = { | ||
1440 | { | ||
1441 | .matches = { | ||
1442 | DMI_MATCH(DMI_SYS_VENDOR, | ||
1443 | "SAMSUNG ELECTRONICS CO., LTD."), | ||
1444 | DMI_MATCH(DMI_CHASSIS_TYPE, "8"), /* Portable */ | ||
1445 | }, | ||
1446 | }, | ||
1447 | { | ||
1448 | .matches = { | ||
1449 | DMI_MATCH(DMI_SYS_VENDOR, | ||
1450 | "SAMSUNG ELECTRONICS CO., LTD."), | ||
1451 | DMI_MATCH(DMI_CHASSIS_TYPE, "9"), /* Laptop */ | ||
1452 | }, | ||
1453 | }, | ||
1454 | { | ||
1455 | .matches = { | ||
1456 | DMI_MATCH(DMI_SYS_VENDOR, | ||
1457 | "SAMSUNG ELECTRONICS CO., LTD."), | ||
1458 | DMI_MATCH(DMI_CHASSIS_TYPE, "10"), /* Notebook */ | ||
1459 | }, | ||
1460 | }, | ||
1461 | { | ||
1462 | .matches = { | ||
1463 | DMI_MATCH(DMI_SYS_VENDOR, | ||
1464 | "SAMSUNG ELECTRONICS CO., LTD."), | ||
1465 | DMI_MATCH(DMI_CHASSIS_TYPE, "14"), /* Sub-Notebook */ | ||
1466 | }, | ||
1467 | }, | ||
1468 | /* Specific DMI ids for laptop with quirks */ | ||
1469 | { | ||
1470 | .callback = samsung_dmi_matched, | ||
1471 | .ident = "N150P", | ||
1472 | .matches = { | ||
1473 | DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | ||
1474 | DMI_MATCH(DMI_PRODUCT_NAME, "N150P"), | ||
1475 | DMI_MATCH(DMI_BOARD_NAME, "N150P"), | ||
1476 | }, | ||
1477 | .driver_data = &samsung_broken_acpi_video, | ||
1478 | }, | ||
1479 | { | ||
1480 | .callback = samsung_dmi_matched, | ||
1481 | .ident = "N145P/N250P/N260P", | ||
1482 | .matches = { | ||
1483 | DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | ||
1484 | DMI_MATCH(DMI_PRODUCT_NAME, "N145P/N250P/N260P"), | ||
1485 | DMI_MATCH(DMI_BOARD_NAME, "N145P/N250P/N260P"), | ||
1486 | }, | ||
1487 | .driver_data = &samsung_broken_acpi_video, | ||
1488 | }, | ||
1489 | { | ||
1490 | .callback = samsung_dmi_matched, | ||
1491 | .ident = "N150/N210/N220", | ||
1492 | .matches = { | ||
1493 | DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | ||
1494 | DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220"), | ||
1495 | DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220"), | ||
1496 | }, | ||
1497 | .driver_data = &samsung_broken_acpi_video, | ||
1498 | }, | ||
1499 | { | ||
1500 | .callback = samsung_dmi_matched, | ||
1501 | .ident = "NF110/NF210/NF310", | ||
1502 | .matches = { | ||
1503 | DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | ||
1504 | DMI_MATCH(DMI_PRODUCT_NAME, "NF110/NF210/NF310"), | ||
1505 | DMI_MATCH(DMI_BOARD_NAME, "NF110/NF210/NF310"), | ||
1506 | }, | ||
1507 | .driver_data = &samsung_broken_acpi_video, | ||
1508 | }, | ||
1509 | { }, | ||
1510 | }; | ||
1511 | MODULE_DEVICE_TABLE(dmi, samsung_dmi_table); | ||
923 | 1512 | ||
924 | error_file_create: | 1513 | static struct platform_device *samsung_platform_device; |
925 | destroy_wireless(); | ||
926 | 1514 | ||
927 | error_no_rfk: | 1515 | static int __init samsung_init(void) |
928 | backlight_device_unregister(backlight_device); | 1516 | { |
1517 | struct samsung_laptop *samsung; | ||
1518 | int ret; | ||
929 | 1519 | ||
930 | error_no_backlight: | 1520 | quirks = &samsung_unknown; |
931 | platform_device_unregister(sdev); | 1521 | if (!force && !dmi_check_system(samsung_dmi_table)) |
1522 | return -ENODEV; | ||
1523 | |||
1524 | samsung = kzalloc(sizeof(*samsung), GFP_KERNEL); | ||
1525 | if (!samsung) | ||
1526 | return -ENOMEM; | ||
932 | 1527 | ||
933 | error_no_platform: | 1528 | mutex_init(&samsung->sabi_mutex); |
934 | iounmap(sabi_iface); | 1529 | samsung->handle_backlight = true; |
1530 | samsung->quirks = quirks; | ||
935 | 1531 | ||
936 | error_no_signature: | 1532 | |
937 | iounmap(f0000_segment); | 1533 | #if (defined CONFIG_ACPI_VIDEO || defined CONFIG_ACPI_VIDEO_MODULE) |
938 | return -EINVAL; | 1534 | /* Don't handle backlight here if the acpi video already handle it */ |
1535 | if (acpi_video_backlight_support()) { | ||
1536 | if (samsung->quirks->broken_acpi_video) { | ||
1537 | pr_info("Disabling ACPI video driver\n"); | ||
1538 | acpi_video_unregister(); | ||
1539 | } else { | ||
1540 | samsung->handle_backlight = false; | ||
1541 | } | ||
1542 | } | ||
1543 | #endif | ||
1544 | |||
1545 | ret = samsung_platform_init(samsung); | ||
1546 | if (ret) | ||
1547 | goto error_platform; | ||
1548 | |||
1549 | ret = samsung_sabi_init(samsung); | ||
1550 | if (ret) | ||
1551 | goto error_sabi; | ||
1552 | |||
1553 | #ifdef CONFIG_ACPI | ||
1554 | /* Only log that if we are really on a sabi platform */ | ||
1555 | if (acpi_video_backlight_support() && | ||
1556 | !samsung->quirks->broken_acpi_video) | ||
1557 | pr_info("Backlight controlled by ACPI video driver\n"); | ||
1558 | #endif | ||
1559 | |||
1560 | ret = samsung_sysfs_init(samsung); | ||
1561 | if (ret) | ||
1562 | goto error_sysfs; | ||
1563 | |||
1564 | ret = samsung_backlight_init(samsung); | ||
1565 | if (ret) | ||
1566 | goto error_backlight; | ||
1567 | |||
1568 | ret = samsung_rfkill_init(samsung); | ||
1569 | if (ret) | ||
1570 | goto error_rfkill; | ||
1571 | |||
1572 | ret = samsung_leds_init(samsung); | ||
1573 | if (ret) | ||
1574 | goto error_leds; | ||
1575 | |||
1576 | ret = samsung_debugfs_init(samsung); | ||
1577 | if (ret) | ||
1578 | goto error_debugfs; | ||
1579 | |||
1580 | samsung_platform_device = samsung->platform_device; | ||
1581 | return ret; | ||
1582 | |||
1583 | error_debugfs: | ||
1584 | samsung_leds_exit(samsung); | ||
1585 | error_leds: | ||
1586 | samsung_rfkill_exit(samsung); | ||
1587 | error_rfkill: | ||
1588 | samsung_backlight_exit(samsung); | ||
1589 | error_backlight: | ||
1590 | samsung_sysfs_exit(samsung); | ||
1591 | error_sysfs: | ||
1592 | samsung_sabi_exit(samsung); | ||
1593 | error_sabi: | ||
1594 | samsung_platform_exit(samsung); | ||
1595 | error_platform: | ||
1596 | kfree(samsung); | ||
1597 | return ret; | ||
939 | } | 1598 | } |
940 | 1599 | ||
941 | static void __exit samsung_exit(void) | 1600 | static void __exit samsung_exit(void) |
942 | { | 1601 | { |
943 | /* Turn off "Linux" mode in the BIOS */ | 1602 | struct samsung_laptop *samsung; |
944 | if (sabi_config->commands.set_linux != 0xff) | 1603 | |
945 | sabi_set_command(sabi_config->commands.set_linux, 0x80); | 1604 | samsung = platform_get_drvdata(samsung_platform_device); |
946 | 1605 | ||
947 | device_remove_file(&sdev->dev, &dev_attr_performance_level); | 1606 | samsung_debugfs_exit(samsung); |
948 | backlight_device_unregister(backlight_device); | 1607 | samsung_leds_exit(samsung); |
949 | destroy_wireless(); | 1608 | samsung_rfkill_exit(samsung); |
950 | iounmap(sabi_iface); | 1609 | samsung_backlight_exit(samsung); |
951 | iounmap(f0000_segment); | 1610 | samsung_sysfs_exit(samsung); |
952 | platform_device_unregister(sdev); | 1611 | samsung_sabi_exit(samsung); |
1612 | samsung_platform_exit(samsung); | ||
1613 | |||
1614 | kfree(samsung); | ||
1615 | samsung_platform_device = NULL; | ||
953 | } | 1616 | } |
954 | 1617 | ||
955 | module_init(samsung_init); | 1618 | module_init(samsung_init); |