blob: 918fa358c11e9c0ea98094f5bf91b7470b9f05c7 [file] [log] [blame]
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001/*
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>
Corentin Charyf34cd9c2011-11-26 11:00:00 +010024#include <linux/acpi.h>
Corentin Chary5b80fc42011-11-26 11:00:03 +010025#include <linux/seq_file.h>
26#include <linux/debugfs.h>
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -050027
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
Corentin Chary7e960712011-11-26 11:00:02 +010047/* Structure get/set data using sabi */
48struct sabi_data {
49 union {
50 struct {
51 u32 d0;
52 u32 d1;
53 u16 d2;
54 u8 d3;
55 };
56 u8 data[11];
57 };
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -050058};
59
60struct 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
69struct sabi_commands {
70 /*
71 * Brightness is 0 - 8, as described above.
72 * Value 0 is for the BIOS to use
73 */
Corentin Chary7e960712011-11-26 11:00:02 +010074 u16 get_brightness;
75 u16 set_brightness;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -050076
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 */
Corentin Chary7e960712011-11-26 11:00:02 +010086 u16 get_wireless_button;
87 u16 set_wireless_button;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -050088
89 /* 0 is off, 1 is on */
Corentin Chary7e960712011-11-26 11:00:02 +010090 u16 get_backlight;
91 u16 set_backlight;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -050092
93 /*
94 * 0x80 or 0x00 - no action
95 * 0x81 - recovery key pressed
96 */
Corentin Chary7e960712011-11-26 11:00:02 +010097 u16 get_recovery_mode;
98 u16 set_recovery_mode;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -050099
100 /*
101 * on seclinux: 0 is low, 1 is high,
102 * on swsmi: 0 is normal, 1 is silent, 2 is turbo
103 */
Corentin Chary7e960712011-11-26 11:00:02 +0100104 u16 get_performance_level;
105 u16 set_performance_level;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500106
Corentin Charycb5b5c92011-11-26 11:00:05 +0100107 /* 0x80 is off, 0x81 is on */
108 u16 get_battery_life_extender;
109 u16 set_battery_life_extender;
110
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500111 /*
112 * Tell the BIOS that Linux is running on this machine.
113 * 81 is on, 80 is off
114 */
Corentin Chary7e960712011-11-26 11:00:02 +0100115 u16 set_linux;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500116};
117
118struct sabi_performance_level {
119 const char *name;
Corentin Chary7e960712011-11-26 11:00:02 +0100120 u16 value;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500121};
122
123struct sabi_config {
124 const char *test_string;
125 u16 main_function;
126 const struct sabi_header_offsets header_offsets;
127 const struct sabi_commands commands;
128 const struct sabi_performance_level performance_levels[4];
129 u8 min_brightness;
130 u8 max_brightness;
131};
132
133static const struct sabi_config sabi_configs[] = {
134 {
135 .test_string = "SECLINUX",
136
137 .main_function = 0x4c49,
138
139 .header_offsets = {
140 .port = 0x00,
141 .re_mem = 0x02,
142 .iface_func = 0x03,
143 .en_mem = 0x04,
144 .data_offset = 0x05,
145 .data_segment = 0x07,
146 },
147
148 .commands = {
149 .get_brightness = 0x00,
150 .set_brightness = 0x01,
151
152 .get_wireless_button = 0x02,
153 .set_wireless_button = 0x03,
154
155 .get_backlight = 0x04,
156 .set_backlight = 0x05,
157
158 .get_recovery_mode = 0x06,
159 .set_recovery_mode = 0x07,
160
161 .get_performance_level = 0x08,
162 .set_performance_level = 0x09,
163
Corentin Charycb5b5c92011-11-26 11:00:05 +0100164 .get_battery_life_extender = 0xFFFF,
165 .set_battery_life_extender = 0xFFFF,
166
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500167 .set_linux = 0x0a,
168 },
169
170 .performance_levels = {
171 {
172 .name = "silent",
173 .value = 0,
174 },
175 {
176 .name = "normal",
177 .value = 1,
178 },
179 { },
180 },
181 .min_brightness = 1,
182 .max_brightness = 8,
183 },
184 {
185 .test_string = "SwSmi@",
186
187 .main_function = 0x5843,
188
189 .header_offsets = {
190 .port = 0x00,
191 .re_mem = 0x04,
192 .iface_func = 0x02,
193 .en_mem = 0x03,
194 .data_offset = 0x05,
195 .data_segment = 0x07,
196 },
197
198 .commands = {
199 .get_brightness = 0x10,
200 .set_brightness = 0x11,
201
202 .get_wireless_button = 0x12,
203 .set_wireless_button = 0x13,
204
205 .get_backlight = 0x2d,
206 .set_backlight = 0x2e,
207
208 .get_recovery_mode = 0xff,
209 .set_recovery_mode = 0xff,
210
211 .get_performance_level = 0x31,
212 .set_performance_level = 0x32,
213
Corentin Charycb5b5c92011-11-26 11:00:05 +0100214 .get_battery_life_extender = 0x65,
215 .set_battery_life_extender = 0x66,
216
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500217 .set_linux = 0xff,
218 },
219
220 .performance_levels = {
221 {
222 .name = "normal",
223 .value = 0,
224 },
225 {
226 .name = "silent",
227 .value = 1,
228 },
229 {
230 .name = "overclock",
231 .value = 2,
232 },
233 { },
234 },
235 .min_brightness = 0,
236 .max_brightness = 8,
237 },
238 { },
239};
240
Corentin Chary5b80fc42011-11-26 11:00:03 +0100241/*
242 * samsung-laptop/ - debugfs root directory
243 * f0000_segment - dump f0000 segment
244 * command - current command
245 * data - current data
246 * d0, d1, d2, d3 - data fields
247 * call - call SABI using command and data
248 *
249 * This allow to call arbitrary sabi commands wihout
250 * modifying the driver at all.
251 * For example, setting the keyboard backlight brightness to 5
252 *
253 * echo 0x78 > command
254 * echo 0x0582 > d0
255 * echo 0 > d1
256 * echo 0 > d2
257 * echo 0 > d3
258 * cat call
259 */
260
261struct samsung_laptop_debug {
262 struct dentry *root;
263 struct sabi_data data;
264 u16 command;
265
266 struct debugfs_blob_wrapper f0000_wrapper;
267 struct debugfs_blob_wrapper data_wrapper;
268};
269
Corentin Charya6df4892011-11-26 10:59:58 +0100270struct samsung_laptop {
271 const struct sabi_config *config;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500272
Corentin Charya6df4892011-11-26 10:59:58 +0100273 void __iomem *sabi;
274 void __iomem *sabi_iface;
275 void __iomem *f0000_segment;
276
277 struct mutex sabi_mutex;
278
Corentin Chary5dea7a22011-11-26 10:59:59 +0100279 struct platform_device *platform_device;
Corentin Charya6df4892011-11-26 10:59:58 +0100280 struct backlight_device *backlight_device;
281 struct rfkill *rfk;
282
Corentin Chary5b80fc42011-11-26 11:00:03 +0100283 struct samsung_laptop_debug debug;
284
Corentin Charyf34cd9c2011-11-26 11:00:00 +0100285 bool handle_backlight;
Corentin Charya6df4892011-11-26 10:59:58 +0100286 bool has_stepping_quirk;
287};
288
Corentin Chary5dea7a22011-11-26 10:59:59 +0100289
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500290
Rusty Russell90ab5ee2012-01-13 09:32:20 +1030291static bool force;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500292module_param(force, bool, 0);
293MODULE_PARM_DESC(force,
294 "Disable the DMI check and forces the driver to be loaded");
295
Rusty Russell90ab5ee2012-01-13 09:32:20 +1030296static bool debug;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500297module_param(debug, bool, S_IRUGO | S_IWUSR);
298MODULE_PARM_DESC(debug, "Debug enabled or not");
299
Corentin Chary7e960712011-11-26 11:00:02 +0100300static int sabi_command(struct samsung_laptop *samsung, u16 command,
301 struct sabi_data *in,
302 struct sabi_data *out)
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500303{
Corentin Charya6df4892011-11-26 10:59:58 +0100304 const struct sabi_config *config = samsung->config;
Corentin Chary7e960712011-11-26 11:00:02 +0100305 int ret = 0;
Corentin Charya6df4892011-11-26 10:59:58 +0100306 u16 port = readw(samsung->sabi + config->header_offsets.port);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500307 u8 complete, iface_data;
308
Corentin Charya6df4892011-11-26 10:59:58 +0100309 mutex_lock(&samsung->sabi_mutex);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500310
Corentin Chary7e960712011-11-26 11:00:02 +0100311 if (debug) {
312 if (in)
313 pr_info("SABI 0x%04x {0x%08x, 0x%08x, 0x%04x, 0x%02x}",
314 command, in->d0, in->d1, in->d2, in->d3);
315 else
316 pr_info("SABI 0x%04x", command);
317 }
318
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500319 /* enable memory to be able to write to it */
Corentin Charya6df4892011-11-26 10:59:58 +0100320 outb(readb(samsung->sabi + config->header_offsets.en_mem), port);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500321
322 /* write out the command */
Corentin Charya6df4892011-11-26 10:59:58 +0100323 writew(config->main_function, samsung->sabi_iface + SABI_IFACE_MAIN);
324 writew(command, samsung->sabi_iface + SABI_IFACE_SUB);
325 writeb(0, samsung->sabi_iface + SABI_IFACE_COMPLETE);
Corentin Chary7e960712011-11-26 11:00:02 +0100326 if (in) {
327 writel(in->d0, samsung->sabi_iface + SABI_IFACE_DATA);
328 writel(in->d1, samsung->sabi_iface + SABI_IFACE_DATA + 4);
329 writew(in->d2, samsung->sabi_iface + SABI_IFACE_DATA + 8);
330 writeb(in->d3, samsung->sabi_iface + SABI_IFACE_DATA + 10);
331 }
Corentin Charya6df4892011-11-26 10:59:58 +0100332 outb(readb(samsung->sabi + config->header_offsets.iface_func), port);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500333
334 /* write protect memory to make it safe */
Corentin Charya6df4892011-11-26 10:59:58 +0100335 outb(readb(samsung->sabi + config->header_offsets.re_mem), port);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500336
337 /* see if the command actually succeeded */
Corentin Charya6df4892011-11-26 10:59:58 +0100338 complete = readb(samsung->sabi_iface + SABI_IFACE_COMPLETE);
339 iface_data = readb(samsung->sabi_iface + SABI_IFACE_DATA);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500340 if (complete != 0xaa || iface_data == 0xff) {
Corentin Chary7e960712011-11-26 11:00:02 +0100341 pr_warn("SABI command 0x%04x failed with"
342 " completion flag 0x%02x and interface data 0x%02x",
343 command, complete, iface_data);
344 ret = -EINVAL;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500345 goto exit;
346 }
Corentin Chary7e960712011-11-26 11:00:02 +0100347
348 if (out) {
349 out->d0 = readl(samsung->sabi_iface + SABI_IFACE_DATA);
350 out->d1 = readl(samsung->sabi_iface + SABI_IFACE_DATA + 4);
351 out->d2 = readw(samsung->sabi_iface + SABI_IFACE_DATA + 2);
352 out->d3 = readb(samsung->sabi_iface + SABI_IFACE_DATA + 1);
353 }
354
355 if (debug && out) {
356 pr_info("SABI {0x%08x, 0x%08x, 0x%04x, 0x%02x}",
357 out->d0, out->d1, out->d2, out->d3);
358 }
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500359
360exit:
Corentin Charya6df4892011-11-26 10:59:58 +0100361 mutex_unlock(&samsung->sabi_mutex);
Corentin Chary7e960712011-11-26 11:00:02 +0100362 return ret;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500363}
364
Corentin Chary7e960712011-11-26 11:00:02 +0100365/* simple wrappers usable with most commands */
366static int sabi_set_commandb(struct samsung_laptop *samsung,
367 u16 command, u8 data)
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500368{
Corentin Chary7e960712011-11-26 11:00:02 +0100369 struct sabi_data in = { .d0 = 0, .d1 = 0, .d2 = 0, .d3 = 0 };
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500370
Corentin Chary7e960712011-11-26 11:00:02 +0100371 in.data[0] = data;
372 return sabi_command(samsung, command, &in, NULL);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500373}
374
Corentin Chary5dea7a22011-11-26 10:59:59 +0100375static int read_brightness(struct samsung_laptop *samsung)
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500376{
Corentin Charya6df4892011-11-26 10:59:58 +0100377 const struct sabi_config *config = samsung->config;
378 const struct sabi_commands *commands = &samsung->config->commands;
Corentin Chary7e960712011-11-26 11:00:02 +0100379 struct sabi_data sretval;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500380 int user_brightness = 0;
381 int retval;
382
Corentin Chary7e960712011-11-26 11:00:02 +0100383 retval = sabi_command(samsung, commands->get_brightness,
384 NULL, &sretval);
385 if (retval)
386 return retval;
387
388 user_brightness = sretval.data[0];
389 if (user_brightness > config->min_brightness)
390 user_brightness -= config->min_brightness;
391 else
392 user_brightness = 0;
393
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500394 return user_brightness;
395}
396
Corentin Chary5dea7a22011-11-26 10:59:59 +0100397static void set_brightness(struct samsung_laptop *samsung, u8 user_brightness)
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500398{
Corentin Charya6df4892011-11-26 10:59:58 +0100399 const struct sabi_config *config = samsung->config;
400 const struct sabi_commands *commands = &samsung->config->commands;
401 u8 user_level = user_brightness + config->min_brightness;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500402
Corentin Charya6df4892011-11-26 10:59:58 +0100403 if (samsung->has_stepping_quirk && user_level != 0) {
Jason Stubbsac080522011-09-20 09:16:13 -0700404 /*
405 * short circuit if the specified level is what's already set
406 * to prevent the screen from flickering needlessly
407 */
Corentin Chary5dea7a22011-11-26 10:59:59 +0100408 if (user_brightness == read_brightness(samsung))
Jason Stubbsac080522011-09-20 09:16:13 -0700409 return;
410
Corentin Chary7e960712011-11-26 11:00:02 +0100411 sabi_set_commandb(samsung, commands->set_brightness, 0);
Jason Stubbsac080522011-09-20 09:16:13 -0700412 }
413
Corentin Chary7e960712011-11-26 11:00:02 +0100414 sabi_set_commandb(samsung, commands->set_brightness, user_level);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500415}
416
417static int get_brightness(struct backlight_device *bd)
418{
Corentin Chary5dea7a22011-11-26 10:59:59 +0100419 struct samsung_laptop *samsung = bl_get_data(bd);
420
421 return read_brightness(samsung);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500422}
423
Corentin Chary5dea7a22011-11-26 10:59:59 +0100424static void check_for_stepping_quirk(struct samsung_laptop *samsung)
Jason Stubbsac080522011-09-20 09:16:13 -0700425{
Corentin Chary5dea7a22011-11-26 10:59:59 +0100426 int initial_level;
427 int check_level;
428 int orig_level = read_brightness(samsung);
Jason Stubbsac080522011-09-20 09:16:13 -0700429
430 /*
431 * Some laptops exhibit the strange behaviour of stepping toward
432 * (rather than setting) the brightness except when changing to/from
433 * brightness level 0. This behaviour is checked for here and worked
434 * around in set_brightness.
435 */
436
John Serockba05b232011-10-13 06:42:01 -0400437 if (orig_level == 0)
Corentin Chary5dea7a22011-11-26 10:59:59 +0100438 set_brightness(samsung, 1);
John Serockba05b232011-10-13 06:42:01 -0400439
Corentin Chary5dea7a22011-11-26 10:59:59 +0100440 initial_level = read_brightness(samsung);
John Serockba05b232011-10-13 06:42:01 -0400441
Jason Stubbsac080522011-09-20 09:16:13 -0700442 if (initial_level <= 2)
443 check_level = initial_level + 2;
444 else
445 check_level = initial_level - 2;
446
Corentin Charya6df4892011-11-26 10:59:58 +0100447 samsung->has_stepping_quirk = false;
Corentin Chary5dea7a22011-11-26 10:59:59 +0100448 set_brightness(samsung, check_level);
Jason Stubbsac080522011-09-20 09:16:13 -0700449
Corentin Chary5dea7a22011-11-26 10:59:59 +0100450 if (read_brightness(samsung) != check_level) {
Corentin Charya6df4892011-11-26 10:59:58 +0100451 samsung->has_stepping_quirk = true;
Jason Stubbsac080522011-09-20 09:16:13 -0700452 pr_info("enabled workaround for brightness stepping quirk\n");
453 }
454
Corentin Chary5dea7a22011-11-26 10:59:59 +0100455 set_brightness(samsung, orig_level);
Jason Stubbsac080522011-09-20 09:16:13 -0700456}
457
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500458static int update_status(struct backlight_device *bd)
459{
Corentin Chary5dea7a22011-11-26 10:59:59 +0100460 struct samsung_laptop *samsung = bl_get_data(bd);
Corentin Charya6df4892011-11-26 10:59:58 +0100461 const struct sabi_commands *commands = &samsung->config->commands;
462
Corentin Chary5dea7a22011-11-26 10:59:59 +0100463 set_brightness(samsung, bd->props.brightness);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500464
465 if (bd->props.power == FB_BLANK_UNBLANK)
Corentin Chary7e960712011-11-26 11:00:02 +0100466 sabi_set_commandb(samsung, commands->set_backlight, 1);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500467 else
Corentin Chary7e960712011-11-26 11:00:02 +0100468 sabi_set_commandb(samsung, commands->set_backlight, 0);
Corentin Chary5dea7a22011-11-26 10:59:59 +0100469
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500470 return 0;
471}
472
473static const struct backlight_ops backlight_ops = {
474 .get_brightness = get_brightness,
475 .update_status = update_status,
476};
477
478static int rfkill_set(void *data, bool blocked)
479{
Corentin Chary5dea7a22011-11-26 10:59:59 +0100480 struct samsung_laptop *samsung = data;
Corentin Charya6df4892011-11-26 10:59:58 +0100481 const struct sabi_commands *commands = &samsung->config->commands;
482
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500483 /* Do something with blocked...*/
484 /*
485 * blocked == false is on
486 * blocked == true is off
487 */
488 if (blocked)
Corentin Chary7e960712011-11-26 11:00:02 +0100489 sabi_set_commandb(samsung, commands->set_wireless_button, 0);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500490 else
Corentin Chary7e960712011-11-26 11:00:02 +0100491 sabi_set_commandb(samsung, commands->set_wireless_button, 1);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500492
493 return 0;
494}
495
496static struct rfkill_ops rfkill_ops = {
497 .set_block = rfkill_set,
498};
499
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500500static ssize_t get_performance_level(struct device *dev,
501 struct device_attribute *attr, char *buf)
502{
Corentin Chary5dea7a22011-11-26 10:59:59 +0100503 struct samsung_laptop *samsung = dev_get_drvdata(dev);
Corentin Charya6df4892011-11-26 10:59:58 +0100504 const struct sabi_config *config = samsung->config;
Corentin Chary5dea7a22011-11-26 10:59:59 +0100505 const struct sabi_commands *commands = &config->commands;
Corentin Chary7e960712011-11-26 11:00:02 +0100506 struct sabi_data sretval;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500507 int retval;
508 int i;
509
510 /* Read the state */
Corentin Chary7e960712011-11-26 11:00:02 +0100511 retval = sabi_command(samsung, commands->get_performance_level,
512 NULL, &sretval);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500513 if (retval)
514 return retval;
515
516 /* The logic is backwards, yeah, lots of fun... */
Corentin Charya6df4892011-11-26 10:59:58 +0100517 for (i = 0; config->performance_levels[i].name; ++i) {
Corentin Chary7e960712011-11-26 11:00:02 +0100518 if (sretval.data[0] == config->performance_levels[i].value)
Corentin Charya6df4892011-11-26 10:59:58 +0100519 return sprintf(buf, "%s\n", config->performance_levels[i].name);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500520 }
521 return sprintf(buf, "%s\n", "unknown");
522}
523
524static ssize_t set_performance_level(struct device *dev,
525 struct device_attribute *attr, const char *buf,
526 size_t count)
527{
Corentin Chary5dea7a22011-11-26 10:59:59 +0100528 struct samsung_laptop *samsung = dev_get_drvdata(dev);
Corentin Charya6df4892011-11-26 10:59:58 +0100529 const struct sabi_config *config = samsung->config;
Corentin Chary5dea7a22011-11-26 10:59:59 +0100530 const struct sabi_commands *commands = &config->commands;
531 int i;
Corentin Charya6df4892011-11-26 10:59:58 +0100532
Corentin Chary5dea7a22011-11-26 10:59:59 +0100533 if (count < 1)
534 return count;
535
536 for (i = 0; config->performance_levels[i].name; ++i) {
537 const struct sabi_performance_level *level =
538 &config->performance_levels[i];
539 if (!strncasecmp(level->name, buf, strlen(level->name))) {
Corentin Chary7e960712011-11-26 11:00:02 +0100540 sabi_set_commandb(samsung,
541 commands->set_performance_level,
542 level->value);
Corentin Chary5dea7a22011-11-26 10:59:59 +0100543 break;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500544 }
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500545 }
Corentin Chary5dea7a22011-11-26 10:59:59 +0100546
547 if (!config->performance_levels[i].name)
548 return -EINVAL;
549
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500550 return count;
551}
Corentin Chary5dea7a22011-11-26 10:59:59 +0100552
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500553static DEVICE_ATTR(performance_level, S_IWUSR | S_IRUGO,
554 get_performance_level, set_performance_level);
555
Corentin Charycb5b5c92011-11-26 11:00:05 +0100556static int read_battery_life_extender(struct samsung_laptop *samsung)
557{
558 const struct sabi_commands *commands = &samsung->config->commands;
559 struct sabi_data data;
560 int retval;
561
562 if (commands->get_battery_life_extender == 0xFFFF)
563 return -ENODEV;
564
565 memset(&data, 0, sizeof(data));
566 data.data[0] = 0x80;
567 retval = sabi_command(samsung, commands->get_battery_life_extender,
568 &data, &data);
569
570 if (retval)
571 return retval;
572
573 if (data.data[0] != 0 && data.data[0] != 1)
574 return -ENODEV;
575
576 return data.data[0];
577}
578
579static int write_battery_life_extender(struct samsung_laptop *samsung,
580 int enabled)
581{
582 const struct sabi_commands *commands = &samsung->config->commands;
583 struct sabi_data data;
584
585 memset(&data, 0, sizeof(data));
586 data.data[0] = 0x80 | enabled;
587 return sabi_command(samsung, commands->set_battery_life_extender,
588 &data, NULL);
589}
590
591static ssize_t get_battery_life_extender(struct device *dev,
592 struct device_attribute *attr,
593 char *buf)
594{
595 struct samsung_laptop *samsung = dev_get_drvdata(dev);
596 int ret;
597
598 ret = read_battery_life_extender(samsung);
599 if (ret < 0)
600 return ret;
601
602 return sprintf(buf, "%d\n", ret);
603}
604
605static ssize_t set_battery_life_extender(struct device *dev,
606 struct device_attribute *attr,
607 const char *buf, size_t count)
608{
609 struct samsung_laptop *samsung = dev_get_drvdata(dev);
610 int ret, value;
611
612 if (!count || sscanf(buf, "%i", &value) != 1)
613 return -EINVAL;
614
615 ret = write_battery_life_extender(samsung, !!value);
616 if (ret < 0)
617 return ret;
618
619 return count;
620}
621
622static DEVICE_ATTR(battery_life_extender, S_IWUSR | S_IRUGO,
623 get_battery_life_extender, set_battery_life_extender);
624
Corentin Charya66c1662011-11-26 11:00:01 +0100625static struct attribute *platform_attributes[] = {
626 &dev_attr_performance_level.attr,
Corentin Charycb5b5c92011-11-26 11:00:05 +0100627 &dev_attr_battery_life_extender.attr,
Corentin Charya66c1662011-11-26 11:00:01 +0100628 NULL
629};
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500630
Corentin Chary5dea7a22011-11-26 10:59:59 +0100631static int find_signature(void __iomem *memcheck, const char *testStr)
632{
633 int i = 0;
634 int loca;
635
636 for (loca = 0; loca < 0xffff; loca++) {
637 char temp = readb(memcheck + loca);
638
639 if (temp == testStr[i]) {
640 if (i == strlen(testStr)-1)
641 break;
642 ++i;
643 } else {
644 i = 0;
645 }
646 }
647 return loca;
648}
649
650static void samsung_rfkill_exit(struct samsung_laptop *samsung)
651{
652 if (samsung->rfk) {
653 rfkill_unregister(samsung->rfk);
654 rfkill_destroy(samsung->rfk);
655 samsung->rfk = NULL;
656 }
657}
658
659static int __init samsung_rfkill_init(struct samsung_laptop *samsung)
660{
661 int retval;
662
663 samsung->rfk = rfkill_alloc("samsung-wifi",
664 &samsung->platform_device->dev,
665 RFKILL_TYPE_WLAN,
666 &rfkill_ops, samsung);
667 if (!samsung->rfk)
668 return -ENOMEM;
669
670 retval = rfkill_register(samsung->rfk);
671 if (retval) {
672 rfkill_destroy(samsung->rfk);
673 samsung->rfk = NULL;
674 return -ENODEV;
675 }
676
677 return 0;
678}
679
680static void samsung_backlight_exit(struct samsung_laptop *samsung)
681{
682 if (samsung->backlight_device) {
683 backlight_device_unregister(samsung->backlight_device);
684 samsung->backlight_device = NULL;
685 }
686}
687
688static int __init samsung_backlight_init(struct samsung_laptop *samsung)
689{
690 struct backlight_device *bd;
691 struct backlight_properties props;
692
Corentin Charyf34cd9c2011-11-26 11:00:00 +0100693 if (!samsung->handle_backlight)
694 return 0;
695
Corentin Chary5dea7a22011-11-26 10:59:59 +0100696 memset(&props, 0, sizeof(struct backlight_properties));
697 props.type = BACKLIGHT_PLATFORM;
698 props.max_brightness = samsung->config->max_brightness -
699 samsung->config->min_brightness;
700
701 bd = backlight_device_register("samsung",
702 &samsung->platform_device->dev,
703 samsung, &backlight_ops,
704 &props);
705 if (IS_ERR(bd))
706 return PTR_ERR(bd);
707
708 samsung->backlight_device = bd;
709 samsung->backlight_device->props.brightness = read_brightness(samsung);
710 samsung->backlight_device->props.power = FB_BLANK_UNBLANK;
711 backlight_update_status(samsung->backlight_device);
712
713 return 0;
714}
715
Corentin Charya66c1662011-11-26 11:00:01 +0100716static mode_t samsung_sysfs_is_visible(struct kobject *kobj,
717 struct attribute *attr, int idx)
718{
719 struct device *dev = container_of(kobj, struct device, kobj);
720 struct platform_device *pdev = to_platform_device(dev);
721 struct samsung_laptop *samsung = platform_get_drvdata(pdev);
722 bool ok = true;
723
724 if (attr == &dev_attr_performance_level.attr)
725 ok = !!samsung->config->performance_levels[0].name;
Corentin Charycb5b5c92011-11-26 11:00:05 +0100726 if (attr == &dev_attr_battery_life_extender.attr)
727 ok = !!(read_battery_life_extender(samsung) >= 0);
Corentin Charya66c1662011-11-26 11:00:01 +0100728
729 return ok ? attr->mode : 0;
730}
731
732static struct attribute_group platform_attribute_group = {
733 .is_visible = samsung_sysfs_is_visible,
734 .attrs = platform_attributes
735};
736
Corentin Chary5dea7a22011-11-26 10:59:59 +0100737static void samsung_sysfs_exit(struct samsung_laptop *samsung)
738{
Corentin Charya66c1662011-11-26 11:00:01 +0100739 struct platform_device *device = samsung->platform_device;
740
741 sysfs_remove_group(&device->dev.kobj, &platform_attribute_group);
Corentin Chary5dea7a22011-11-26 10:59:59 +0100742}
743
744static int __init samsung_sysfs_init(struct samsung_laptop *samsung)
745{
Corentin Charya66c1662011-11-26 11:00:01 +0100746 struct platform_device *device = samsung->platform_device;
747
748 return sysfs_create_group(&device->dev.kobj, &platform_attribute_group);
749
Corentin Chary5dea7a22011-11-26 10:59:59 +0100750}
751
Corentin Chary5b80fc42011-11-26 11:00:03 +0100752static int show_call(struct seq_file *m, void *data)
753{
754 struct samsung_laptop *samsung = m->private;
755 struct sabi_data *sdata = &samsung->debug.data;
756 int ret;
757
758 seq_printf(m, "SABI 0x%04x {0x%08x, 0x%08x, 0x%04x, 0x%02x}\n",
759 samsung->debug.command,
760 sdata->d0, sdata->d1, sdata->d2, sdata->d3);
761
762 ret = sabi_command(samsung, samsung->debug.command, sdata, sdata);
763
764 if (ret) {
765 seq_printf(m, "SABI command 0x%04x failed\n",
766 samsung->debug.command);
767 return ret;
768 }
769
770 seq_printf(m, "SABI {0x%08x, 0x%08x, 0x%04x, 0x%02x}\n",
771 sdata->d0, sdata->d1, sdata->d2, sdata->d3);
772 return 0;
773}
774
775static int samsung_debugfs_open(struct inode *inode, struct file *file)
776{
777 return single_open(file, show_call, inode->i_private);
778}
779
780static const struct file_operations samsung_laptop_call_io_ops = {
781 .owner = THIS_MODULE,
782 .open = samsung_debugfs_open,
783 .read = seq_read,
784 .llseek = seq_lseek,
785 .release = single_release,
786};
787
788static void samsung_debugfs_exit(struct samsung_laptop *samsung)
789{
790 debugfs_remove_recursive(samsung->debug.root);
791}
792
793static int samsung_debugfs_init(struct samsung_laptop *samsung)
794{
795 struct dentry *dent;
796
797 samsung->debug.root = debugfs_create_dir("samsung-laptop", NULL);
798 if (!samsung->debug.root) {
799 pr_err("failed to create debugfs directory");
800 goto error_debugfs;
801 }
802
803 samsung->debug.f0000_wrapper.data = samsung->f0000_segment;
804 samsung->debug.f0000_wrapper.size = 0xffff;
805
806 samsung->debug.data_wrapper.data = &samsung->debug.data;
807 samsung->debug.data_wrapper.size = sizeof(samsung->debug.data);
808
809 dent = debugfs_create_u16("command", S_IRUGO | S_IWUSR,
810 samsung->debug.root, &samsung->debug.command);
811 if (!dent)
812 goto error_debugfs;
813
814 dent = debugfs_create_u32("d0", S_IRUGO | S_IWUSR, samsung->debug.root,
815 &samsung->debug.data.d0);
816 if (!dent)
817 goto error_debugfs;
818
819 dent = debugfs_create_u32("d1", S_IRUGO | S_IWUSR, samsung->debug.root,
820 &samsung->debug.data.d1);
821 if (!dent)
822 goto error_debugfs;
823
824 dent = debugfs_create_u16("d2", S_IRUGO | S_IWUSR, samsung->debug.root,
825 &samsung->debug.data.d2);
826 if (!dent)
827 goto error_debugfs;
828
829 dent = debugfs_create_u8("d3", S_IRUGO | S_IWUSR, samsung->debug.root,
830 &samsung->debug.data.d3);
831 if (!dent)
832 goto error_debugfs;
833
834 dent = debugfs_create_blob("data", S_IRUGO | S_IWUSR,
835 samsung->debug.root,
836 &samsung->debug.data_wrapper);
837 if (!dent)
838 goto error_debugfs;
839
840 dent = debugfs_create_blob("f0000_segment", S_IRUSR | S_IWUSR,
841 samsung->debug.root,
842 &samsung->debug.f0000_wrapper);
843 if (!dent)
844 goto error_debugfs;
845
846 dent = debugfs_create_file("call", S_IFREG | S_IRUGO,
847 samsung->debug.root, samsung,
848 &samsung_laptop_call_io_ops);
849 if (!dent)
850 goto error_debugfs;
851
852 return 0;
853
854error_debugfs:
855 samsung_debugfs_exit(samsung);
856 return -ENOMEM;
857}
858
Corentin Chary5dea7a22011-11-26 10:59:59 +0100859static void samsung_sabi_exit(struct samsung_laptop *samsung)
860{
861 const struct sabi_config *config = samsung->config;
862
863 /* Turn off "Linux" mode in the BIOS */
864 if (config && config->commands.set_linux != 0xff)
Corentin Chary7e960712011-11-26 11:00:02 +0100865 sabi_set_commandb(samsung, config->commands.set_linux, 0x80);
Corentin Chary5dea7a22011-11-26 10:59:59 +0100866
867 if (samsung->sabi_iface) {
868 iounmap(samsung->sabi_iface);
869 samsung->sabi_iface = NULL;
870 }
871 if (samsung->f0000_segment) {
872 iounmap(samsung->f0000_segment);
873 samsung->f0000_segment = NULL;
874 }
875
876 samsung->config = NULL;
877}
878
Corentin Chary49dd7732011-11-26 11:00:04 +0100879static __init void samsung_sabi_infos(struct samsung_laptop *samsung, int loca,
880 unsigned int ifaceP)
Corentin Chary5dea7a22011-11-26 10:59:59 +0100881{
882 const struct sabi_config *config = samsung->config;
883
884 printk(KERN_DEBUG "This computer supports SABI==%x\n",
885 loca + 0xf0000 - 6);
Corentin Chary49dd7732011-11-26 11:00:04 +0100886
Corentin Chary5dea7a22011-11-26 10:59:59 +0100887 printk(KERN_DEBUG "SABI header:\n");
888 printk(KERN_DEBUG " SMI Port Number = 0x%04x\n",
889 readw(samsung->sabi + config->header_offsets.port));
890 printk(KERN_DEBUG " SMI Interface Function = 0x%02x\n",
891 readb(samsung->sabi + config->header_offsets.iface_func));
892 printk(KERN_DEBUG " SMI enable memory buffer = 0x%02x\n",
893 readb(samsung->sabi + config->header_offsets.en_mem));
894 printk(KERN_DEBUG " SMI restore memory buffer = 0x%02x\n",
895 readb(samsung->sabi + config->header_offsets.re_mem));
896 printk(KERN_DEBUG " SABI data offset = 0x%04x\n",
897 readw(samsung->sabi + config->header_offsets.data_offset));
898 printk(KERN_DEBUG " SABI data segment = 0x%04x\n",
899 readw(samsung->sabi + config->header_offsets.data_segment));
Corentin Chary5dea7a22011-11-26 10:59:59 +0100900
Corentin Chary49dd7732011-11-26 11:00:04 +0100901 printk(KERN_DEBUG " SABI pointer = 0x%08x\n", ifaceP);
Corentin Chary5dea7a22011-11-26 10:59:59 +0100902}
903
904static int __init samsung_sabi_init(struct samsung_laptop *samsung)
905{
906 const struct sabi_config *config = NULL;
907 const struct sabi_commands *commands;
908 unsigned int ifaceP;
909 int ret = 0;
910 int i;
911 int loca;
912
913 samsung->f0000_segment = ioremap_nocache(0xf0000, 0xffff);
914 if (!samsung->f0000_segment) {
915 pr_err("Can't map the segment at 0xf0000\n");
916 ret = -EINVAL;
917 goto exit;
918 }
919
920 /* Try to find one of the signatures in memory to find the header */
921 for (i = 0; sabi_configs[i].test_string != 0; ++i) {
922 samsung->config = &sabi_configs[i];
923 loca = find_signature(samsung->f0000_segment,
924 samsung->config->test_string);
925 if (loca != 0xffff)
926 break;
927 }
928
929 if (loca == 0xffff) {
930 pr_err("This computer does not support SABI\n");
931 ret = -ENODEV;
932 goto exit;
933 }
934
935 config = samsung->config;
936 commands = &config->commands;
937
938 /* point to the SMI port Number */
939 loca += 1;
940 samsung->sabi = (samsung->f0000_segment + loca);
941
Corentin Chary5dea7a22011-11-26 10:59:59 +0100942 /* Get a pointer to the SABI Interface */
943 ifaceP = (readw(samsung->sabi + config->header_offsets.data_segment) & 0x0ffff) << 4;
944 ifaceP += readw(samsung->sabi + config->header_offsets.data_offset) & 0x0ffff;
Corentin Chary49dd7732011-11-26 11:00:04 +0100945
946 if (debug)
947 samsung_sabi_infos(samsung, loca, ifaceP);
948
Corentin Chary5dea7a22011-11-26 10:59:59 +0100949 samsung->sabi_iface = ioremap_nocache(ifaceP, 16);
950 if (!samsung->sabi_iface) {
951 pr_err("Can't remap %x\n", ifaceP);
952 ret = -EINVAL;
953 goto exit;
954 }
955
Corentin Chary5dea7a22011-11-26 10:59:59 +0100956 /* Turn on "Linux" mode in the BIOS */
957 if (commands->set_linux != 0xff) {
Corentin Chary7e960712011-11-26 11:00:02 +0100958 int retval = sabi_set_commandb(samsung,
959 commands->set_linux, 0x81);
Corentin Chary5dea7a22011-11-26 10:59:59 +0100960 if (retval) {
961 pr_warn("Linux mode was not set!\n");
962 ret = -ENODEV;
963 goto exit;
964 }
965 }
966
967 /* Check for stepping quirk */
Corentin Charyf34cd9c2011-11-26 11:00:00 +0100968 if (samsung->handle_backlight)
969 check_for_stepping_quirk(samsung);
Corentin Chary5dea7a22011-11-26 10:59:59 +0100970
971exit:
972 if (ret)
973 samsung_sabi_exit(samsung);
974
975 return ret;
976}
977
978static void samsung_platform_exit(struct samsung_laptop *samsung)
979{
980 if (samsung->platform_device) {
981 platform_device_unregister(samsung->platform_device);
982 samsung->platform_device = NULL;
983 }
984}
985
986static int __init samsung_platform_init(struct samsung_laptop *samsung)
987{
988 struct platform_device *pdev;
989
990 pdev = platform_device_register_simple("samsung", -1, NULL, 0);
991 if (IS_ERR(pdev))
992 return PTR_ERR(pdev);
993
994 samsung->platform_device = pdev;
995 platform_set_drvdata(samsung->platform_device, samsung);
996 return 0;
997}
998
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500999static int __init dmi_check_cb(const struct dmi_system_id *id)
1000{
Corentin Chary5dea7a22011-11-26 10:59:59 +01001001 pr_info("found laptop model '%s'\n", id->ident);
Axel Lin27836582011-03-14 18:56:18 +08001002 return 1;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001003}
1004
1005static struct dmi_system_id __initdata samsung_dmi_table[] = {
1006 {
1007 .ident = "N128",
1008 .matches = {
1009 DMI_MATCH(DMI_SYS_VENDOR,
1010 "SAMSUNG ELECTRONICS CO., LTD."),
1011 DMI_MATCH(DMI_PRODUCT_NAME, "N128"),
1012 DMI_MATCH(DMI_BOARD_NAME, "N128"),
1013 },
1014 .callback = dmi_check_cb,
1015 },
1016 {
1017 .ident = "N130",
1018 .matches = {
1019 DMI_MATCH(DMI_SYS_VENDOR,
1020 "SAMSUNG ELECTRONICS CO., LTD."),
1021 DMI_MATCH(DMI_PRODUCT_NAME, "N130"),
1022 DMI_MATCH(DMI_BOARD_NAME, "N130"),
1023 },
1024 .callback = dmi_check_cb,
1025 },
1026 {
J Witteveen4e2441c2011-07-03 13:15:44 +02001027 .ident = "N510",
1028 .matches = {
1029 DMI_MATCH(DMI_SYS_VENDOR,
1030 "SAMSUNG ELECTRONICS CO., LTD."),
1031 DMI_MATCH(DMI_PRODUCT_NAME, "N510"),
1032 DMI_MATCH(DMI_BOARD_NAME, "N510"),
1033 },
1034 .callback = dmi_check_cb,
1035 },
1036 {
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001037 .ident = "X125",
1038 .matches = {
1039 DMI_MATCH(DMI_SYS_VENDOR,
1040 "SAMSUNG ELECTRONICS CO., LTD."),
1041 DMI_MATCH(DMI_PRODUCT_NAME, "X125"),
1042 DMI_MATCH(DMI_BOARD_NAME, "X125"),
1043 },
1044 .callback = dmi_check_cb,
1045 },
1046 {
1047 .ident = "X120/X170",
1048 .matches = {
1049 DMI_MATCH(DMI_SYS_VENDOR,
1050 "SAMSUNG ELECTRONICS CO., LTD."),
1051 DMI_MATCH(DMI_PRODUCT_NAME, "X120/X170"),
1052 DMI_MATCH(DMI_BOARD_NAME, "X120/X170"),
1053 },
1054 .callback = dmi_check_cb,
1055 },
1056 {
1057 .ident = "NC10",
1058 .matches = {
1059 DMI_MATCH(DMI_SYS_VENDOR,
1060 "SAMSUNG ELECTRONICS CO., LTD."),
1061 DMI_MATCH(DMI_PRODUCT_NAME, "NC10"),
1062 DMI_MATCH(DMI_BOARD_NAME, "NC10"),
1063 },
1064 .callback = dmi_check_cb,
1065 },
1066 {
1067 .ident = "NP-Q45",
1068 .matches = {
1069 DMI_MATCH(DMI_SYS_VENDOR,
1070 "SAMSUNG ELECTRONICS CO., LTD."),
1071 DMI_MATCH(DMI_PRODUCT_NAME, "SQ45S70S"),
1072 DMI_MATCH(DMI_BOARD_NAME, "SQ45S70S"),
1073 },
1074 .callback = dmi_check_cb,
1075 },
1076 {
1077 .ident = "X360",
1078 .matches = {
1079 DMI_MATCH(DMI_SYS_VENDOR,
1080 "SAMSUNG ELECTRONICS CO., LTD."),
1081 DMI_MATCH(DMI_PRODUCT_NAME, "X360"),
1082 DMI_MATCH(DMI_BOARD_NAME, "X360"),
1083 },
1084 .callback = dmi_check_cb,
1085 },
1086 {
Alberto Mardegan3d536ed2011-04-08 17:02:03 +02001087 .ident = "R410 Plus",
1088 .matches = {
1089 DMI_MATCH(DMI_SYS_VENDOR,
1090 "SAMSUNG ELECTRONICS CO., LTD."),
1091 DMI_MATCH(DMI_PRODUCT_NAME, "R410P"),
1092 DMI_MATCH(DMI_BOARD_NAME, "R460"),
1093 },
1094 .callback = dmi_check_cb,
1095 },
1096 {
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001097 .ident = "R518",
1098 .matches = {
1099 DMI_MATCH(DMI_SYS_VENDOR,
1100 "SAMSUNG ELECTRONICS CO., LTD."),
1101 DMI_MATCH(DMI_PRODUCT_NAME, "R518"),
1102 DMI_MATCH(DMI_BOARD_NAME, "R518"),
1103 },
1104 .callback = dmi_check_cb,
1105 },
1106 {
1107 .ident = "R519/R719",
1108 .matches = {
1109 DMI_MATCH(DMI_SYS_VENDOR,
1110 "SAMSUNG ELECTRONICS CO., LTD."),
1111 DMI_MATCH(DMI_PRODUCT_NAME, "R519/R719"),
1112 DMI_MATCH(DMI_BOARD_NAME, "R519/R719"),
1113 },
1114 .callback = dmi_check_cb,
1115 },
1116 {
Thomas Courbon78a75392011-07-20 22:57:44 +02001117 .ident = "N150/N210/N220",
1118 .matches = {
1119 DMI_MATCH(DMI_SYS_VENDOR,
1120 "SAMSUNG ELECTRONICS CO., LTD."),
1121 DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220"),
1122 DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220"),
1123 },
1124 .callback = dmi_check_cb,
1125 },
1126 {
Raul Gutierrez Segalesf689c872011-09-20 09:16:15 -07001127 .ident = "N220",
1128 .matches = {
1129 DMI_MATCH(DMI_SYS_VENDOR,
1130 "SAMSUNG ELECTRONICS CO., LTD."),
1131 DMI_MATCH(DMI_PRODUCT_NAME, "N220"),
1132 DMI_MATCH(DMI_BOARD_NAME, "N220"),
1133 },
1134 .callback = dmi_check_cb,
1135 },
1136 {
Greg Kroah-Hartman10165072011-04-08 17:02:04 +02001137 .ident = "N150/N210/N220/N230",
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001138 .matches = {
1139 DMI_MATCH(DMI_SYS_VENDOR,
1140 "SAMSUNG ELECTRONICS CO., LTD."),
Greg Kroah-Hartman10165072011-04-08 17:02:04 +02001141 DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220/N230"),
1142 DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220/N230"),
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001143 },
1144 .callback = dmi_check_cb,
1145 },
1146 {
1147 .ident = "N150P/N210P/N220P",
1148 .matches = {
1149 DMI_MATCH(DMI_SYS_VENDOR,
1150 "SAMSUNG ELECTRONICS CO., LTD."),
1151 DMI_MATCH(DMI_PRODUCT_NAME, "N150P/N210P/N220P"),
1152 DMI_MATCH(DMI_BOARD_NAME, "N150P/N210P/N220P"),
1153 },
1154 .callback = dmi_check_cb,
1155 },
1156 {
Stefan Bellerf87d0292011-09-20 09:16:08 -07001157 .ident = "R700",
1158 .matches = {
1159 DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1160 DMI_MATCH(DMI_PRODUCT_NAME, "SR700"),
1161 DMI_MATCH(DMI_BOARD_NAME, "SR700"),
1162 },
1163 .callback = dmi_check_cb,
1164 },
1165 {
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001166 .ident = "R530/R730",
1167 .matches = {
1168 DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1169 DMI_MATCH(DMI_PRODUCT_NAME, "R530/R730"),
1170 DMI_MATCH(DMI_BOARD_NAME, "R530/R730"),
1171 },
1172 .callback = dmi_check_cb,
1173 },
1174 {
1175 .ident = "NF110/NF210/NF310",
1176 .matches = {
1177 DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1178 DMI_MATCH(DMI_PRODUCT_NAME, "NF110/NF210/NF310"),
1179 DMI_MATCH(DMI_BOARD_NAME, "NF110/NF210/NF310"),
1180 },
1181 .callback = dmi_check_cb,
1182 },
1183 {
1184 .ident = "N145P/N250P/N260P",
1185 .matches = {
1186 DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1187 DMI_MATCH(DMI_PRODUCT_NAME, "N145P/N250P/N260P"),
1188 DMI_MATCH(DMI_BOARD_NAME, "N145P/N250P/N260P"),
1189 },
1190 .callback = dmi_check_cb,
1191 },
1192 {
1193 .ident = "R70/R71",
1194 .matches = {
1195 DMI_MATCH(DMI_SYS_VENDOR,
1196 "SAMSUNG ELECTRONICS CO., LTD."),
1197 DMI_MATCH(DMI_PRODUCT_NAME, "R70/R71"),
1198 DMI_MATCH(DMI_BOARD_NAME, "R70/R71"),
1199 },
1200 .callback = dmi_check_cb,
1201 },
1202 {
1203 .ident = "P460",
1204 .matches = {
1205 DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1206 DMI_MATCH(DMI_PRODUCT_NAME, "P460"),
1207 DMI_MATCH(DMI_BOARD_NAME, "P460"),
1208 },
1209 .callback = dmi_check_cb,
1210 },
Smelov Andrey093ed562011-09-20 09:16:10 -07001211 {
1212 .ident = "R528/R728",
1213 .matches = {
1214 DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1215 DMI_MATCH(DMI_PRODUCT_NAME, "R528/R728"),
1216 DMI_MATCH(DMI_BOARD_NAME, "R528/R728"),
1217 },
1218 .callback = dmi_check_cb,
1219 },
Jason Stubbs7b3c2572011-09-20 09:16:14 -07001220 {
1221 .ident = "NC210/NC110",
1222 .matches = {
1223 DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1224 DMI_MATCH(DMI_PRODUCT_NAME, "NC210/NC110"),
1225 DMI_MATCH(DMI_BOARD_NAME, "NC210/NC110"),
1226 },
1227 .callback = dmi_check_cb,
1228 },
Tommaso Massimi7500eeb2011-09-20 09:16:09 -07001229 {
1230 .ident = "X520",
1231 .matches = {
1232 DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1233 DMI_MATCH(DMI_PRODUCT_NAME, "X520"),
1234 DMI_MATCH(DMI_BOARD_NAME, "X520"),
1235 },
1236 .callback = dmi_check_cb,
1237 },
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001238 { },
1239};
1240MODULE_DEVICE_TABLE(dmi, samsung_dmi_table);
1241
Corentin Chary5dea7a22011-11-26 10:59:59 +01001242static struct platform_device *samsung_platform_device;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001243
1244static int __init samsung_init(void)
1245{
Corentin Chary5dea7a22011-11-26 10:59:59 +01001246 struct samsung_laptop *samsung;
1247 int ret;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001248
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001249 if (!force && !dmi_check_system(samsung_dmi_table))
1250 return -ENODEV;
1251
Corentin Charya6df4892011-11-26 10:59:58 +01001252 samsung = kzalloc(sizeof(*samsung), GFP_KERNEL);
1253 if (!samsung)
1254 return -ENOMEM;
1255
1256 mutex_init(&samsung->sabi_mutex);
Corentin Charyf34cd9c2011-11-26 11:00:00 +01001257 samsung->handle_backlight = true;
1258
1259#ifdef CONFIG_ACPI
1260 /* Don't handle backlight here if the acpi video already handle it */
1261 if (acpi_video_backlight_support()) {
1262 pr_info("Backlight controlled by ACPI video driver\n");
1263 samsung->handle_backlight = false;
1264 }
1265#endif
Corentin Charya6df4892011-11-26 10:59:58 +01001266
Corentin Chary5dea7a22011-11-26 10:59:59 +01001267 ret = samsung_platform_init(samsung);
1268 if (ret)
1269 goto error_platform;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001270
Corentin Chary5dea7a22011-11-26 10:59:59 +01001271 ret = samsung_sabi_init(samsung);
1272 if (ret)
1273 goto error_sabi;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001274
Corentin Chary5dea7a22011-11-26 10:59:59 +01001275 ret = samsung_sysfs_init(samsung);
1276 if (ret)
1277 goto error_sysfs;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001278
Corentin Chary5dea7a22011-11-26 10:59:59 +01001279 ret = samsung_backlight_init(samsung);
1280 if (ret)
1281 goto error_backlight;
Corentin Charya6df4892011-11-26 10:59:58 +01001282
Corentin Chary5dea7a22011-11-26 10:59:59 +01001283 ret = samsung_rfkill_init(samsung);
1284 if (ret)
1285 goto error_rfkill;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001286
Corentin Chary5b80fc42011-11-26 11:00:03 +01001287 ret = samsung_debugfs_init(samsung);
1288 if (ret)
1289 goto error_debugfs;
1290
Corentin Chary5dea7a22011-11-26 10:59:59 +01001291 samsung_platform_device = samsung->platform_device;
1292 return ret;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001293
Corentin Chary5b80fc42011-11-26 11:00:03 +01001294error_debugfs:
1295 samsung_rfkill_exit(samsung);
Corentin Chary5dea7a22011-11-26 10:59:59 +01001296error_rfkill:
1297 samsung_backlight_exit(samsung);
1298error_backlight:
1299 samsung_sysfs_exit(samsung);
1300error_sysfs:
1301 samsung_sabi_exit(samsung);
1302error_sabi:
1303 samsung_platform_exit(samsung);
1304error_platform:
Corentin Charya6df4892011-11-26 10:59:58 +01001305 kfree(samsung);
Corentin Chary5dea7a22011-11-26 10:59:59 +01001306 return ret;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001307}
1308
1309static void __exit samsung_exit(void)
1310{
Corentin Chary5dea7a22011-11-26 10:59:59 +01001311 struct samsung_laptop *samsung;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001312
Corentin Chary5dea7a22011-11-26 10:59:59 +01001313 samsung = platform_get_drvdata(samsung_platform_device);
Corentin Charya6df4892011-11-26 10:59:58 +01001314
Corentin Chary5b80fc42011-11-26 11:00:03 +01001315 samsung_debugfs_exit(samsung);
Corentin Chary5dea7a22011-11-26 10:59:59 +01001316 samsung_rfkill_exit(samsung);
1317 samsung_backlight_exit(samsung);
1318 samsung_sysfs_exit(samsung);
1319 samsung_sabi_exit(samsung);
1320 samsung_platform_exit(samsung);
1321
Corentin Charya6df4892011-11-26 10:59:58 +01001322 kfree(samsung);
Corentin Chary5dea7a22011-11-26 10:59:59 +01001323 samsung_platform_device = NULL;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001324}
1325
1326module_init(samsung_init);
1327module_exit(samsung_exit);
1328
1329MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@suse.de>");
1330MODULE_DESCRIPTION("Samsung Backlight driver");
1331MODULE_LICENSE("GPL");