blob: fd0ebedc86e217f6416171c61b1c4542fc3b63e7 [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>
Corentin Charyf674ebf2011-11-26 11:00:08 +010020#include <linux/leds.h>
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -050021#include <linux/fb.h>
22#include <linux/dmi.h>
23#include <linux/platform_device.h>
24#include <linux/rfkill.h>
Corentin Charyf34cd9c2011-11-26 11:00:00 +010025#include <linux/acpi.h>
Corentin Chary5b80fc42011-11-26 11:00:03 +010026#include <linux/seq_file.h>
27#include <linux/debugfs.h>
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -050028
29/*
30 * This driver is needed because a number of Samsung laptops do not hook
31 * their control settings through ACPI. So we have to poke around in the
32 * BIOS to do things like brightness values, and "special" key controls.
33 */
34
35/*
36 * We have 0 - 8 as valid brightness levels. The specs say that level 0 should
37 * be reserved by the BIOS (which really doesn't make much sense), we tell
38 * userspace that the value is 0 - 7 and then just tell the hardware 1 - 8
39 */
40#define MAX_BRIGHT 0x07
41
42
43#define SABI_IFACE_MAIN 0x00
44#define SABI_IFACE_SUB 0x02
45#define SABI_IFACE_COMPLETE 0x04
46#define SABI_IFACE_DATA 0x05
47
Corentin Chary7e960712011-11-26 11:00:02 +010048/* Structure get/set data using sabi */
49struct sabi_data {
50 union {
51 struct {
52 u32 d0;
53 u32 d1;
54 u16 d2;
55 u8 d3;
56 };
57 u8 data[11];
58 };
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -050059};
60
61struct sabi_header_offsets {
62 u8 port;
63 u8 re_mem;
64 u8 iface_func;
65 u8 en_mem;
66 u8 data_offset;
67 u8 data_segment;
68};
69
70struct sabi_commands {
71 /*
72 * Brightness is 0 - 8, as described above.
73 * Value 0 is for the BIOS to use
74 */
Corentin Chary7e960712011-11-26 11:00:02 +010075 u16 get_brightness;
76 u16 set_brightness;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -050077
78 /*
79 * first byte:
80 * 0x00 - wireless is off
81 * 0x01 - wireless is on
82 * second byte:
83 * 0x02 - 3G is off
84 * 0x03 - 3G is on
85 * TODO, verify 3G is correct, that doesn't seem right...
86 */
Corentin Chary7e960712011-11-26 11:00:02 +010087 u16 get_wireless_button;
88 u16 set_wireless_button;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -050089
90 /* 0 is off, 1 is on */
Corentin Chary7e960712011-11-26 11:00:02 +010091 u16 get_backlight;
92 u16 set_backlight;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -050093
94 /*
95 * 0x80 or 0x00 - no action
96 * 0x81 - recovery key pressed
97 */
Corentin Chary7e960712011-11-26 11:00:02 +010098 u16 get_recovery_mode;
99 u16 set_recovery_mode;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500100
101 /*
102 * on seclinux: 0 is low, 1 is high,
103 * on swsmi: 0 is normal, 1 is silent, 2 is turbo
104 */
Corentin Chary7e960712011-11-26 11:00:02 +0100105 u16 get_performance_level;
106 u16 set_performance_level;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500107
Corentin Charycb5b5c92011-11-26 11:00:05 +0100108 /* 0x80 is off, 0x81 is on */
109 u16 get_battery_life_extender;
110 u16 set_battery_life_extender;
111
Corentin Chary3a75d372011-11-26 11:00:06 +0100112 /* 0x80 is off, 0x81 is on */
113 u16 get_usb_charge;
114 u16 set_usb_charge;
115
Corentin Charyf674ebf2011-11-26 11:00:08 +0100116 /* 0x81 to read, (0x82 | level << 8) to set, 0xaabb to enable */
117 u16 kbd_backlight;
118
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500119 /*
120 * Tell the BIOS that Linux is running on this machine.
121 * 81 is on, 80 is off
122 */
Corentin Chary7e960712011-11-26 11:00:02 +0100123 u16 set_linux;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500124};
125
126struct sabi_performance_level {
127 const char *name;
Corentin Chary7e960712011-11-26 11:00:02 +0100128 u16 value;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500129};
130
131struct sabi_config {
132 const char *test_string;
133 u16 main_function;
134 const struct sabi_header_offsets header_offsets;
135 const struct sabi_commands commands;
136 const struct sabi_performance_level performance_levels[4];
137 u8 min_brightness;
138 u8 max_brightness;
139};
140
141static const struct sabi_config sabi_configs[] = {
142 {
143 .test_string = "SECLINUX",
144
145 .main_function = 0x4c49,
146
147 .header_offsets = {
148 .port = 0x00,
149 .re_mem = 0x02,
150 .iface_func = 0x03,
151 .en_mem = 0x04,
152 .data_offset = 0x05,
153 .data_segment = 0x07,
154 },
155
156 .commands = {
157 .get_brightness = 0x00,
158 .set_brightness = 0x01,
159
160 .get_wireless_button = 0x02,
161 .set_wireless_button = 0x03,
162
163 .get_backlight = 0x04,
164 .set_backlight = 0x05,
165
166 .get_recovery_mode = 0x06,
167 .set_recovery_mode = 0x07,
168
169 .get_performance_level = 0x08,
170 .set_performance_level = 0x09,
171
Corentin Charycb5b5c92011-11-26 11:00:05 +0100172 .get_battery_life_extender = 0xFFFF,
173 .set_battery_life_extender = 0xFFFF,
174
Corentin Chary3a75d372011-11-26 11:00:06 +0100175 .get_usb_charge = 0xFFFF,
176 .set_usb_charge = 0xFFFF,
177
Corentin Charyf674ebf2011-11-26 11:00:08 +0100178 .kbd_backlight = 0xFFFF,
179
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500180 .set_linux = 0x0a,
181 },
182
183 .performance_levels = {
184 {
185 .name = "silent",
186 .value = 0,
187 },
188 {
189 .name = "normal",
190 .value = 1,
191 },
192 { },
193 },
194 .min_brightness = 1,
195 .max_brightness = 8,
196 },
197 {
198 .test_string = "SwSmi@",
199
200 .main_function = 0x5843,
201
202 .header_offsets = {
203 .port = 0x00,
204 .re_mem = 0x04,
205 .iface_func = 0x02,
206 .en_mem = 0x03,
207 .data_offset = 0x05,
208 .data_segment = 0x07,
209 },
210
211 .commands = {
212 .get_brightness = 0x10,
213 .set_brightness = 0x11,
214
215 .get_wireless_button = 0x12,
216 .set_wireless_button = 0x13,
217
218 .get_backlight = 0x2d,
219 .set_backlight = 0x2e,
220
221 .get_recovery_mode = 0xff,
222 .set_recovery_mode = 0xff,
223
224 .get_performance_level = 0x31,
225 .set_performance_level = 0x32,
226
Corentin Charycb5b5c92011-11-26 11:00:05 +0100227 .get_battery_life_extender = 0x65,
228 .set_battery_life_extender = 0x66,
229
Corentin Chary3a75d372011-11-26 11:00:06 +0100230 .get_usb_charge = 0x67,
231 .set_usb_charge = 0x68,
232
Corentin Charyf674ebf2011-11-26 11:00:08 +0100233 .kbd_backlight = 0x78,
234
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500235 .set_linux = 0xff,
236 },
237
238 .performance_levels = {
239 {
240 .name = "normal",
241 .value = 0,
242 },
243 {
244 .name = "silent",
245 .value = 1,
246 },
247 {
248 .name = "overclock",
249 .value = 2,
250 },
251 { },
252 },
253 .min_brightness = 0,
254 .max_brightness = 8,
255 },
256 { },
257};
258
Corentin Chary5b80fc42011-11-26 11:00:03 +0100259/*
260 * samsung-laptop/ - debugfs root directory
261 * f0000_segment - dump f0000 segment
262 * command - current command
263 * data - current data
264 * d0, d1, d2, d3 - data fields
265 * call - call SABI using command and data
266 *
267 * This allow to call arbitrary sabi commands wihout
268 * modifying the driver at all.
269 * For example, setting the keyboard backlight brightness to 5
270 *
271 * echo 0x78 > command
272 * echo 0x0582 > d0
273 * echo 0 > d1
274 * echo 0 > d2
275 * echo 0 > d3
276 * cat call
277 */
278
279struct samsung_laptop_debug {
280 struct dentry *root;
281 struct sabi_data data;
282 u16 command;
283
284 struct debugfs_blob_wrapper f0000_wrapper;
285 struct debugfs_blob_wrapper data_wrapper;
286};
287
Corentin Charya6df4892011-11-26 10:59:58 +0100288struct samsung_laptop {
289 const struct sabi_config *config;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500290
Corentin Charya6df4892011-11-26 10:59:58 +0100291 void __iomem *sabi;
292 void __iomem *sabi_iface;
293 void __iomem *f0000_segment;
294
295 struct mutex sabi_mutex;
296
Corentin Chary5dea7a22011-11-26 10:59:59 +0100297 struct platform_device *platform_device;
Corentin Charya6df4892011-11-26 10:59:58 +0100298 struct backlight_device *backlight_device;
299 struct rfkill *rfk;
300
Corentin Charyf674ebf2011-11-26 11:00:08 +0100301 struct led_classdev kbd_led;
302 int kbd_led_wk;
303 struct workqueue_struct *led_workqueue;
304 struct work_struct kbd_led_work;
305
Corentin Chary5b80fc42011-11-26 11:00:03 +0100306 struct samsung_laptop_debug debug;
307
Corentin Charyf34cd9c2011-11-26 11:00:00 +0100308 bool handle_backlight;
Corentin Charya6df4892011-11-26 10:59:58 +0100309 bool has_stepping_quirk;
310};
311
Corentin Chary5dea7a22011-11-26 10:59:59 +0100312
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500313
Rusty Russell90ab5ee2012-01-13 09:32:20 +1030314static bool force;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500315module_param(force, bool, 0);
316MODULE_PARM_DESC(force,
317 "Disable the DMI check and forces the driver to be loaded");
318
Rusty Russell90ab5ee2012-01-13 09:32:20 +1030319static bool debug;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500320module_param(debug, bool, S_IRUGO | S_IWUSR);
321MODULE_PARM_DESC(debug, "Debug enabled or not");
322
Corentin Chary7e960712011-11-26 11:00:02 +0100323static int sabi_command(struct samsung_laptop *samsung, u16 command,
324 struct sabi_data *in,
325 struct sabi_data *out)
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500326{
Corentin Charya6df4892011-11-26 10:59:58 +0100327 const struct sabi_config *config = samsung->config;
Corentin Chary7e960712011-11-26 11:00:02 +0100328 int ret = 0;
Corentin Charya6df4892011-11-26 10:59:58 +0100329 u16 port = readw(samsung->sabi + config->header_offsets.port);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500330 u8 complete, iface_data;
331
Corentin Charya6df4892011-11-26 10:59:58 +0100332 mutex_lock(&samsung->sabi_mutex);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500333
Corentin Chary7e960712011-11-26 11:00:02 +0100334 if (debug) {
335 if (in)
336 pr_info("SABI 0x%04x {0x%08x, 0x%08x, 0x%04x, 0x%02x}",
337 command, in->d0, in->d1, in->d2, in->d3);
338 else
339 pr_info("SABI 0x%04x", command);
340 }
341
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500342 /* enable memory to be able to write to it */
Corentin Charya6df4892011-11-26 10:59:58 +0100343 outb(readb(samsung->sabi + config->header_offsets.en_mem), port);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500344
345 /* write out the command */
Corentin Charya6df4892011-11-26 10:59:58 +0100346 writew(config->main_function, samsung->sabi_iface + SABI_IFACE_MAIN);
347 writew(command, samsung->sabi_iface + SABI_IFACE_SUB);
348 writeb(0, samsung->sabi_iface + SABI_IFACE_COMPLETE);
Corentin Chary7e960712011-11-26 11:00:02 +0100349 if (in) {
350 writel(in->d0, samsung->sabi_iface + SABI_IFACE_DATA);
351 writel(in->d1, samsung->sabi_iface + SABI_IFACE_DATA + 4);
352 writew(in->d2, samsung->sabi_iface + SABI_IFACE_DATA + 8);
353 writeb(in->d3, samsung->sabi_iface + SABI_IFACE_DATA + 10);
354 }
Corentin Charya6df4892011-11-26 10:59:58 +0100355 outb(readb(samsung->sabi + config->header_offsets.iface_func), port);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500356
357 /* write protect memory to make it safe */
Corentin Charya6df4892011-11-26 10:59:58 +0100358 outb(readb(samsung->sabi + config->header_offsets.re_mem), port);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500359
360 /* see if the command actually succeeded */
Corentin Charya6df4892011-11-26 10:59:58 +0100361 complete = readb(samsung->sabi_iface + SABI_IFACE_COMPLETE);
362 iface_data = readb(samsung->sabi_iface + SABI_IFACE_DATA);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500363 if (complete != 0xaa || iface_data == 0xff) {
Corentin Chary7e960712011-11-26 11:00:02 +0100364 pr_warn("SABI command 0x%04x failed with"
365 " completion flag 0x%02x and interface data 0x%02x",
366 command, complete, iface_data);
367 ret = -EINVAL;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500368 goto exit;
369 }
Corentin Chary7e960712011-11-26 11:00:02 +0100370
371 if (out) {
372 out->d0 = readl(samsung->sabi_iface + SABI_IFACE_DATA);
373 out->d1 = readl(samsung->sabi_iface + SABI_IFACE_DATA + 4);
374 out->d2 = readw(samsung->sabi_iface + SABI_IFACE_DATA + 2);
375 out->d3 = readb(samsung->sabi_iface + SABI_IFACE_DATA + 1);
376 }
377
378 if (debug && out) {
379 pr_info("SABI {0x%08x, 0x%08x, 0x%04x, 0x%02x}",
380 out->d0, out->d1, out->d2, out->d3);
381 }
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500382
383exit:
Corentin Charya6df4892011-11-26 10:59:58 +0100384 mutex_unlock(&samsung->sabi_mutex);
Corentin Chary7e960712011-11-26 11:00:02 +0100385 return ret;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500386}
387
Corentin Chary7e960712011-11-26 11:00:02 +0100388/* simple wrappers usable with most commands */
389static int sabi_set_commandb(struct samsung_laptop *samsung,
390 u16 command, u8 data)
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500391{
Corentin Chary7e960712011-11-26 11:00:02 +0100392 struct sabi_data in = { .d0 = 0, .d1 = 0, .d2 = 0, .d3 = 0 };
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500393
Corentin Chary7e960712011-11-26 11:00:02 +0100394 in.data[0] = data;
395 return sabi_command(samsung, command, &in, NULL);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500396}
397
Corentin Chary5dea7a22011-11-26 10:59:59 +0100398static int read_brightness(struct samsung_laptop *samsung)
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500399{
Corentin Charya6df4892011-11-26 10:59:58 +0100400 const struct sabi_config *config = samsung->config;
401 const struct sabi_commands *commands = &samsung->config->commands;
Corentin Chary7e960712011-11-26 11:00:02 +0100402 struct sabi_data sretval;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500403 int user_brightness = 0;
404 int retval;
405
Corentin Chary7e960712011-11-26 11:00:02 +0100406 retval = sabi_command(samsung, commands->get_brightness,
407 NULL, &sretval);
408 if (retval)
409 return retval;
410
411 user_brightness = sretval.data[0];
412 if (user_brightness > config->min_brightness)
413 user_brightness -= config->min_brightness;
414 else
415 user_brightness = 0;
416
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500417 return user_brightness;
418}
419
Corentin Chary5dea7a22011-11-26 10:59:59 +0100420static void set_brightness(struct samsung_laptop *samsung, u8 user_brightness)
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500421{
Corentin Charya6df4892011-11-26 10:59:58 +0100422 const struct sabi_config *config = samsung->config;
423 const struct sabi_commands *commands = &samsung->config->commands;
424 u8 user_level = user_brightness + config->min_brightness;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500425
Corentin Charya6df4892011-11-26 10:59:58 +0100426 if (samsung->has_stepping_quirk && user_level != 0) {
Jason Stubbsac080522011-09-20 09:16:13 -0700427 /*
428 * short circuit if the specified level is what's already set
429 * to prevent the screen from flickering needlessly
430 */
Corentin Chary5dea7a22011-11-26 10:59:59 +0100431 if (user_brightness == read_brightness(samsung))
Jason Stubbsac080522011-09-20 09:16:13 -0700432 return;
433
Corentin Chary7e960712011-11-26 11:00:02 +0100434 sabi_set_commandb(samsung, commands->set_brightness, 0);
Jason Stubbsac080522011-09-20 09:16:13 -0700435 }
436
Corentin Chary7e960712011-11-26 11:00:02 +0100437 sabi_set_commandb(samsung, commands->set_brightness, user_level);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500438}
439
440static int get_brightness(struct backlight_device *bd)
441{
Corentin Chary5dea7a22011-11-26 10:59:59 +0100442 struct samsung_laptop *samsung = bl_get_data(bd);
443
444 return read_brightness(samsung);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500445}
446
Corentin Chary5dea7a22011-11-26 10:59:59 +0100447static void check_for_stepping_quirk(struct samsung_laptop *samsung)
Jason Stubbsac080522011-09-20 09:16:13 -0700448{
Corentin Chary5dea7a22011-11-26 10:59:59 +0100449 int initial_level;
450 int check_level;
451 int orig_level = read_brightness(samsung);
Jason Stubbsac080522011-09-20 09:16:13 -0700452
453 /*
454 * Some laptops exhibit the strange behaviour of stepping toward
455 * (rather than setting) the brightness except when changing to/from
456 * brightness level 0. This behaviour is checked for here and worked
457 * around in set_brightness.
458 */
459
John Serockba05b232011-10-13 06:42:01 -0400460 if (orig_level == 0)
Corentin Chary5dea7a22011-11-26 10:59:59 +0100461 set_brightness(samsung, 1);
John Serockba05b232011-10-13 06:42:01 -0400462
Corentin Chary5dea7a22011-11-26 10:59:59 +0100463 initial_level = read_brightness(samsung);
John Serockba05b232011-10-13 06:42:01 -0400464
Jason Stubbsac080522011-09-20 09:16:13 -0700465 if (initial_level <= 2)
466 check_level = initial_level + 2;
467 else
468 check_level = initial_level - 2;
469
Corentin Charya6df4892011-11-26 10:59:58 +0100470 samsung->has_stepping_quirk = false;
Corentin Chary5dea7a22011-11-26 10:59:59 +0100471 set_brightness(samsung, check_level);
Jason Stubbsac080522011-09-20 09:16:13 -0700472
Corentin Chary5dea7a22011-11-26 10:59:59 +0100473 if (read_brightness(samsung) != check_level) {
Corentin Charya6df4892011-11-26 10:59:58 +0100474 samsung->has_stepping_quirk = true;
Jason Stubbsac080522011-09-20 09:16:13 -0700475 pr_info("enabled workaround for brightness stepping quirk\n");
476 }
477
Corentin Chary5dea7a22011-11-26 10:59:59 +0100478 set_brightness(samsung, orig_level);
Jason Stubbsac080522011-09-20 09:16:13 -0700479}
480
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500481static int update_status(struct backlight_device *bd)
482{
Corentin Chary5dea7a22011-11-26 10:59:59 +0100483 struct samsung_laptop *samsung = bl_get_data(bd);
Corentin Charya6df4892011-11-26 10:59:58 +0100484 const struct sabi_commands *commands = &samsung->config->commands;
485
Corentin Chary5dea7a22011-11-26 10:59:59 +0100486 set_brightness(samsung, bd->props.brightness);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500487
488 if (bd->props.power == FB_BLANK_UNBLANK)
Corentin Chary7e960712011-11-26 11:00:02 +0100489 sabi_set_commandb(samsung, commands->set_backlight, 1);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500490 else
Corentin Chary7e960712011-11-26 11:00:02 +0100491 sabi_set_commandb(samsung, commands->set_backlight, 0);
Corentin Chary5dea7a22011-11-26 10:59:59 +0100492
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500493 return 0;
494}
495
496static const struct backlight_ops backlight_ops = {
497 .get_brightness = get_brightness,
498 .update_status = update_status,
499};
500
501static int rfkill_set(void *data, bool blocked)
502{
Corentin Chary5dea7a22011-11-26 10:59:59 +0100503 struct samsung_laptop *samsung = data;
Corentin Charya6df4892011-11-26 10:59:58 +0100504 const struct sabi_commands *commands = &samsung->config->commands;
505
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500506 /* Do something with blocked...*/
507 /*
508 * blocked == false is on
509 * blocked == true is off
510 */
511 if (blocked)
Corentin Chary7e960712011-11-26 11:00:02 +0100512 sabi_set_commandb(samsung, commands->set_wireless_button, 0);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500513 else
Corentin Chary7e960712011-11-26 11:00:02 +0100514 sabi_set_commandb(samsung, commands->set_wireless_button, 1);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500515
516 return 0;
517}
518
519static struct rfkill_ops rfkill_ops = {
520 .set_block = rfkill_set,
521};
522
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500523static ssize_t get_performance_level(struct device *dev,
524 struct device_attribute *attr, char *buf)
525{
Corentin Chary5dea7a22011-11-26 10:59:59 +0100526 struct samsung_laptop *samsung = dev_get_drvdata(dev);
Corentin Charya6df4892011-11-26 10:59:58 +0100527 const struct sabi_config *config = samsung->config;
Corentin Chary5dea7a22011-11-26 10:59:59 +0100528 const struct sabi_commands *commands = &config->commands;
Corentin Chary7e960712011-11-26 11:00:02 +0100529 struct sabi_data sretval;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500530 int retval;
531 int i;
532
533 /* Read the state */
Corentin Chary7e960712011-11-26 11:00:02 +0100534 retval = sabi_command(samsung, commands->get_performance_level,
535 NULL, &sretval);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500536 if (retval)
537 return retval;
538
539 /* The logic is backwards, yeah, lots of fun... */
Corentin Charya6df4892011-11-26 10:59:58 +0100540 for (i = 0; config->performance_levels[i].name; ++i) {
Corentin Chary7e960712011-11-26 11:00:02 +0100541 if (sretval.data[0] == config->performance_levels[i].value)
Corentin Charya6df4892011-11-26 10:59:58 +0100542 return sprintf(buf, "%s\n", config->performance_levels[i].name);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500543 }
544 return sprintf(buf, "%s\n", "unknown");
545}
546
547static ssize_t set_performance_level(struct device *dev,
548 struct device_attribute *attr, const char *buf,
549 size_t count)
550{
Corentin Chary5dea7a22011-11-26 10:59:59 +0100551 struct samsung_laptop *samsung = dev_get_drvdata(dev);
Corentin Charya6df4892011-11-26 10:59:58 +0100552 const struct sabi_config *config = samsung->config;
Corentin Chary5dea7a22011-11-26 10:59:59 +0100553 const struct sabi_commands *commands = &config->commands;
554 int i;
Corentin Charya6df4892011-11-26 10:59:58 +0100555
Corentin Chary5dea7a22011-11-26 10:59:59 +0100556 if (count < 1)
557 return count;
558
559 for (i = 0; config->performance_levels[i].name; ++i) {
560 const struct sabi_performance_level *level =
561 &config->performance_levels[i];
562 if (!strncasecmp(level->name, buf, strlen(level->name))) {
Corentin Chary7e960712011-11-26 11:00:02 +0100563 sabi_set_commandb(samsung,
564 commands->set_performance_level,
565 level->value);
Corentin Chary5dea7a22011-11-26 10:59:59 +0100566 break;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500567 }
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500568 }
Corentin Chary5dea7a22011-11-26 10:59:59 +0100569
570 if (!config->performance_levels[i].name)
571 return -EINVAL;
572
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500573 return count;
574}
Corentin Chary5dea7a22011-11-26 10:59:59 +0100575
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500576static DEVICE_ATTR(performance_level, S_IWUSR | S_IRUGO,
577 get_performance_level, set_performance_level);
578
Corentin Charycb5b5c92011-11-26 11:00:05 +0100579static int read_battery_life_extender(struct samsung_laptop *samsung)
580{
581 const struct sabi_commands *commands = &samsung->config->commands;
582 struct sabi_data data;
583 int retval;
584
585 if (commands->get_battery_life_extender == 0xFFFF)
586 return -ENODEV;
587
588 memset(&data, 0, sizeof(data));
589 data.data[0] = 0x80;
590 retval = sabi_command(samsung, commands->get_battery_life_extender,
591 &data, &data);
592
593 if (retval)
594 return retval;
595
596 if (data.data[0] != 0 && data.data[0] != 1)
597 return -ENODEV;
598
599 return data.data[0];
600}
601
602static int write_battery_life_extender(struct samsung_laptop *samsung,
603 int enabled)
604{
605 const struct sabi_commands *commands = &samsung->config->commands;
606 struct sabi_data data;
607
608 memset(&data, 0, sizeof(data));
609 data.data[0] = 0x80 | enabled;
610 return sabi_command(samsung, commands->set_battery_life_extender,
611 &data, NULL);
612}
613
614static ssize_t get_battery_life_extender(struct device *dev,
615 struct device_attribute *attr,
616 char *buf)
617{
618 struct samsung_laptop *samsung = dev_get_drvdata(dev);
619 int ret;
620
621 ret = read_battery_life_extender(samsung);
622 if (ret < 0)
623 return ret;
624
625 return sprintf(buf, "%d\n", ret);
626}
627
628static ssize_t set_battery_life_extender(struct device *dev,
629 struct device_attribute *attr,
630 const char *buf, size_t count)
631{
632 struct samsung_laptop *samsung = dev_get_drvdata(dev);
633 int ret, value;
634
635 if (!count || sscanf(buf, "%i", &value) != 1)
636 return -EINVAL;
637
638 ret = write_battery_life_extender(samsung, !!value);
639 if (ret < 0)
640 return ret;
641
642 return count;
643}
644
645static DEVICE_ATTR(battery_life_extender, S_IWUSR | S_IRUGO,
646 get_battery_life_extender, set_battery_life_extender);
647
Corentin Chary3a75d372011-11-26 11:00:06 +0100648static int read_usb_charge(struct samsung_laptop *samsung)
649{
650 const struct sabi_commands *commands = &samsung->config->commands;
651 struct sabi_data data;
652 int retval;
653
654 if (commands->get_usb_charge == 0xFFFF)
655 return -ENODEV;
656
657 memset(&data, 0, sizeof(data));
658 data.data[0] = 0x80;
659 retval = sabi_command(samsung, commands->get_usb_charge,
660 &data, &data);
661
662 if (retval)
663 return retval;
664
665 if (data.data[0] != 0 && data.data[0] != 1)
666 return -ENODEV;
667
668 return data.data[0];
669}
670
671static int write_usb_charge(struct samsung_laptop *samsung,
672 int enabled)
673{
674 const struct sabi_commands *commands = &samsung->config->commands;
675 struct sabi_data data;
676
677 memset(&data, 0, sizeof(data));
678 data.data[0] = 0x80 | enabled;
679 return sabi_command(samsung, commands->set_usb_charge,
680 &data, NULL);
681}
682
683static ssize_t get_usb_charge(struct device *dev,
684 struct device_attribute *attr,
685 char *buf)
686{
687 struct samsung_laptop *samsung = dev_get_drvdata(dev);
688 int ret;
689
690 ret = read_usb_charge(samsung);
691 if (ret < 0)
692 return ret;
693
694 return sprintf(buf, "%d\n", ret);
695}
696
697static ssize_t set_usb_charge(struct device *dev,
698 struct device_attribute *attr,
699 const char *buf, size_t count)
700{
701 struct samsung_laptop *samsung = dev_get_drvdata(dev);
702 int ret, value;
703
704 if (!count || sscanf(buf, "%i", &value) != 1)
705 return -EINVAL;
706
707 ret = write_usb_charge(samsung, !!value);
708 if (ret < 0)
709 return ret;
710
711 return count;
712}
713
714static DEVICE_ATTR(usb_charge, S_IWUSR | S_IRUGO,
715 get_usb_charge, set_usb_charge);
716
Corentin Charya66c1662011-11-26 11:00:01 +0100717static struct attribute *platform_attributes[] = {
718 &dev_attr_performance_level.attr,
Corentin Charycb5b5c92011-11-26 11:00:05 +0100719 &dev_attr_battery_life_extender.attr,
Corentin Chary3a75d372011-11-26 11:00:06 +0100720 &dev_attr_usb_charge.attr,
Corentin Charya66c1662011-11-26 11:00:01 +0100721 NULL
722};
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500723
Corentin Chary5dea7a22011-11-26 10:59:59 +0100724static int find_signature(void __iomem *memcheck, const char *testStr)
725{
726 int i = 0;
727 int loca;
728
729 for (loca = 0; loca < 0xffff; loca++) {
730 char temp = readb(memcheck + loca);
731
732 if (temp == testStr[i]) {
733 if (i == strlen(testStr)-1)
734 break;
735 ++i;
736 } else {
737 i = 0;
738 }
739 }
740 return loca;
741}
742
743static void samsung_rfkill_exit(struct samsung_laptop *samsung)
744{
745 if (samsung->rfk) {
746 rfkill_unregister(samsung->rfk);
747 rfkill_destroy(samsung->rfk);
748 samsung->rfk = NULL;
749 }
750}
751
752static int __init samsung_rfkill_init(struct samsung_laptop *samsung)
753{
754 int retval;
755
756 samsung->rfk = rfkill_alloc("samsung-wifi",
757 &samsung->platform_device->dev,
758 RFKILL_TYPE_WLAN,
759 &rfkill_ops, samsung);
760 if (!samsung->rfk)
761 return -ENOMEM;
762
763 retval = rfkill_register(samsung->rfk);
764 if (retval) {
765 rfkill_destroy(samsung->rfk);
766 samsung->rfk = NULL;
767 return -ENODEV;
768 }
769
770 return 0;
771}
772
Corentin Charyf674ebf2011-11-26 11:00:08 +0100773static int kbd_backlight_enable(struct samsung_laptop *samsung)
774{
775 const struct sabi_commands *commands = &samsung->config->commands;
776 struct sabi_data data;
777 int retval;
778
779 if (commands->kbd_backlight == 0xFFFF)
780 return -ENODEV;
781
782 memset(&data, 0, sizeof(data));
783 data.d0 = 0xaabb;
784 retval = sabi_command(samsung, commands->kbd_backlight,
785 &data, &data);
786
787 if (retval)
788 return retval;
789
790 if (data.d0 != 0xccdd)
791 return -ENODEV;
792 return 0;
793}
794
795static int kbd_backlight_read(struct samsung_laptop *samsung)
796{
797 const struct sabi_commands *commands = &samsung->config->commands;
798 struct sabi_data data;
799 int retval;
800
801 memset(&data, 0, sizeof(data));
802 data.data[0] = 0x81;
803 retval = sabi_command(samsung, commands->kbd_backlight,
804 &data, &data);
805
806 if (retval)
807 return retval;
808
809 return data.data[0];
810}
811
812static int kbd_backlight_write(struct samsung_laptop *samsung, int brightness)
813{
814 const struct sabi_commands *commands = &samsung->config->commands;
815 struct sabi_data data;
816
817 memset(&data, 0, sizeof(data));
818 data.d0 = 0x82 | ((brightness & 0xFF) << 8);
819 return sabi_command(samsung, commands->kbd_backlight,
820 &data, NULL);
821}
822
823static void kbd_led_update(struct work_struct *work)
824{
825 struct samsung_laptop *samsung;
826
827 samsung = container_of(work, struct samsung_laptop, kbd_led_work);
828 kbd_backlight_write(samsung, samsung->kbd_led_wk);
829}
830
831static void kbd_led_set(struct led_classdev *led_cdev,
832 enum led_brightness value)
833{
834 struct samsung_laptop *samsung;
835
836 samsung = container_of(led_cdev, struct samsung_laptop, kbd_led);
837
838 if (value > samsung->kbd_led.max_brightness)
839 value = samsung->kbd_led.max_brightness;
840 else if (value < 0)
841 value = 0;
842
843 samsung->kbd_led_wk = value;
844 queue_work(samsung->led_workqueue, &samsung->kbd_led_work);
845}
846
847static enum led_brightness kbd_led_get(struct led_classdev *led_cdev)
848{
849 struct samsung_laptop *samsung;
850
851 samsung = container_of(led_cdev, struct samsung_laptop, kbd_led);
852 return kbd_backlight_read(samsung);
853}
854
855static void samsung_leds_exit(struct samsung_laptop *samsung)
856{
857 if (!IS_ERR_OR_NULL(samsung->kbd_led.dev))
858 led_classdev_unregister(&samsung->kbd_led);
859 if (samsung->led_workqueue)
860 destroy_workqueue(samsung->led_workqueue);
861}
862
863static int __init samsung_leds_init(struct samsung_laptop *samsung)
864{
865 int ret = 0;
866
867 samsung->led_workqueue = create_singlethread_workqueue("led_workqueue");
868 if (!samsung->led_workqueue)
869 return -ENOMEM;
870
871 if (kbd_backlight_enable(samsung) >= 0) {
872 INIT_WORK(&samsung->kbd_led_work, kbd_led_update);
873
874 samsung->kbd_led.name = "samsung::kbd_backlight";
875 samsung->kbd_led.brightness_set = kbd_led_set;
876 samsung->kbd_led.brightness_get = kbd_led_get;
877 samsung->kbd_led.max_brightness = 8;
878
879 ret = led_classdev_register(&samsung->platform_device->dev,
880 &samsung->kbd_led);
881 }
882
883 if (ret)
884 samsung_leds_exit(samsung);
885
886 return ret;
887}
888
Corentin Chary5dea7a22011-11-26 10:59:59 +0100889static void samsung_backlight_exit(struct samsung_laptop *samsung)
890{
891 if (samsung->backlight_device) {
892 backlight_device_unregister(samsung->backlight_device);
893 samsung->backlight_device = NULL;
894 }
895}
896
897static int __init samsung_backlight_init(struct samsung_laptop *samsung)
898{
899 struct backlight_device *bd;
900 struct backlight_properties props;
901
Corentin Charyf34cd9c2011-11-26 11:00:00 +0100902 if (!samsung->handle_backlight)
903 return 0;
904
Corentin Chary5dea7a22011-11-26 10:59:59 +0100905 memset(&props, 0, sizeof(struct backlight_properties));
906 props.type = BACKLIGHT_PLATFORM;
907 props.max_brightness = samsung->config->max_brightness -
908 samsung->config->min_brightness;
909
910 bd = backlight_device_register("samsung",
911 &samsung->platform_device->dev,
912 samsung, &backlight_ops,
913 &props);
914 if (IS_ERR(bd))
915 return PTR_ERR(bd);
916
917 samsung->backlight_device = bd;
918 samsung->backlight_device->props.brightness = read_brightness(samsung);
919 samsung->backlight_device->props.power = FB_BLANK_UNBLANK;
920 backlight_update_status(samsung->backlight_device);
921
922 return 0;
923}
924
Corentin Charya66c1662011-11-26 11:00:01 +0100925static mode_t samsung_sysfs_is_visible(struct kobject *kobj,
926 struct attribute *attr, int idx)
927{
928 struct device *dev = container_of(kobj, struct device, kobj);
929 struct platform_device *pdev = to_platform_device(dev);
930 struct samsung_laptop *samsung = platform_get_drvdata(pdev);
931 bool ok = true;
932
933 if (attr == &dev_attr_performance_level.attr)
934 ok = !!samsung->config->performance_levels[0].name;
Corentin Charycb5b5c92011-11-26 11:00:05 +0100935 if (attr == &dev_attr_battery_life_extender.attr)
936 ok = !!(read_battery_life_extender(samsung) >= 0);
Corentin Chary3a75d372011-11-26 11:00:06 +0100937 if (attr == &dev_attr_usb_charge.attr)
938 ok = !!(read_usb_charge(samsung) >= 0);
Corentin Charya66c1662011-11-26 11:00:01 +0100939
940 return ok ? attr->mode : 0;
941}
942
943static struct attribute_group platform_attribute_group = {
944 .is_visible = samsung_sysfs_is_visible,
945 .attrs = platform_attributes
946};
947
Corentin Chary5dea7a22011-11-26 10:59:59 +0100948static void samsung_sysfs_exit(struct samsung_laptop *samsung)
949{
Corentin Charya66c1662011-11-26 11:00:01 +0100950 struct platform_device *device = samsung->platform_device;
951
952 sysfs_remove_group(&device->dev.kobj, &platform_attribute_group);
Corentin Chary5dea7a22011-11-26 10:59:59 +0100953}
954
955static int __init samsung_sysfs_init(struct samsung_laptop *samsung)
956{
Corentin Charya66c1662011-11-26 11:00:01 +0100957 struct platform_device *device = samsung->platform_device;
958
959 return sysfs_create_group(&device->dev.kobj, &platform_attribute_group);
960
Corentin Chary5dea7a22011-11-26 10:59:59 +0100961}
962
Corentin Chary5b80fc42011-11-26 11:00:03 +0100963static int show_call(struct seq_file *m, void *data)
964{
965 struct samsung_laptop *samsung = m->private;
966 struct sabi_data *sdata = &samsung->debug.data;
967 int ret;
968
969 seq_printf(m, "SABI 0x%04x {0x%08x, 0x%08x, 0x%04x, 0x%02x}\n",
970 samsung->debug.command,
971 sdata->d0, sdata->d1, sdata->d2, sdata->d3);
972
973 ret = sabi_command(samsung, samsung->debug.command, sdata, sdata);
974
975 if (ret) {
976 seq_printf(m, "SABI command 0x%04x failed\n",
977 samsung->debug.command);
978 return ret;
979 }
980
981 seq_printf(m, "SABI {0x%08x, 0x%08x, 0x%04x, 0x%02x}\n",
982 sdata->d0, sdata->d1, sdata->d2, sdata->d3);
983 return 0;
984}
985
986static int samsung_debugfs_open(struct inode *inode, struct file *file)
987{
988 return single_open(file, show_call, inode->i_private);
989}
990
991static const struct file_operations samsung_laptop_call_io_ops = {
992 .owner = THIS_MODULE,
993 .open = samsung_debugfs_open,
994 .read = seq_read,
995 .llseek = seq_lseek,
996 .release = single_release,
997};
998
999static void samsung_debugfs_exit(struct samsung_laptop *samsung)
1000{
1001 debugfs_remove_recursive(samsung->debug.root);
1002}
1003
1004static int samsung_debugfs_init(struct samsung_laptop *samsung)
1005{
1006 struct dentry *dent;
1007
1008 samsung->debug.root = debugfs_create_dir("samsung-laptop", NULL);
1009 if (!samsung->debug.root) {
1010 pr_err("failed to create debugfs directory");
1011 goto error_debugfs;
1012 }
1013
1014 samsung->debug.f0000_wrapper.data = samsung->f0000_segment;
1015 samsung->debug.f0000_wrapper.size = 0xffff;
1016
1017 samsung->debug.data_wrapper.data = &samsung->debug.data;
1018 samsung->debug.data_wrapper.size = sizeof(samsung->debug.data);
1019
1020 dent = debugfs_create_u16("command", S_IRUGO | S_IWUSR,
1021 samsung->debug.root, &samsung->debug.command);
1022 if (!dent)
1023 goto error_debugfs;
1024
1025 dent = debugfs_create_u32("d0", S_IRUGO | S_IWUSR, samsung->debug.root,
1026 &samsung->debug.data.d0);
1027 if (!dent)
1028 goto error_debugfs;
1029
1030 dent = debugfs_create_u32("d1", S_IRUGO | S_IWUSR, samsung->debug.root,
1031 &samsung->debug.data.d1);
1032 if (!dent)
1033 goto error_debugfs;
1034
1035 dent = debugfs_create_u16("d2", S_IRUGO | S_IWUSR, samsung->debug.root,
1036 &samsung->debug.data.d2);
1037 if (!dent)
1038 goto error_debugfs;
1039
1040 dent = debugfs_create_u8("d3", S_IRUGO | S_IWUSR, samsung->debug.root,
1041 &samsung->debug.data.d3);
1042 if (!dent)
1043 goto error_debugfs;
1044
1045 dent = debugfs_create_blob("data", S_IRUGO | S_IWUSR,
1046 samsung->debug.root,
1047 &samsung->debug.data_wrapper);
1048 if (!dent)
1049 goto error_debugfs;
1050
1051 dent = debugfs_create_blob("f0000_segment", S_IRUSR | S_IWUSR,
1052 samsung->debug.root,
1053 &samsung->debug.f0000_wrapper);
1054 if (!dent)
1055 goto error_debugfs;
1056
1057 dent = debugfs_create_file("call", S_IFREG | S_IRUGO,
1058 samsung->debug.root, samsung,
1059 &samsung_laptop_call_io_ops);
1060 if (!dent)
1061 goto error_debugfs;
1062
1063 return 0;
1064
1065error_debugfs:
1066 samsung_debugfs_exit(samsung);
1067 return -ENOMEM;
1068}
1069
Corentin Chary5dea7a22011-11-26 10:59:59 +01001070static void samsung_sabi_exit(struct samsung_laptop *samsung)
1071{
1072 const struct sabi_config *config = samsung->config;
1073
1074 /* Turn off "Linux" mode in the BIOS */
1075 if (config && config->commands.set_linux != 0xff)
Corentin Chary7e960712011-11-26 11:00:02 +01001076 sabi_set_commandb(samsung, config->commands.set_linux, 0x80);
Corentin Chary5dea7a22011-11-26 10:59:59 +01001077
1078 if (samsung->sabi_iface) {
1079 iounmap(samsung->sabi_iface);
1080 samsung->sabi_iface = NULL;
1081 }
1082 if (samsung->f0000_segment) {
1083 iounmap(samsung->f0000_segment);
1084 samsung->f0000_segment = NULL;
1085 }
1086
1087 samsung->config = NULL;
1088}
1089
Corentin Chary49dd7732011-11-26 11:00:04 +01001090static __init void samsung_sabi_infos(struct samsung_laptop *samsung, int loca,
1091 unsigned int ifaceP)
Corentin Chary5dea7a22011-11-26 10:59:59 +01001092{
1093 const struct sabi_config *config = samsung->config;
1094
1095 printk(KERN_DEBUG "This computer supports SABI==%x\n",
1096 loca + 0xf0000 - 6);
Corentin Chary49dd7732011-11-26 11:00:04 +01001097
Corentin Chary5dea7a22011-11-26 10:59:59 +01001098 printk(KERN_DEBUG "SABI header:\n");
1099 printk(KERN_DEBUG " SMI Port Number = 0x%04x\n",
1100 readw(samsung->sabi + config->header_offsets.port));
1101 printk(KERN_DEBUG " SMI Interface Function = 0x%02x\n",
1102 readb(samsung->sabi + config->header_offsets.iface_func));
1103 printk(KERN_DEBUG " SMI enable memory buffer = 0x%02x\n",
1104 readb(samsung->sabi + config->header_offsets.en_mem));
1105 printk(KERN_DEBUG " SMI restore memory buffer = 0x%02x\n",
1106 readb(samsung->sabi + config->header_offsets.re_mem));
1107 printk(KERN_DEBUG " SABI data offset = 0x%04x\n",
1108 readw(samsung->sabi + config->header_offsets.data_offset));
1109 printk(KERN_DEBUG " SABI data segment = 0x%04x\n",
1110 readw(samsung->sabi + config->header_offsets.data_segment));
Corentin Chary5dea7a22011-11-26 10:59:59 +01001111
Corentin Chary49dd7732011-11-26 11:00:04 +01001112 printk(KERN_DEBUG " SABI pointer = 0x%08x\n", ifaceP);
Corentin Chary5dea7a22011-11-26 10:59:59 +01001113}
1114
1115static int __init samsung_sabi_init(struct samsung_laptop *samsung)
1116{
1117 const struct sabi_config *config = NULL;
1118 const struct sabi_commands *commands;
1119 unsigned int ifaceP;
1120 int ret = 0;
1121 int i;
1122 int loca;
1123
1124 samsung->f0000_segment = ioremap_nocache(0xf0000, 0xffff);
1125 if (!samsung->f0000_segment) {
1126 pr_err("Can't map the segment at 0xf0000\n");
1127 ret = -EINVAL;
1128 goto exit;
1129 }
1130
1131 /* Try to find one of the signatures in memory to find the header */
1132 for (i = 0; sabi_configs[i].test_string != 0; ++i) {
1133 samsung->config = &sabi_configs[i];
1134 loca = find_signature(samsung->f0000_segment,
1135 samsung->config->test_string);
1136 if (loca != 0xffff)
1137 break;
1138 }
1139
1140 if (loca == 0xffff) {
1141 pr_err("This computer does not support SABI\n");
1142 ret = -ENODEV;
1143 goto exit;
1144 }
1145
1146 config = samsung->config;
1147 commands = &config->commands;
1148
1149 /* point to the SMI port Number */
1150 loca += 1;
1151 samsung->sabi = (samsung->f0000_segment + loca);
1152
Corentin Chary5dea7a22011-11-26 10:59:59 +01001153 /* Get a pointer to the SABI Interface */
1154 ifaceP = (readw(samsung->sabi + config->header_offsets.data_segment) & 0x0ffff) << 4;
1155 ifaceP += readw(samsung->sabi + config->header_offsets.data_offset) & 0x0ffff;
Corentin Chary49dd7732011-11-26 11:00:04 +01001156
1157 if (debug)
1158 samsung_sabi_infos(samsung, loca, ifaceP);
1159
Corentin Chary5dea7a22011-11-26 10:59:59 +01001160 samsung->sabi_iface = ioremap_nocache(ifaceP, 16);
1161 if (!samsung->sabi_iface) {
1162 pr_err("Can't remap %x\n", ifaceP);
1163 ret = -EINVAL;
1164 goto exit;
1165 }
1166
Corentin Chary5dea7a22011-11-26 10:59:59 +01001167 /* Turn on "Linux" mode in the BIOS */
1168 if (commands->set_linux != 0xff) {
Corentin Chary7e960712011-11-26 11:00:02 +01001169 int retval = sabi_set_commandb(samsung,
1170 commands->set_linux, 0x81);
Corentin Chary5dea7a22011-11-26 10:59:59 +01001171 if (retval) {
1172 pr_warn("Linux mode was not set!\n");
1173 ret = -ENODEV;
1174 goto exit;
1175 }
1176 }
1177
1178 /* Check for stepping quirk */
Corentin Charyf34cd9c2011-11-26 11:00:00 +01001179 if (samsung->handle_backlight)
1180 check_for_stepping_quirk(samsung);
Corentin Chary5dea7a22011-11-26 10:59:59 +01001181
1182exit:
1183 if (ret)
1184 samsung_sabi_exit(samsung);
1185
1186 return ret;
1187}
1188
1189static void samsung_platform_exit(struct samsung_laptop *samsung)
1190{
1191 if (samsung->platform_device) {
1192 platform_device_unregister(samsung->platform_device);
1193 samsung->platform_device = NULL;
1194 }
1195}
1196
1197static int __init samsung_platform_init(struct samsung_laptop *samsung)
1198{
1199 struct platform_device *pdev;
1200
1201 pdev = platform_device_register_simple("samsung", -1, NULL, 0);
1202 if (IS_ERR(pdev))
1203 return PTR_ERR(pdev);
1204
1205 samsung->platform_device = pdev;
1206 platform_set_drvdata(samsung->platform_device, samsung);
1207 return 0;
1208}
1209
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001210static int __init dmi_check_cb(const struct dmi_system_id *id)
1211{
Corentin Chary5dea7a22011-11-26 10:59:59 +01001212 pr_info("found laptop model '%s'\n", id->ident);
Axel Lin27836582011-03-14 18:56:18 +08001213 return 1;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001214}
1215
1216static struct dmi_system_id __initdata samsung_dmi_table[] = {
1217 {
1218 .ident = "N128",
1219 .matches = {
1220 DMI_MATCH(DMI_SYS_VENDOR,
1221 "SAMSUNG ELECTRONICS CO., LTD."),
1222 DMI_MATCH(DMI_PRODUCT_NAME, "N128"),
1223 DMI_MATCH(DMI_BOARD_NAME, "N128"),
1224 },
1225 .callback = dmi_check_cb,
1226 },
1227 {
1228 .ident = "N130",
1229 .matches = {
1230 DMI_MATCH(DMI_SYS_VENDOR,
1231 "SAMSUNG ELECTRONICS CO., LTD."),
1232 DMI_MATCH(DMI_PRODUCT_NAME, "N130"),
1233 DMI_MATCH(DMI_BOARD_NAME, "N130"),
1234 },
1235 .callback = dmi_check_cb,
1236 },
1237 {
J Witteveen4e2441c2011-07-03 13:15:44 +02001238 .ident = "N510",
1239 .matches = {
1240 DMI_MATCH(DMI_SYS_VENDOR,
1241 "SAMSUNG ELECTRONICS CO., LTD."),
1242 DMI_MATCH(DMI_PRODUCT_NAME, "N510"),
1243 DMI_MATCH(DMI_BOARD_NAME, "N510"),
1244 },
1245 .callback = dmi_check_cb,
1246 },
1247 {
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001248 .ident = "X125",
1249 .matches = {
1250 DMI_MATCH(DMI_SYS_VENDOR,
1251 "SAMSUNG ELECTRONICS CO., LTD."),
1252 DMI_MATCH(DMI_PRODUCT_NAME, "X125"),
1253 DMI_MATCH(DMI_BOARD_NAME, "X125"),
1254 },
1255 .callback = dmi_check_cb,
1256 },
1257 {
1258 .ident = "X120/X170",
1259 .matches = {
1260 DMI_MATCH(DMI_SYS_VENDOR,
1261 "SAMSUNG ELECTRONICS CO., LTD."),
1262 DMI_MATCH(DMI_PRODUCT_NAME, "X120/X170"),
1263 DMI_MATCH(DMI_BOARD_NAME, "X120/X170"),
1264 },
1265 .callback = dmi_check_cb,
1266 },
1267 {
1268 .ident = "NC10",
1269 .matches = {
1270 DMI_MATCH(DMI_SYS_VENDOR,
1271 "SAMSUNG ELECTRONICS CO., LTD."),
1272 DMI_MATCH(DMI_PRODUCT_NAME, "NC10"),
1273 DMI_MATCH(DMI_BOARD_NAME, "NC10"),
1274 },
1275 .callback = dmi_check_cb,
1276 },
1277 {
1278 .ident = "NP-Q45",
1279 .matches = {
1280 DMI_MATCH(DMI_SYS_VENDOR,
1281 "SAMSUNG ELECTRONICS CO., LTD."),
1282 DMI_MATCH(DMI_PRODUCT_NAME, "SQ45S70S"),
1283 DMI_MATCH(DMI_BOARD_NAME, "SQ45S70S"),
1284 },
1285 .callback = dmi_check_cb,
1286 },
1287 {
1288 .ident = "X360",
1289 .matches = {
1290 DMI_MATCH(DMI_SYS_VENDOR,
1291 "SAMSUNG ELECTRONICS CO., LTD."),
1292 DMI_MATCH(DMI_PRODUCT_NAME, "X360"),
1293 DMI_MATCH(DMI_BOARD_NAME, "X360"),
1294 },
1295 .callback = dmi_check_cb,
1296 },
1297 {
Alberto Mardegan3d536ed2011-04-08 17:02:03 +02001298 .ident = "R410 Plus",
1299 .matches = {
1300 DMI_MATCH(DMI_SYS_VENDOR,
1301 "SAMSUNG ELECTRONICS CO., LTD."),
1302 DMI_MATCH(DMI_PRODUCT_NAME, "R410P"),
1303 DMI_MATCH(DMI_BOARD_NAME, "R460"),
1304 },
1305 .callback = dmi_check_cb,
1306 },
1307 {
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001308 .ident = "R518",
1309 .matches = {
1310 DMI_MATCH(DMI_SYS_VENDOR,
1311 "SAMSUNG ELECTRONICS CO., LTD."),
1312 DMI_MATCH(DMI_PRODUCT_NAME, "R518"),
1313 DMI_MATCH(DMI_BOARD_NAME, "R518"),
1314 },
1315 .callback = dmi_check_cb,
1316 },
1317 {
1318 .ident = "R519/R719",
1319 .matches = {
1320 DMI_MATCH(DMI_SYS_VENDOR,
1321 "SAMSUNG ELECTRONICS CO., LTD."),
1322 DMI_MATCH(DMI_PRODUCT_NAME, "R519/R719"),
1323 DMI_MATCH(DMI_BOARD_NAME, "R519/R719"),
1324 },
1325 .callback = dmi_check_cb,
1326 },
1327 {
Thomas Courbon78a75392011-07-20 22:57:44 +02001328 .ident = "N150/N210/N220",
1329 .matches = {
1330 DMI_MATCH(DMI_SYS_VENDOR,
1331 "SAMSUNG ELECTRONICS CO., LTD."),
1332 DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220"),
1333 DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220"),
1334 },
1335 .callback = dmi_check_cb,
1336 },
1337 {
Raul Gutierrez Segalesf689c872011-09-20 09:16:15 -07001338 .ident = "N220",
1339 .matches = {
1340 DMI_MATCH(DMI_SYS_VENDOR,
1341 "SAMSUNG ELECTRONICS CO., LTD."),
1342 DMI_MATCH(DMI_PRODUCT_NAME, "N220"),
1343 DMI_MATCH(DMI_BOARD_NAME, "N220"),
1344 },
1345 .callback = dmi_check_cb,
1346 },
1347 {
Greg Kroah-Hartman10165072011-04-08 17:02:04 +02001348 .ident = "N150/N210/N220/N230",
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001349 .matches = {
1350 DMI_MATCH(DMI_SYS_VENDOR,
1351 "SAMSUNG ELECTRONICS CO., LTD."),
Greg Kroah-Hartman10165072011-04-08 17:02:04 +02001352 DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220/N230"),
1353 DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220/N230"),
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001354 },
1355 .callback = dmi_check_cb,
1356 },
1357 {
1358 .ident = "N150P/N210P/N220P",
1359 .matches = {
1360 DMI_MATCH(DMI_SYS_VENDOR,
1361 "SAMSUNG ELECTRONICS CO., LTD."),
1362 DMI_MATCH(DMI_PRODUCT_NAME, "N150P/N210P/N220P"),
1363 DMI_MATCH(DMI_BOARD_NAME, "N150P/N210P/N220P"),
1364 },
1365 .callback = dmi_check_cb,
1366 },
1367 {
Stefan Bellerf87d0292011-09-20 09:16:08 -07001368 .ident = "R700",
1369 .matches = {
1370 DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1371 DMI_MATCH(DMI_PRODUCT_NAME, "SR700"),
1372 DMI_MATCH(DMI_BOARD_NAME, "SR700"),
1373 },
1374 .callback = dmi_check_cb,
1375 },
1376 {
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001377 .ident = "R530/R730",
1378 .matches = {
1379 DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1380 DMI_MATCH(DMI_PRODUCT_NAME, "R530/R730"),
1381 DMI_MATCH(DMI_BOARD_NAME, "R530/R730"),
1382 },
1383 .callback = dmi_check_cb,
1384 },
1385 {
1386 .ident = "NF110/NF210/NF310",
1387 .matches = {
1388 DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1389 DMI_MATCH(DMI_PRODUCT_NAME, "NF110/NF210/NF310"),
1390 DMI_MATCH(DMI_BOARD_NAME, "NF110/NF210/NF310"),
1391 },
1392 .callback = dmi_check_cb,
1393 },
1394 {
1395 .ident = "N145P/N250P/N260P",
1396 .matches = {
1397 DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1398 DMI_MATCH(DMI_PRODUCT_NAME, "N145P/N250P/N260P"),
1399 DMI_MATCH(DMI_BOARD_NAME, "N145P/N250P/N260P"),
1400 },
1401 .callback = dmi_check_cb,
1402 },
1403 {
1404 .ident = "R70/R71",
1405 .matches = {
1406 DMI_MATCH(DMI_SYS_VENDOR,
1407 "SAMSUNG ELECTRONICS CO., LTD."),
1408 DMI_MATCH(DMI_PRODUCT_NAME, "R70/R71"),
1409 DMI_MATCH(DMI_BOARD_NAME, "R70/R71"),
1410 },
1411 .callback = dmi_check_cb,
1412 },
1413 {
1414 .ident = "P460",
1415 .matches = {
1416 DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1417 DMI_MATCH(DMI_PRODUCT_NAME, "P460"),
1418 DMI_MATCH(DMI_BOARD_NAME, "P460"),
1419 },
1420 .callback = dmi_check_cb,
1421 },
Smelov Andrey093ed562011-09-20 09:16:10 -07001422 {
1423 .ident = "R528/R728",
1424 .matches = {
1425 DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1426 DMI_MATCH(DMI_PRODUCT_NAME, "R528/R728"),
1427 DMI_MATCH(DMI_BOARD_NAME, "R528/R728"),
1428 },
1429 .callback = dmi_check_cb,
1430 },
Jason Stubbs7b3c2572011-09-20 09:16:14 -07001431 {
1432 .ident = "NC210/NC110",
1433 .matches = {
1434 DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1435 DMI_MATCH(DMI_PRODUCT_NAME, "NC210/NC110"),
1436 DMI_MATCH(DMI_BOARD_NAME, "NC210/NC110"),
1437 },
1438 .callback = dmi_check_cb,
1439 },
Tommaso Massimi7500eeb2011-09-20 09:16:09 -07001440 {
1441 .ident = "X520",
1442 .matches = {
1443 DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1444 DMI_MATCH(DMI_PRODUCT_NAME, "X520"),
1445 DMI_MATCH(DMI_BOARD_NAME, "X520"),
1446 },
1447 .callback = dmi_check_cb,
1448 },
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001449 { },
1450};
1451MODULE_DEVICE_TABLE(dmi, samsung_dmi_table);
1452
Corentin Chary5dea7a22011-11-26 10:59:59 +01001453static struct platform_device *samsung_platform_device;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001454
1455static int __init samsung_init(void)
1456{
Corentin Chary5dea7a22011-11-26 10:59:59 +01001457 struct samsung_laptop *samsung;
1458 int ret;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001459
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001460 if (!force && !dmi_check_system(samsung_dmi_table))
1461 return -ENODEV;
1462
Corentin Charya6df4892011-11-26 10:59:58 +01001463 samsung = kzalloc(sizeof(*samsung), GFP_KERNEL);
1464 if (!samsung)
1465 return -ENOMEM;
1466
1467 mutex_init(&samsung->sabi_mutex);
Corentin Charyf34cd9c2011-11-26 11:00:00 +01001468 samsung->handle_backlight = true;
1469
1470#ifdef CONFIG_ACPI
1471 /* Don't handle backlight here if the acpi video already handle it */
1472 if (acpi_video_backlight_support()) {
1473 pr_info("Backlight controlled by ACPI video driver\n");
1474 samsung->handle_backlight = false;
1475 }
1476#endif
Corentin Charya6df4892011-11-26 10:59:58 +01001477
Corentin Chary5dea7a22011-11-26 10:59:59 +01001478 ret = samsung_platform_init(samsung);
1479 if (ret)
1480 goto error_platform;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001481
Corentin Chary5dea7a22011-11-26 10:59:59 +01001482 ret = samsung_sabi_init(samsung);
1483 if (ret)
1484 goto error_sabi;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001485
Corentin Chary5dea7a22011-11-26 10:59:59 +01001486 ret = samsung_sysfs_init(samsung);
1487 if (ret)
1488 goto error_sysfs;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001489
Corentin Chary5dea7a22011-11-26 10:59:59 +01001490 ret = samsung_backlight_init(samsung);
1491 if (ret)
1492 goto error_backlight;
Corentin Charya6df4892011-11-26 10:59:58 +01001493
Corentin Chary5dea7a22011-11-26 10:59:59 +01001494 ret = samsung_rfkill_init(samsung);
1495 if (ret)
1496 goto error_rfkill;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001497
Corentin Charyf674ebf2011-11-26 11:00:08 +01001498 ret = samsung_leds_init(samsung);
1499 if (ret)
1500 goto error_leds;
1501
Corentin Chary5b80fc42011-11-26 11:00:03 +01001502 ret = samsung_debugfs_init(samsung);
1503 if (ret)
1504 goto error_debugfs;
1505
Corentin Chary5dea7a22011-11-26 10:59:59 +01001506 samsung_platform_device = samsung->platform_device;
1507 return ret;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001508
Corentin Chary5b80fc42011-11-26 11:00:03 +01001509error_debugfs:
Corentin Charyf674ebf2011-11-26 11:00:08 +01001510 samsung_leds_exit(samsung);
1511error_leds:
Corentin Chary5b80fc42011-11-26 11:00:03 +01001512 samsung_rfkill_exit(samsung);
Corentin Chary5dea7a22011-11-26 10:59:59 +01001513error_rfkill:
1514 samsung_backlight_exit(samsung);
1515error_backlight:
1516 samsung_sysfs_exit(samsung);
1517error_sysfs:
1518 samsung_sabi_exit(samsung);
1519error_sabi:
1520 samsung_platform_exit(samsung);
1521error_platform:
Corentin Charya6df4892011-11-26 10:59:58 +01001522 kfree(samsung);
Corentin Chary5dea7a22011-11-26 10:59:59 +01001523 return ret;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001524}
1525
1526static void __exit samsung_exit(void)
1527{
Corentin Chary5dea7a22011-11-26 10:59:59 +01001528 struct samsung_laptop *samsung;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001529
Corentin Chary5dea7a22011-11-26 10:59:59 +01001530 samsung = platform_get_drvdata(samsung_platform_device);
Corentin Charya6df4892011-11-26 10:59:58 +01001531
Corentin Chary5b80fc42011-11-26 11:00:03 +01001532 samsung_debugfs_exit(samsung);
Corentin Charyf674ebf2011-11-26 11:00:08 +01001533 samsung_leds_exit(samsung);
Corentin Chary5dea7a22011-11-26 10:59:59 +01001534 samsung_rfkill_exit(samsung);
1535 samsung_backlight_exit(samsung);
1536 samsung_sysfs_exit(samsung);
1537 samsung_sabi_exit(samsung);
1538 samsung_platform_exit(samsung);
1539
Corentin Charya6df4892011-11-26 10:59:58 +01001540 kfree(samsung);
Corentin Chary5dea7a22011-11-26 10:59:59 +01001541 samsung_platform_device = NULL;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001542}
1543
1544module_init(samsung_init);
1545module_exit(samsung_exit);
1546
1547MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@suse.de>");
1548MODULE_DESCRIPTION("Samsung Backlight driver");
1549MODULE_LICENSE("GPL");