blob: 431f7dc2f222b48cdb75282372c381e8b56aab94 [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 Chary84d482f2011-11-26 11:00:09 +010048#define WL_STATUS_WLAN 0x0
49#define WL_STATUS_BT 0x2
50
Corentin Chary7e960712011-11-26 11:00:02 +010051/* Structure get/set data using sabi */
52struct sabi_data {
53 union {
54 struct {
55 u32 d0;
56 u32 d1;
57 u16 d2;
58 u8 d3;
59 };
60 u8 data[11];
61 };
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -050062};
63
64struct sabi_header_offsets {
65 u8 port;
66 u8 re_mem;
67 u8 iface_func;
68 u8 en_mem;
69 u8 data_offset;
70 u8 data_segment;
71};
72
73struct sabi_commands {
74 /*
75 * Brightness is 0 - 8, as described above.
76 * Value 0 is for the BIOS to use
77 */
Corentin Chary7e960712011-11-26 11:00:02 +010078 u16 get_brightness;
79 u16 set_brightness;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -050080
81 /*
82 * first byte:
83 * 0x00 - wireless is off
84 * 0x01 - wireless is on
85 * second byte:
86 * 0x02 - 3G is off
87 * 0x03 - 3G is on
88 * TODO, verify 3G is correct, that doesn't seem right...
89 */
Corentin Chary7e960712011-11-26 11:00:02 +010090 u16 get_wireless_button;
91 u16 set_wireless_button;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -050092
93 /* 0 is off, 1 is on */
Corentin Chary7e960712011-11-26 11:00:02 +010094 u16 get_backlight;
95 u16 set_backlight;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -050096
97 /*
98 * 0x80 or 0x00 - no action
99 * 0x81 - recovery key pressed
100 */
Corentin Chary7e960712011-11-26 11:00:02 +0100101 u16 get_recovery_mode;
102 u16 set_recovery_mode;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500103
104 /*
105 * on seclinux: 0 is low, 1 is high,
106 * on swsmi: 0 is normal, 1 is silent, 2 is turbo
107 */
Corentin Chary7e960712011-11-26 11:00:02 +0100108 u16 get_performance_level;
109 u16 set_performance_level;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500110
Corentin Charycb5b5c92011-11-26 11:00:05 +0100111 /* 0x80 is off, 0x81 is on */
112 u16 get_battery_life_extender;
113 u16 set_battery_life_extender;
114
Corentin Chary3a75d372011-11-26 11:00:06 +0100115 /* 0x80 is off, 0x81 is on */
116 u16 get_usb_charge;
117 u16 set_usb_charge;
118
Corentin Chary84d482f2011-11-26 11:00:09 +0100119 /* the first byte is for bluetooth and the third one is for wlan */
120 u16 get_wireless_status;
121 u16 set_wireless_status;
122
Corentin Charyf674ebf2011-11-26 11:00:08 +0100123 /* 0x81 to read, (0x82 | level << 8) to set, 0xaabb to enable */
124 u16 kbd_backlight;
125
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500126 /*
127 * Tell the BIOS that Linux is running on this machine.
128 * 81 is on, 80 is off
129 */
Corentin Chary7e960712011-11-26 11:00:02 +0100130 u16 set_linux;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500131};
132
133struct sabi_performance_level {
134 const char *name;
Corentin Chary7e960712011-11-26 11:00:02 +0100135 u16 value;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500136};
137
138struct sabi_config {
Corentin Chary84d482f2011-11-26 11:00:09 +0100139 int sabi_version;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500140 const char *test_string;
141 u16 main_function;
142 const struct sabi_header_offsets header_offsets;
143 const struct sabi_commands commands;
144 const struct sabi_performance_level performance_levels[4];
145 u8 min_brightness;
146 u8 max_brightness;
147};
148
149static const struct sabi_config sabi_configs[] = {
150 {
Corentin Chary84d482f2011-11-26 11:00:09 +0100151 /* I don't know if it is really 2, but it it is
152 * less than 3 anyway */
153 .sabi_version = 2,
154
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500155 .test_string = "SECLINUX",
156
157 .main_function = 0x4c49,
158
159 .header_offsets = {
160 .port = 0x00,
161 .re_mem = 0x02,
162 .iface_func = 0x03,
163 .en_mem = 0x04,
164 .data_offset = 0x05,
165 .data_segment = 0x07,
166 },
167
168 .commands = {
169 .get_brightness = 0x00,
170 .set_brightness = 0x01,
171
172 .get_wireless_button = 0x02,
173 .set_wireless_button = 0x03,
174
175 .get_backlight = 0x04,
176 .set_backlight = 0x05,
177
178 .get_recovery_mode = 0x06,
179 .set_recovery_mode = 0x07,
180
181 .get_performance_level = 0x08,
182 .set_performance_level = 0x09,
183
Corentin Charycb5b5c92011-11-26 11:00:05 +0100184 .get_battery_life_extender = 0xFFFF,
185 .set_battery_life_extender = 0xFFFF,
186
Corentin Chary3a75d372011-11-26 11:00:06 +0100187 .get_usb_charge = 0xFFFF,
188 .set_usb_charge = 0xFFFF,
189
Corentin Chary84d482f2011-11-26 11:00:09 +0100190 .get_wireless_status = 0xFFFF,
191 .set_wireless_status = 0xFFFF,
192
Corentin Charyf674ebf2011-11-26 11:00:08 +0100193 .kbd_backlight = 0xFFFF,
194
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500195 .set_linux = 0x0a,
196 },
197
198 .performance_levels = {
199 {
200 .name = "silent",
201 .value = 0,
202 },
203 {
204 .name = "normal",
205 .value = 1,
206 },
207 { },
208 },
209 .min_brightness = 1,
210 .max_brightness = 8,
211 },
212 {
Corentin Chary84d482f2011-11-26 11:00:09 +0100213 .sabi_version = 3,
214
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500215 .test_string = "SwSmi@",
216
217 .main_function = 0x5843,
218
219 .header_offsets = {
220 .port = 0x00,
221 .re_mem = 0x04,
222 .iface_func = 0x02,
223 .en_mem = 0x03,
224 .data_offset = 0x05,
225 .data_segment = 0x07,
226 },
227
228 .commands = {
229 .get_brightness = 0x10,
230 .set_brightness = 0x11,
231
232 .get_wireless_button = 0x12,
233 .set_wireless_button = 0x13,
234
235 .get_backlight = 0x2d,
236 .set_backlight = 0x2e,
237
238 .get_recovery_mode = 0xff,
239 .set_recovery_mode = 0xff,
240
241 .get_performance_level = 0x31,
242 .set_performance_level = 0x32,
243
Corentin Charycb5b5c92011-11-26 11:00:05 +0100244 .get_battery_life_extender = 0x65,
245 .set_battery_life_extender = 0x66,
246
Corentin Chary3a75d372011-11-26 11:00:06 +0100247 .get_usb_charge = 0x67,
248 .set_usb_charge = 0x68,
249
Corentin Chary84d482f2011-11-26 11:00:09 +0100250 .get_wireless_status = 0x69,
251 .set_wireless_status = 0x6a,
252
Corentin Charyf674ebf2011-11-26 11:00:08 +0100253 .kbd_backlight = 0x78,
254
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500255 .set_linux = 0xff,
256 },
257
258 .performance_levels = {
259 {
260 .name = "normal",
261 .value = 0,
262 },
263 {
264 .name = "silent",
265 .value = 1,
266 },
267 {
268 .name = "overclock",
269 .value = 2,
270 },
271 { },
272 },
273 .min_brightness = 0,
274 .max_brightness = 8,
275 },
276 { },
277};
278
Corentin Chary5b80fc42011-11-26 11:00:03 +0100279/*
280 * samsung-laptop/ - debugfs root directory
281 * f0000_segment - dump f0000 segment
282 * command - current command
283 * data - current data
284 * d0, d1, d2, d3 - data fields
285 * call - call SABI using command and data
286 *
287 * This allow to call arbitrary sabi commands wihout
288 * modifying the driver at all.
289 * For example, setting the keyboard backlight brightness to 5
290 *
291 * echo 0x78 > command
292 * echo 0x0582 > d0
293 * echo 0 > d1
294 * echo 0 > d2
295 * echo 0 > d3
296 * cat call
297 */
298
299struct samsung_laptop_debug {
300 struct dentry *root;
301 struct sabi_data data;
302 u16 command;
303
304 struct debugfs_blob_wrapper f0000_wrapper;
305 struct debugfs_blob_wrapper data_wrapper;
306};
307
Corentin Chary84d482f2011-11-26 11:00:09 +0100308struct samsung_laptop;
309
310struct samsung_rfkill {
311 struct samsung_laptop *samsung;
312 struct rfkill *rfkill;
313 enum rfkill_type type;
314};
315
Corentin Charya6df4892011-11-26 10:59:58 +0100316struct samsung_laptop {
317 const struct sabi_config *config;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500318
Corentin Charya6df4892011-11-26 10:59:58 +0100319 void __iomem *sabi;
320 void __iomem *sabi_iface;
321 void __iomem *f0000_segment;
322
323 struct mutex sabi_mutex;
324
Corentin Chary5dea7a22011-11-26 10:59:59 +0100325 struct platform_device *platform_device;
Corentin Charya6df4892011-11-26 10:59:58 +0100326 struct backlight_device *backlight_device;
Corentin Chary84d482f2011-11-26 11:00:09 +0100327
328 struct samsung_rfkill wlan;
329 struct samsung_rfkill bluetooth;
Corentin Charya6df4892011-11-26 10:59:58 +0100330
Corentin Charyf674ebf2011-11-26 11:00:08 +0100331 struct led_classdev kbd_led;
332 int kbd_led_wk;
333 struct workqueue_struct *led_workqueue;
334 struct work_struct kbd_led_work;
335
Corentin Chary5b80fc42011-11-26 11:00:03 +0100336 struct samsung_laptop_debug debug;
337
Corentin Charyf34cd9c2011-11-26 11:00:00 +0100338 bool handle_backlight;
Corentin Charya6df4892011-11-26 10:59:58 +0100339 bool has_stepping_quirk;
340};
341
Corentin Chary5dea7a22011-11-26 10:59:59 +0100342
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500343
Rusty Russell90ab5ee2012-01-13 09:32:20 +1030344static bool force;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500345module_param(force, bool, 0);
346MODULE_PARM_DESC(force,
347 "Disable the DMI check and forces the driver to be loaded");
348
Rusty Russell90ab5ee2012-01-13 09:32:20 +1030349static bool debug;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500350module_param(debug, bool, S_IRUGO | S_IWUSR);
351MODULE_PARM_DESC(debug, "Debug enabled or not");
352
Corentin Chary7e960712011-11-26 11:00:02 +0100353static int sabi_command(struct samsung_laptop *samsung, u16 command,
354 struct sabi_data *in,
355 struct sabi_data *out)
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500356{
Corentin Charya6df4892011-11-26 10:59:58 +0100357 const struct sabi_config *config = samsung->config;
Corentin Chary7e960712011-11-26 11:00:02 +0100358 int ret = 0;
Corentin Charya6df4892011-11-26 10:59:58 +0100359 u16 port = readw(samsung->sabi + config->header_offsets.port);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500360 u8 complete, iface_data;
361
Corentin Charya6df4892011-11-26 10:59:58 +0100362 mutex_lock(&samsung->sabi_mutex);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500363
Corentin Chary7e960712011-11-26 11:00:02 +0100364 if (debug) {
365 if (in)
366 pr_info("SABI 0x%04x {0x%08x, 0x%08x, 0x%04x, 0x%02x}",
367 command, in->d0, in->d1, in->d2, in->d3);
368 else
369 pr_info("SABI 0x%04x", command);
370 }
371
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500372 /* enable memory to be able to write to it */
Corentin Charya6df4892011-11-26 10:59:58 +0100373 outb(readb(samsung->sabi + config->header_offsets.en_mem), port);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500374
375 /* write out the command */
Corentin Charya6df4892011-11-26 10:59:58 +0100376 writew(config->main_function, samsung->sabi_iface + SABI_IFACE_MAIN);
377 writew(command, samsung->sabi_iface + SABI_IFACE_SUB);
378 writeb(0, samsung->sabi_iface + SABI_IFACE_COMPLETE);
Corentin Chary7e960712011-11-26 11:00:02 +0100379 if (in) {
380 writel(in->d0, samsung->sabi_iface + SABI_IFACE_DATA);
381 writel(in->d1, samsung->sabi_iface + SABI_IFACE_DATA + 4);
382 writew(in->d2, samsung->sabi_iface + SABI_IFACE_DATA + 8);
383 writeb(in->d3, samsung->sabi_iface + SABI_IFACE_DATA + 10);
384 }
Corentin Charya6df4892011-11-26 10:59:58 +0100385 outb(readb(samsung->sabi + config->header_offsets.iface_func), port);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500386
387 /* write protect memory to make it safe */
Corentin Charya6df4892011-11-26 10:59:58 +0100388 outb(readb(samsung->sabi + config->header_offsets.re_mem), port);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500389
390 /* see if the command actually succeeded */
Corentin Charya6df4892011-11-26 10:59:58 +0100391 complete = readb(samsung->sabi_iface + SABI_IFACE_COMPLETE);
392 iface_data = readb(samsung->sabi_iface + SABI_IFACE_DATA);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500393 if (complete != 0xaa || iface_data == 0xff) {
Corentin Chary7e960712011-11-26 11:00:02 +0100394 pr_warn("SABI command 0x%04x failed with"
395 " completion flag 0x%02x and interface data 0x%02x",
396 command, complete, iface_data);
397 ret = -EINVAL;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500398 goto exit;
399 }
Corentin Chary7e960712011-11-26 11:00:02 +0100400
401 if (out) {
402 out->d0 = readl(samsung->sabi_iface + SABI_IFACE_DATA);
403 out->d1 = readl(samsung->sabi_iface + SABI_IFACE_DATA + 4);
404 out->d2 = readw(samsung->sabi_iface + SABI_IFACE_DATA + 2);
405 out->d3 = readb(samsung->sabi_iface + SABI_IFACE_DATA + 1);
406 }
407
408 if (debug && out) {
409 pr_info("SABI {0x%08x, 0x%08x, 0x%04x, 0x%02x}",
410 out->d0, out->d1, out->d2, out->d3);
411 }
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500412
413exit:
Corentin Charya6df4892011-11-26 10:59:58 +0100414 mutex_unlock(&samsung->sabi_mutex);
Corentin Chary7e960712011-11-26 11:00:02 +0100415 return ret;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500416}
417
Corentin Chary7e960712011-11-26 11:00:02 +0100418/* simple wrappers usable with most commands */
419static int sabi_set_commandb(struct samsung_laptop *samsung,
420 u16 command, u8 data)
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500421{
Corentin Chary7e960712011-11-26 11:00:02 +0100422 struct sabi_data in = { .d0 = 0, .d1 = 0, .d2 = 0, .d3 = 0 };
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500423
Corentin Chary7e960712011-11-26 11:00:02 +0100424 in.data[0] = data;
425 return sabi_command(samsung, command, &in, NULL);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500426}
427
Corentin Chary5dea7a22011-11-26 10:59:59 +0100428static int read_brightness(struct samsung_laptop *samsung)
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500429{
Corentin Charya6df4892011-11-26 10:59:58 +0100430 const struct sabi_config *config = samsung->config;
431 const struct sabi_commands *commands = &samsung->config->commands;
Corentin Chary7e960712011-11-26 11:00:02 +0100432 struct sabi_data sretval;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500433 int user_brightness = 0;
434 int retval;
435
Corentin Chary7e960712011-11-26 11:00:02 +0100436 retval = sabi_command(samsung, commands->get_brightness,
437 NULL, &sretval);
438 if (retval)
439 return retval;
440
441 user_brightness = sretval.data[0];
442 if (user_brightness > config->min_brightness)
443 user_brightness -= config->min_brightness;
444 else
445 user_brightness = 0;
446
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500447 return user_brightness;
448}
449
Corentin Chary5dea7a22011-11-26 10:59:59 +0100450static void set_brightness(struct samsung_laptop *samsung, u8 user_brightness)
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500451{
Corentin Charya6df4892011-11-26 10:59:58 +0100452 const struct sabi_config *config = samsung->config;
453 const struct sabi_commands *commands = &samsung->config->commands;
454 u8 user_level = user_brightness + config->min_brightness;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500455
Corentin Charya6df4892011-11-26 10:59:58 +0100456 if (samsung->has_stepping_quirk && user_level != 0) {
Jason Stubbsac080522011-09-20 09:16:13 -0700457 /*
458 * short circuit if the specified level is what's already set
459 * to prevent the screen from flickering needlessly
460 */
Corentin Chary5dea7a22011-11-26 10:59:59 +0100461 if (user_brightness == read_brightness(samsung))
Jason Stubbsac080522011-09-20 09:16:13 -0700462 return;
463
Corentin Chary7e960712011-11-26 11:00:02 +0100464 sabi_set_commandb(samsung, commands->set_brightness, 0);
Jason Stubbsac080522011-09-20 09:16:13 -0700465 }
466
Corentin Chary7e960712011-11-26 11:00:02 +0100467 sabi_set_commandb(samsung, commands->set_brightness, user_level);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500468}
469
470static int get_brightness(struct backlight_device *bd)
471{
Corentin Chary5dea7a22011-11-26 10:59:59 +0100472 struct samsung_laptop *samsung = bl_get_data(bd);
473
474 return read_brightness(samsung);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500475}
476
Corentin Chary5dea7a22011-11-26 10:59:59 +0100477static void check_for_stepping_quirk(struct samsung_laptop *samsung)
Jason Stubbsac080522011-09-20 09:16:13 -0700478{
Corentin Chary5dea7a22011-11-26 10:59:59 +0100479 int initial_level;
480 int check_level;
481 int orig_level = read_brightness(samsung);
Jason Stubbsac080522011-09-20 09:16:13 -0700482
483 /*
484 * Some laptops exhibit the strange behaviour of stepping toward
485 * (rather than setting) the brightness except when changing to/from
486 * brightness level 0. This behaviour is checked for here and worked
487 * around in set_brightness.
488 */
489
John Serockba05b232011-10-13 06:42:01 -0400490 if (orig_level == 0)
Corentin Chary5dea7a22011-11-26 10:59:59 +0100491 set_brightness(samsung, 1);
John Serockba05b232011-10-13 06:42:01 -0400492
Corentin Chary5dea7a22011-11-26 10:59:59 +0100493 initial_level = read_brightness(samsung);
John Serockba05b232011-10-13 06:42:01 -0400494
Jason Stubbsac080522011-09-20 09:16:13 -0700495 if (initial_level <= 2)
496 check_level = initial_level + 2;
497 else
498 check_level = initial_level - 2;
499
Corentin Charya6df4892011-11-26 10:59:58 +0100500 samsung->has_stepping_quirk = false;
Corentin Chary5dea7a22011-11-26 10:59:59 +0100501 set_brightness(samsung, check_level);
Jason Stubbsac080522011-09-20 09:16:13 -0700502
Corentin Chary5dea7a22011-11-26 10:59:59 +0100503 if (read_brightness(samsung) != check_level) {
Corentin Charya6df4892011-11-26 10:59:58 +0100504 samsung->has_stepping_quirk = true;
Jason Stubbsac080522011-09-20 09:16:13 -0700505 pr_info("enabled workaround for brightness stepping quirk\n");
506 }
507
Corentin Chary5dea7a22011-11-26 10:59:59 +0100508 set_brightness(samsung, orig_level);
Jason Stubbsac080522011-09-20 09:16:13 -0700509}
510
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500511static int update_status(struct backlight_device *bd)
512{
Corentin Chary5dea7a22011-11-26 10:59:59 +0100513 struct samsung_laptop *samsung = bl_get_data(bd);
Corentin Charya6df4892011-11-26 10:59:58 +0100514 const struct sabi_commands *commands = &samsung->config->commands;
515
Corentin Chary5dea7a22011-11-26 10:59:59 +0100516 set_brightness(samsung, bd->props.brightness);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500517
518 if (bd->props.power == FB_BLANK_UNBLANK)
Corentin Chary7e960712011-11-26 11:00:02 +0100519 sabi_set_commandb(samsung, commands->set_backlight, 1);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500520 else
Corentin Chary7e960712011-11-26 11:00:02 +0100521 sabi_set_commandb(samsung, commands->set_backlight, 0);
Corentin Chary5dea7a22011-11-26 10:59:59 +0100522
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500523 return 0;
524}
525
526static const struct backlight_ops backlight_ops = {
527 .get_brightness = get_brightness,
528 .update_status = update_status,
529};
530
Corentin Chary84d482f2011-11-26 11:00:09 +0100531static int seclinux_rfkill_set(void *data, bool blocked)
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500532{
Corentin Chary5dea7a22011-11-26 10:59:59 +0100533 struct samsung_laptop *samsung = data;
Corentin Charya6df4892011-11-26 10:59:58 +0100534 const struct sabi_commands *commands = &samsung->config->commands;
535
Corentin Chary84d482f2011-11-26 11:00:09 +0100536 return sabi_set_commandb(samsung, commands->set_wireless_button,
537 !blocked);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500538}
539
Corentin Chary84d482f2011-11-26 11:00:09 +0100540static struct rfkill_ops seclinux_rfkill_ops = {
541 .set_block = seclinux_rfkill_set,
542};
543
544static int swsmi_wireless_status(struct samsung_laptop *samsung,
545 struct sabi_data *data)
546{
547 const struct sabi_commands *commands = &samsung->config->commands;
548
549 return sabi_command(samsung, commands->get_wireless_status,
550 NULL, data);
551}
552
553static int swsmi_rfkill_set(void *priv, bool blocked)
554{
555 struct samsung_rfkill *srfkill = priv;
556 struct samsung_laptop *samsung = srfkill->samsung;
557 const struct sabi_commands *commands = &samsung->config->commands;
558 struct sabi_data data;
559 int ret, i;
560
561 ret = swsmi_wireless_status(samsung, &data);
562 if (ret)
563 return ret;
564
565 /* Don't set the state for non-present devices */
566 for (i = 0; i < 4; i++)
567 if (data.data[i] == 0x02)
568 data.data[1] = 0;
569
570 if (srfkill->type == RFKILL_TYPE_WLAN)
571 data.data[WL_STATUS_WLAN] = !blocked;
572 else if (srfkill->type == RFKILL_TYPE_BLUETOOTH)
573 data.data[WL_STATUS_BT] = !blocked;
574
575 return sabi_command(samsung, commands->set_wireless_status,
576 &data, &data);
577}
578
579static void swsmi_rfkill_query(struct rfkill *rfkill, void *priv)
580{
581 struct samsung_rfkill *srfkill = priv;
582 struct samsung_laptop *samsung = srfkill->samsung;
583 struct sabi_data data;
584 int ret;
585
586 ret = swsmi_wireless_status(samsung, &data);
587 if (ret)
588 return ;
589
590 if (srfkill->type == RFKILL_TYPE_WLAN)
591 ret = data.data[WL_STATUS_WLAN];
592 else if (srfkill->type == RFKILL_TYPE_BLUETOOTH)
593 ret = data.data[WL_STATUS_BT];
594 else
595 return ;
596
597 rfkill_set_sw_state(rfkill, !ret);
598}
599
600static struct rfkill_ops swsmi_rfkill_ops = {
601 .set_block = swsmi_rfkill_set,
602 .query = swsmi_rfkill_query,
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500603};
604
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500605static ssize_t get_performance_level(struct device *dev,
606 struct device_attribute *attr, char *buf)
607{
Corentin Chary5dea7a22011-11-26 10:59:59 +0100608 struct samsung_laptop *samsung = dev_get_drvdata(dev);
Corentin Charya6df4892011-11-26 10:59:58 +0100609 const struct sabi_config *config = samsung->config;
Corentin Chary5dea7a22011-11-26 10:59:59 +0100610 const struct sabi_commands *commands = &config->commands;
Corentin Chary7e960712011-11-26 11:00:02 +0100611 struct sabi_data sretval;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500612 int retval;
613 int i;
614
615 /* Read the state */
Corentin Chary7e960712011-11-26 11:00:02 +0100616 retval = sabi_command(samsung, commands->get_performance_level,
617 NULL, &sretval);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500618 if (retval)
619 return retval;
620
621 /* The logic is backwards, yeah, lots of fun... */
Corentin Charya6df4892011-11-26 10:59:58 +0100622 for (i = 0; config->performance_levels[i].name; ++i) {
Corentin Chary7e960712011-11-26 11:00:02 +0100623 if (sretval.data[0] == config->performance_levels[i].value)
Corentin Charya6df4892011-11-26 10:59:58 +0100624 return sprintf(buf, "%s\n", config->performance_levels[i].name);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500625 }
626 return sprintf(buf, "%s\n", "unknown");
627}
628
629static ssize_t set_performance_level(struct device *dev,
630 struct device_attribute *attr, const char *buf,
631 size_t count)
632{
Corentin Chary5dea7a22011-11-26 10:59:59 +0100633 struct samsung_laptop *samsung = dev_get_drvdata(dev);
Corentin Charya6df4892011-11-26 10:59:58 +0100634 const struct sabi_config *config = samsung->config;
Corentin Chary5dea7a22011-11-26 10:59:59 +0100635 const struct sabi_commands *commands = &config->commands;
636 int i;
Corentin Charya6df4892011-11-26 10:59:58 +0100637
Corentin Chary5dea7a22011-11-26 10:59:59 +0100638 if (count < 1)
639 return count;
640
641 for (i = 0; config->performance_levels[i].name; ++i) {
642 const struct sabi_performance_level *level =
643 &config->performance_levels[i];
644 if (!strncasecmp(level->name, buf, strlen(level->name))) {
Corentin Chary7e960712011-11-26 11:00:02 +0100645 sabi_set_commandb(samsung,
646 commands->set_performance_level,
647 level->value);
Corentin Chary5dea7a22011-11-26 10:59:59 +0100648 break;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500649 }
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500650 }
Corentin Chary5dea7a22011-11-26 10:59:59 +0100651
652 if (!config->performance_levels[i].name)
653 return -EINVAL;
654
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500655 return count;
656}
Corentin Chary5dea7a22011-11-26 10:59:59 +0100657
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500658static DEVICE_ATTR(performance_level, S_IWUSR | S_IRUGO,
659 get_performance_level, set_performance_level);
660
Corentin Charycb5b5c92011-11-26 11:00:05 +0100661static int read_battery_life_extender(struct samsung_laptop *samsung)
662{
663 const struct sabi_commands *commands = &samsung->config->commands;
664 struct sabi_data data;
665 int retval;
666
667 if (commands->get_battery_life_extender == 0xFFFF)
668 return -ENODEV;
669
670 memset(&data, 0, sizeof(data));
671 data.data[0] = 0x80;
672 retval = sabi_command(samsung, commands->get_battery_life_extender,
673 &data, &data);
674
675 if (retval)
676 return retval;
677
678 if (data.data[0] != 0 && data.data[0] != 1)
679 return -ENODEV;
680
681 return data.data[0];
682}
683
684static int write_battery_life_extender(struct samsung_laptop *samsung,
685 int enabled)
686{
687 const struct sabi_commands *commands = &samsung->config->commands;
688 struct sabi_data data;
689
690 memset(&data, 0, sizeof(data));
691 data.data[0] = 0x80 | enabled;
692 return sabi_command(samsung, commands->set_battery_life_extender,
693 &data, NULL);
694}
695
696static ssize_t get_battery_life_extender(struct device *dev,
697 struct device_attribute *attr,
698 char *buf)
699{
700 struct samsung_laptop *samsung = dev_get_drvdata(dev);
701 int ret;
702
703 ret = read_battery_life_extender(samsung);
704 if (ret < 0)
705 return ret;
706
707 return sprintf(buf, "%d\n", ret);
708}
709
710static ssize_t set_battery_life_extender(struct device *dev,
711 struct device_attribute *attr,
712 const char *buf, size_t count)
713{
714 struct samsung_laptop *samsung = dev_get_drvdata(dev);
715 int ret, value;
716
717 if (!count || sscanf(buf, "%i", &value) != 1)
718 return -EINVAL;
719
720 ret = write_battery_life_extender(samsung, !!value);
721 if (ret < 0)
722 return ret;
723
724 return count;
725}
726
727static DEVICE_ATTR(battery_life_extender, S_IWUSR | S_IRUGO,
728 get_battery_life_extender, set_battery_life_extender);
729
Corentin Chary3a75d372011-11-26 11:00:06 +0100730static int read_usb_charge(struct samsung_laptop *samsung)
731{
732 const struct sabi_commands *commands = &samsung->config->commands;
733 struct sabi_data data;
734 int retval;
735
736 if (commands->get_usb_charge == 0xFFFF)
737 return -ENODEV;
738
739 memset(&data, 0, sizeof(data));
740 data.data[0] = 0x80;
741 retval = sabi_command(samsung, commands->get_usb_charge,
742 &data, &data);
743
744 if (retval)
745 return retval;
746
747 if (data.data[0] != 0 && data.data[0] != 1)
748 return -ENODEV;
749
750 return data.data[0];
751}
752
753static int write_usb_charge(struct samsung_laptop *samsung,
754 int enabled)
755{
756 const struct sabi_commands *commands = &samsung->config->commands;
757 struct sabi_data data;
758
759 memset(&data, 0, sizeof(data));
760 data.data[0] = 0x80 | enabled;
761 return sabi_command(samsung, commands->set_usb_charge,
762 &data, NULL);
763}
764
765static ssize_t get_usb_charge(struct device *dev,
766 struct device_attribute *attr,
767 char *buf)
768{
769 struct samsung_laptop *samsung = dev_get_drvdata(dev);
770 int ret;
771
772 ret = read_usb_charge(samsung);
773 if (ret < 0)
774 return ret;
775
776 return sprintf(buf, "%d\n", ret);
777}
778
779static ssize_t set_usb_charge(struct device *dev,
780 struct device_attribute *attr,
781 const char *buf, size_t count)
782{
783 struct samsung_laptop *samsung = dev_get_drvdata(dev);
784 int ret, value;
785
786 if (!count || sscanf(buf, "%i", &value) != 1)
787 return -EINVAL;
788
789 ret = write_usb_charge(samsung, !!value);
790 if (ret < 0)
791 return ret;
792
793 return count;
794}
795
796static DEVICE_ATTR(usb_charge, S_IWUSR | S_IRUGO,
797 get_usb_charge, set_usb_charge);
798
Corentin Charya66c1662011-11-26 11:00:01 +0100799static struct attribute *platform_attributes[] = {
800 &dev_attr_performance_level.attr,
Corentin Charycb5b5c92011-11-26 11:00:05 +0100801 &dev_attr_battery_life_extender.attr,
Corentin Chary3a75d372011-11-26 11:00:06 +0100802 &dev_attr_usb_charge.attr,
Corentin Charya66c1662011-11-26 11:00:01 +0100803 NULL
804};
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500805
Corentin Chary5dea7a22011-11-26 10:59:59 +0100806static int find_signature(void __iomem *memcheck, const char *testStr)
807{
808 int i = 0;
809 int loca;
810
811 for (loca = 0; loca < 0xffff; loca++) {
812 char temp = readb(memcheck + loca);
813
814 if (temp == testStr[i]) {
815 if (i == strlen(testStr)-1)
816 break;
817 ++i;
818 } else {
819 i = 0;
820 }
821 }
822 return loca;
823}
824
825static void samsung_rfkill_exit(struct samsung_laptop *samsung)
826{
Corentin Chary84d482f2011-11-26 11:00:09 +0100827 if (samsung->wlan.rfkill) {
828 rfkill_unregister(samsung->wlan.rfkill);
829 rfkill_destroy(samsung->wlan.rfkill);
830 samsung->wlan.rfkill = NULL;
Corentin Chary5dea7a22011-11-26 10:59:59 +0100831 }
Corentin Chary84d482f2011-11-26 11:00:09 +0100832 if (samsung->bluetooth.rfkill) {
833 rfkill_unregister(samsung->bluetooth.rfkill);
834 rfkill_destroy(samsung->bluetooth.rfkill);
835 samsung->bluetooth.rfkill = NULL;
836 }
837}
838
839static int samsung_new_rfkill(struct samsung_laptop *samsung,
840 struct samsung_rfkill *arfkill,
841 const char *name, enum rfkill_type type,
842 const struct rfkill_ops *ops,
843 int blocked)
844{
845 struct rfkill **rfkill = &arfkill->rfkill;
846 int ret;
847
848 arfkill->type = type;
849 arfkill->samsung = samsung;
850
851 *rfkill = rfkill_alloc(name, &samsung->platform_device->dev,
852 type, ops, arfkill);
853
854 if (!*rfkill)
855 return -EINVAL;
856
857 if (blocked != -1)
858 rfkill_init_sw_state(*rfkill, blocked);
859
860 ret = rfkill_register(*rfkill);
861 if (ret) {
862 rfkill_destroy(*rfkill);
863 *rfkill = NULL;
864 return ret;
865 }
866 return 0;
867}
868
869static int __init samsung_rfkill_init_seclinux(struct samsung_laptop *samsung)
870{
871 return samsung_new_rfkill(samsung, &samsung->wlan, "samsung-wlan",
872 RFKILL_TYPE_WLAN, &seclinux_rfkill_ops, -1);
873}
874
875static int __init samsung_rfkill_init_swsmi(struct samsung_laptop *samsung)
876{
877 struct sabi_data data;
878 int ret;
879
880 ret = swsmi_wireless_status(samsung, &data);
881 if (ret)
882 return ret;
883
884 /* 0x02 seems to mean that the device is no present/available */
885
886 if (data.data[WL_STATUS_WLAN] != 0x02)
887 ret = samsung_new_rfkill(samsung, &samsung->wlan,
888 "samsung-wlan",
889 RFKILL_TYPE_WLAN,
890 &swsmi_rfkill_ops,
891 !data.data[WL_STATUS_WLAN]);
892 if (ret)
893 goto exit;
894
895 if (data.data[WL_STATUS_BT] != 0x02)
896 ret = samsung_new_rfkill(samsung, &samsung->bluetooth,
897 "samsung-bluetooth",
898 RFKILL_TYPE_BLUETOOTH,
899 &swsmi_rfkill_ops,
900 !data.data[WL_STATUS_BT]);
901 if (ret)
902 goto exit;
903
904exit:
905 if (ret)
906 samsung_rfkill_exit(samsung);
907
908 return ret;
Corentin Chary5dea7a22011-11-26 10:59:59 +0100909}
910
911static int __init samsung_rfkill_init(struct samsung_laptop *samsung)
912{
Corentin Chary84d482f2011-11-26 11:00:09 +0100913 if (samsung->config->sabi_version == 2)
914 return samsung_rfkill_init_seclinux(samsung);
915 if (samsung->config->sabi_version == 3)
916 return samsung_rfkill_init_swsmi(samsung);
Corentin Chary5dea7a22011-11-26 10:59:59 +0100917 return 0;
918}
919
Corentin Charyf674ebf2011-11-26 11:00:08 +0100920static int kbd_backlight_enable(struct samsung_laptop *samsung)
921{
922 const struct sabi_commands *commands = &samsung->config->commands;
923 struct sabi_data data;
924 int retval;
925
926 if (commands->kbd_backlight == 0xFFFF)
927 return -ENODEV;
928
929 memset(&data, 0, sizeof(data));
930 data.d0 = 0xaabb;
931 retval = sabi_command(samsung, commands->kbd_backlight,
932 &data, &data);
933
934 if (retval)
935 return retval;
936
937 if (data.d0 != 0xccdd)
938 return -ENODEV;
939 return 0;
940}
941
942static int kbd_backlight_read(struct samsung_laptop *samsung)
943{
944 const struct sabi_commands *commands = &samsung->config->commands;
945 struct sabi_data data;
946 int retval;
947
948 memset(&data, 0, sizeof(data));
949 data.data[0] = 0x81;
950 retval = sabi_command(samsung, commands->kbd_backlight,
951 &data, &data);
952
953 if (retval)
954 return retval;
955
956 return data.data[0];
957}
958
959static int kbd_backlight_write(struct samsung_laptop *samsung, int brightness)
960{
961 const struct sabi_commands *commands = &samsung->config->commands;
962 struct sabi_data data;
963
964 memset(&data, 0, sizeof(data));
965 data.d0 = 0x82 | ((brightness & 0xFF) << 8);
966 return sabi_command(samsung, commands->kbd_backlight,
967 &data, NULL);
968}
969
970static void kbd_led_update(struct work_struct *work)
971{
972 struct samsung_laptop *samsung;
973
974 samsung = container_of(work, struct samsung_laptop, kbd_led_work);
975 kbd_backlight_write(samsung, samsung->kbd_led_wk);
976}
977
978static void kbd_led_set(struct led_classdev *led_cdev,
979 enum led_brightness value)
980{
981 struct samsung_laptop *samsung;
982
983 samsung = container_of(led_cdev, struct samsung_laptop, kbd_led);
984
985 if (value > samsung->kbd_led.max_brightness)
986 value = samsung->kbd_led.max_brightness;
987 else if (value < 0)
988 value = 0;
989
990 samsung->kbd_led_wk = value;
991 queue_work(samsung->led_workqueue, &samsung->kbd_led_work);
992}
993
994static enum led_brightness kbd_led_get(struct led_classdev *led_cdev)
995{
996 struct samsung_laptop *samsung;
997
998 samsung = container_of(led_cdev, struct samsung_laptop, kbd_led);
999 return kbd_backlight_read(samsung);
1000}
1001
1002static void samsung_leds_exit(struct samsung_laptop *samsung)
1003{
1004 if (!IS_ERR_OR_NULL(samsung->kbd_led.dev))
1005 led_classdev_unregister(&samsung->kbd_led);
1006 if (samsung->led_workqueue)
1007 destroy_workqueue(samsung->led_workqueue);
1008}
1009
1010static int __init samsung_leds_init(struct samsung_laptop *samsung)
1011{
1012 int ret = 0;
1013
1014 samsung->led_workqueue = create_singlethread_workqueue("led_workqueue");
1015 if (!samsung->led_workqueue)
1016 return -ENOMEM;
1017
1018 if (kbd_backlight_enable(samsung) >= 0) {
1019 INIT_WORK(&samsung->kbd_led_work, kbd_led_update);
1020
1021 samsung->kbd_led.name = "samsung::kbd_backlight";
1022 samsung->kbd_led.brightness_set = kbd_led_set;
1023 samsung->kbd_led.brightness_get = kbd_led_get;
1024 samsung->kbd_led.max_brightness = 8;
1025
1026 ret = led_classdev_register(&samsung->platform_device->dev,
1027 &samsung->kbd_led);
1028 }
1029
1030 if (ret)
1031 samsung_leds_exit(samsung);
1032
1033 return ret;
1034}
1035
Corentin Chary5dea7a22011-11-26 10:59:59 +01001036static void samsung_backlight_exit(struct samsung_laptop *samsung)
1037{
1038 if (samsung->backlight_device) {
1039 backlight_device_unregister(samsung->backlight_device);
1040 samsung->backlight_device = NULL;
1041 }
1042}
1043
1044static int __init samsung_backlight_init(struct samsung_laptop *samsung)
1045{
1046 struct backlight_device *bd;
1047 struct backlight_properties props;
1048
Corentin Charyf34cd9c2011-11-26 11:00:00 +01001049 if (!samsung->handle_backlight)
1050 return 0;
1051
Corentin Chary5dea7a22011-11-26 10:59:59 +01001052 memset(&props, 0, sizeof(struct backlight_properties));
1053 props.type = BACKLIGHT_PLATFORM;
1054 props.max_brightness = samsung->config->max_brightness -
1055 samsung->config->min_brightness;
1056
1057 bd = backlight_device_register("samsung",
1058 &samsung->platform_device->dev,
1059 samsung, &backlight_ops,
1060 &props);
1061 if (IS_ERR(bd))
1062 return PTR_ERR(bd);
1063
1064 samsung->backlight_device = bd;
1065 samsung->backlight_device->props.brightness = read_brightness(samsung);
1066 samsung->backlight_device->props.power = FB_BLANK_UNBLANK;
1067 backlight_update_status(samsung->backlight_device);
1068
1069 return 0;
1070}
1071
Corentin Charya66c1662011-11-26 11:00:01 +01001072static mode_t samsung_sysfs_is_visible(struct kobject *kobj,
1073 struct attribute *attr, int idx)
1074{
1075 struct device *dev = container_of(kobj, struct device, kobj);
1076 struct platform_device *pdev = to_platform_device(dev);
1077 struct samsung_laptop *samsung = platform_get_drvdata(pdev);
1078 bool ok = true;
1079
1080 if (attr == &dev_attr_performance_level.attr)
1081 ok = !!samsung->config->performance_levels[0].name;
Corentin Charycb5b5c92011-11-26 11:00:05 +01001082 if (attr == &dev_attr_battery_life_extender.attr)
1083 ok = !!(read_battery_life_extender(samsung) >= 0);
Corentin Chary3a75d372011-11-26 11:00:06 +01001084 if (attr == &dev_attr_usb_charge.attr)
1085 ok = !!(read_usb_charge(samsung) >= 0);
Corentin Charya66c1662011-11-26 11:00:01 +01001086
1087 return ok ? attr->mode : 0;
1088}
1089
1090static struct attribute_group platform_attribute_group = {
1091 .is_visible = samsung_sysfs_is_visible,
1092 .attrs = platform_attributes
1093};
1094
Corentin Chary5dea7a22011-11-26 10:59:59 +01001095static void samsung_sysfs_exit(struct samsung_laptop *samsung)
1096{
Corentin Charya66c1662011-11-26 11:00:01 +01001097 struct platform_device *device = samsung->platform_device;
1098
1099 sysfs_remove_group(&device->dev.kobj, &platform_attribute_group);
Corentin Chary5dea7a22011-11-26 10:59:59 +01001100}
1101
1102static int __init samsung_sysfs_init(struct samsung_laptop *samsung)
1103{
Corentin Charya66c1662011-11-26 11:00:01 +01001104 struct platform_device *device = samsung->platform_device;
1105
1106 return sysfs_create_group(&device->dev.kobj, &platform_attribute_group);
1107
Corentin Chary5dea7a22011-11-26 10:59:59 +01001108}
1109
Corentin Chary5b80fc42011-11-26 11:00:03 +01001110static int show_call(struct seq_file *m, void *data)
1111{
1112 struct samsung_laptop *samsung = m->private;
1113 struct sabi_data *sdata = &samsung->debug.data;
1114 int ret;
1115
1116 seq_printf(m, "SABI 0x%04x {0x%08x, 0x%08x, 0x%04x, 0x%02x}\n",
1117 samsung->debug.command,
1118 sdata->d0, sdata->d1, sdata->d2, sdata->d3);
1119
1120 ret = sabi_command(samsung, samsung->debug.command, sdata, sdata);
1121
1122 if (ret) {
1123 seq_printf(m, "SABI command 0x%04x failed\n",
1124 samsung->debug.command);
1125 return ret;
1126 }
1127
1128 seq_printf(m, "SABI {0x%08x, 0x%08x, 0x%04x, 0x%02x}\n",
1129 sdata->d0, sdata->d1, sdata->d2, sdata->d3);
1130 return 0;
1131}
1132
1133static int samsung_debugfs_open(struct inode *inode, struct file *file)
1134{
1135 return single_open(file, show_call, inode->i_private);
1136}
1137
1138static const struct file_operations samsung_laptop_call_io_ops = {
1139 .owner = THIS_MODULE,
1140 .open = samsung_debugfs_open,
1141 .read = seq_read,
1142 .llseek = seq_lseek,
1143 .release = single_release,
1144};
1145
1146static void samsung_debugfs_exit(struct samsung_laptop *samsung)
1147{
1148 debugfs_remove_recursive(samsung->debug.root);
1149}
1150
1151static int samsung_debugfs_init(struct samsung_laptop *samsung)
1152{
1153 struct dentry *dent;
1154
1155 samsung->debug.root = debugfs_create_dir("samsung-laptop", NULL);
1156 if (!samsung->debug.root) {
1157 pr_err("failed to create debugfs directory");
1158 goto error_debugfs;
1159 }
1160
1161 samsung->debug.f0000_wrapper.data = samsung->f0000_segment;
1162 samsung->debug.f0000_wrapper.size = 0xffff;
1163
1164 samsung->debug.data_wrapper.data = &samsung->debug.data;
1165 samsung->debug.data_wrapper.size = sizeof(samsung->debug.data);
1166
1167 dent = debugfs_create_u16("command", S_IRUGO | S_IWUSR,
1168 samsung->debug.root, &samsung->debug.command);
1169 if (!dent)
1170 goto error_debugfs;
1171
1172 dent = debugfs_create_u32("d0", S_IRUGO | S_IWUSR, samsung->debug.root,
1173 &samsung->debug.data.d0);
1174 if (!dent)
1175 goto error_debugfs;
1176
1177 dent = debugfs_create_u32("d1", S_IRUGO | S_IWUSR, samsung->debug.root,
1178 &samsung->debug.data.d1);
1179 if (!dent)
1180 goto error_debugfs;
1181
1182 dent = debugfs_create_u16("d2", S_IRUGO | S_IWUSR, samsung->debug.root,
1183 &samsung->debug.data.d2);
1184 if (!dent)
1185 goto error_debugfs;
1186
1187 dent = debugfs_create_u8("d3", S_IRUGO | S_IWUSR, samsung->debug.root,
1188 &samsung->debug.data.d3);
1189 if (!dent)
1190 goto error_debugfs;
1191
1192 dent = debugfs_create_blob("data", S_IRUGO | S_IWUSR,
1193 samsung->debug.root,
1194 &samsung->debug.data_wrapper);
1195 if (!dent)
1196 goto error_debugfs;
1197
1198 dent = debugfs_create_blob("f0000_segment", S_IRUSR | S_IWUSR,
1199 samsung->debug.root,
1200 &samsung->debug.f0000_wrapper);
1201 if (!dent)
1202 goto error_debugfs;
1203
1204 dent = debugfs_create_file("call", S_IFREG | S_IRUGO,
1205 samsung->debug.root, samsung,
1206 &samsung_laptop_call_io_ops);
1207 if (!dent)
1208 goto error_debugfs;
1209
1210 return 0;
1211
1212error_debugfs:
1213 samsung_debugfs_exit(samsung);
1214 return -ENOMEM;
1215}
1216
Corentin Chary5dea7a22011-11-26 10:59:59 +01001217static void samsung_sabi_exit(struct samsung_laptop *samsung)
1218{
1219 const struct sabi_config *config = samsung->config;
1220
1221 /* Turn off "Linux" mode in the BIOS */
1222 if (config && config->commands.set_linux != 0xff)
Corentin Chary7e960712011-11-26 11:00:02 +01001223 sabi_set_commandb(samsung, config->commands.set_linux, 0x80);
Corentin Chary5dea7a22011-11-26 10:59:59 +01001224
1225 if (samsung->sabi_iface) {
1226 iounmap(samsung->sabi_iface);
1227 samsung->sabi_iface = NULL;
1228 }
1229 if (samsung->f0000_segment) {
1230 iounmap(samsung->f0000_segment);
1231 samsung->f0000_segment = NULL;
1232 }
1233
1234 samsung->config = NULL;
1235}
1236
Corentin Chary49dd7732011-11-26 11:00:04 +01001237static __init void samsung_sabi_infos(struct samsung_laptop *samsung, int loca,
1238 unsigned int ifaceP)
Corentin Chary5dea7a22011-11-26 10:59:59 +01001239{
1240 const struct sabi_config *config = samsung->config;
1241
1242 printk(KERN_DEBUG "This computer supports SABI==%x\n",
1243 loca + 0xf0000 - 6);
Corentin Chary49dd7732011-11-26 11:00:04 +01001244
Corentin Chary5dea7a22011-11-26 10:59:59 +01001245 printk(KERN_DEBUG "SABI header:\n");
1246 printk(KERN_DEBUG " SMI Port Number = 0x%04x\n",
1247 readw(samsung->sabi + config->header_offsets.port));
1248 printk(KERN_DEBUG " SMI Interface Function = 0x%02x\n",
1249 readb(samsung->sabi + config->header_offsets.iface_func));
1250 printk(KERN_DEBUG " SMI enable memory buffer = 0x%02x\n",
1251 readb(samsung->sabi + config->header_offsets.en_mem));
1252 printk(KERN_DEBUG " SMI restore memory buffer = 0x%02x\n",
1253 readb(samsung->sabi + config->header_offsets.re_mem));
1254 printk(KERN_DEBUG " SABI data offset = 0x%04x\n",
1255 readw(samsung->sabi + config->header_offsets.data_offset));
1256 printk(KERN_DEBUG " SABI data segment = 0x%04x\n",
1257 readw(samsung->sabi + config->header_offsets.data_segment));
Corentin Chary5dea7a22011-11-26 10:59:59 +01001258
Corentin Chary49dd7732011-11-26 11:00:04 +01001259 printk(KERN_DEBUG " SABI pointer = 0x%08x\n", ifaceP);
Corentin Chary5dea7a22011-11-26 10:59:59 +01001260}
1261
1262static int __init samsung_sabi_init(struct samsung_laptop *samsung)
1263{
1264 const struct sabi_config *config = NULL;
1265 const struct sabi_commands *commands;
1266 unsigned int ifaceP;
1267 int ret = 0;
1268 int i;
1269 int loca;
1270
1271 samsung->f0000_segment = ioremap_nocache(0xf0000, 0xffff);
1272 if (!samsung->f0000_segment) {
Corentin Chary3be324a2011-11-26 11:00:10 +01001273 if (debug || force)
1274 pr_err("Can't map the segment at 0xf0000\n");
Corentin Chary5dea7a22011-11-26 10:59:59 +01001275 ret = -EINVAL;
1276 goto exit;
1277 }
1278
1279 /* Try to find one of the signatures in memory to find the header */
1280 for (i = 0; sabi_configs[i].test_string != 0; ++i) {
1281 samsung->config = &sabi_configs[i];
1282 loca = find_signature(samsung->f0000_segment,
1283 samsung->config->test_string);
1284 if (loca != 0xffff)
1285 break;
1286 }
1287
1288 if (loca == 0xffff) {
Corentin Chary3be324a2011-11-26 11:00:10 +01001289 if (debug || force)
1290 pr_err("This computer does not support SABI\n");
Corentin Chary5dea7a22011-11-26 10:59:59 +01001291 ret = -ENODEV;
1292 goto exit;
1293 }
1294
1295 config = samsung->config;
1296 commands = &config->commands;
1297
1298 /* point to the SMI port Number */
1299 loca += 1;
1300 samsung->sabi = (samsung->f0000_segment + loca);
1301
Corentin Chary5dea7a22011-11-26 10:59:59 +01001302 /* Get a pointer to the SABI Interface */
1303 ifaceP = (readw(samsung->sabi + config->header_offsets.data_segment) & 0x0ffff) << 4;
1304 ifaceP += readw(samsung->sabi + config->header_offsets.data_offset) & 0x0ffff;
Corentin Chary49dd7732011-11-26 11:00:04 +01001305
1306 if (debug)
1307 samsung_sabi_infos(samsung, loca, ifaceP);
1308
Corentin Chary5dea7a22011-11-26 10:59:59 +01001309 samsung->sabi_iface = ioremap_nocache(ifaceP, 16);
1310 if (!samsung->sabi_iface) {
1311 pr_err("Can't remap %x\n", ifaceP);
1312 ret = -EINVAL;
1313 goto exit;
1314 }
1315
Corentin Chary5dea7a22011-11-26 10:59:59 +01001316 /* Turn on "Linux" mode in the BIOS */
1317 if (commands->set_linux != 0xff) {
Corentin Chary7e960712011-11-26 11:00:02 +01001318 int retval = sabi_set_commandb(samsung,
1319 commands->set_linux, 0x81);
Corentin Chary5dea7a22011-11-26 10:59:59 +01001320 if (retval) {
1321 pr_warn("Linux mode was not set!\n");
1322 ret = -ENODEV;
1323 goto exit;
1324 }
1325 }
1326
1327 /* Check for stepping quirk */
Corentin Charyf34cd9c2011-11-26 11:00:00 +01001328 if (samsung->handle_backlight)
1329 check_for_stepping_quirk(samsung);
Corentin Chary5dea7a22011-11-26 10:59:59 +01001330
1331exit:
1332 if (ret)
1333 samsung_sabi_exit(samsung);
1334
1335 return ret;
1336}
1337
1338static void samsung_platform_exit(struct samsung_laptop *samsung)
1339{
1340 if (samsung->platform_device) {
1341 platform_device_unregister(samsung->platform_device);
1342 samsung->platform_device = NULL;
1343 }
1344}
1345
1346static int __init samsung_platform_init(struct samsung_laptop *samsung)
1347{
1348 struct platform_device *pdev;
1349
1350 pdev = platform_device_register_simple("samsung", -1, NULL, 0);
1351 if (IS_ERR(pdev))
1352 return PTR_ERR(pdev);
1353
1354 samsung->platform_device = pdev;
1355 platform_set_drvdata(samsung->platform_device, samsung);
1356 return 0;
1357}
1358
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001359static struct dmi_system_id __initdata samsung_dmi_table[] = {
1360 {
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001361 .matches = {
1362 DMI_MATCH(DMI_SYS_VENDOR,
1363 "SAMSUNG ELECTRONICS CO., LTD."),
Corentin Chary3be324a2011-11-26 11:00:10 +01001364 DMI_MATCH(DMI_CHASSIS_TYPE, "8"), /* Portable */
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001365 },
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001366 },
1367 {
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001368 .matches = {
1369 DMI_MATCH(DMI_SYS_VENDOR,
1370 "SAMSUNG ELECTRONICS CO., LTD."),
Corentin Chary3be324a2011-11-26 11:00:10 +01001371 DMI_MATCH(DMI_CHASSIS_TYPE, "9"), /* Laptop */
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001372 },
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001373 },
1374 {
J Witteveen4e2441c2011-07-03 13:15:44 +02001375 .matches = {
1376 DMI_MATCH(DMI_SYS_VENDOR,
1377 "SAMSUNG ELECTRONICS CO., LTD."),
Corentin Chary3be324a2011-11-26 11:00:10 +01001378 DMI_MATCH(DMI_CHASSIS_TYPE, "10"), /* Notebook */
J Witteveen4e2441c2011-07-03 13:15:44 +02001379 },
J Witteveen4e2441c2011-07-03 13:15:44 +02001380 },
1381 {
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001382 .matches = {
1383 DMI_MATCH(DMI_SYS_VENDOR,
1384 "SAMSUNG ELECTRONICS CO., LTD."),
Corentin Chary3be324a2011-11-26 11:00:10 +01001385 DMI_MATCH(DMI_CHASSIS_TYPE, "14"), /* Sub-Notebook */
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001386 },
Tommaso Massimi7500eeb2011-09-20 09:16:09 -07001387 },
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001388 { },
1389};
1390MODULE_DEVICE_TABLE(dmi, samsung_dmi_table);
1391
Corentin Chary5dea7a22011-11-26 10:59:59 +01001392static struct platform_device *samsung_platform_device;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001393
1394static int __init samsung_init(void)
1395{
Corentin Chary5dea7a22011-11-26 10:59:59 +01001396 struct samsung_laptop *samsung;
1397 int ret;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001398
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001399 if (!force && !dmi_check_system(samsung_dmi_table))
1400 return -ENODEV;
1401
Corentin Charya6df4892011-11-26 10:59:58 +01001402 samsung = kzalloc(sizeof(*samsung), GFP_KERNEL);
1403 if (!samsung)
1404 return -ENOMEM;
1405
1406 mutex_init(&samsung->sabi_mutex);
Corentin Charyf34cd9c2011-11-26 11:00:00 +01001407 samsung->handle_backlight = true;
1408
1409#ifdef CONFIG_ACPI
1410 /* Don't handle backlight here if the acpi video already handle it */
Corentin Chary3be324a2011-11-26 11:00:10 +01001411 if (acpi_video_backlight_support())
Corentin Charyf34cd9c2011-11-26 11:00:00 +01001412 samsung->handle_backlight = false;
Corentin Charyf34cd9c2011-11-26 11:00:00 +01001413#endif
Corentin Chary5dea7a22011-11-26 10:59:59 +01001414 ret = samsung_platform_init(samsung);
1415 if (ret)
1416 goto error_platform;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001417
Corentin Chary5dea7a22011-11-26 10:59:59 +01001418 ret = samsung_sabi_init(samsung);
1419 if (ret)
1420 goto error_sabi;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001421
Corentin Chary3be324a2011-11-26 11:00:10 +01001422#ifdef CONFIG_ACPI
1423 /* Only log that if we are really on a sabi platform */
1424 if (acpi_video_backlight_support())
1425 pr_info("Backlight controlled by ACPI video driver\n");
1426#endif
1427
Corentin Chary5dea7a22011-11-26 10:59:59 +01001428 ret = samsung_sysfs_init(samsung);
1429 if (ret)
1430 goto error_sysfs;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001431
Corentin Chary5dea7a22011-11-26 10:59:59 +01001432 ret = samsung_backlight_init(samsung);
1433 if (ret)
1434 goto error_backlight;
Corentin Charya6df4892011-11-26 10:59:58 +01001435
Corentin Chary5dea7a22011-11-26 10:59:59 +01001436 ret = samsung_rfkill_init(samsung);
1437 if (ret)
1438 goto error_rfkill;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001439
Corentin Charyf674ebf2011-11-26 11:00:08 +01001440 ret = samsung_leds_init(samsung);
1441 if (ret)
1442 goto error_leds;
1443
Corentin Chary5b80fc42011-11-26 11:00:03 +01001444 ret = samsung_debugfs_init(samsung);
1445 if (ret)
1446 goto error_debugfs;
1447
Corentin Chary5dea7a22011-11-26 10:59:59 +01001448 samsung_platform_device = samsung->platform_device;
1449 return ret;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001450
Corentin Chary5b80fc42011-11-26 11:00:03 +01001451error_debugfs:
Corentin Charyf674ebf2011-11-26 11:00:08 +01001452 samsung_leds_exit(samsung);
1453error_leds:
Corentin Chary5b80fc42011-11-26 11:00:03 +01001454 samsung_rfkill_exit(samsung);
Corentin Chary5dea7a22011-11-26 10:59:59 +01001455error_rfkill:
1456 samsung_backlight_exit(samsung);
1457error_backlight:
1458 samsung_sysfs_exit(samsung);
1459error_sysfs:
1460 samsung_sabi_exit(samsung);
1461error_sabi:
1462 samsung_platform_exit(samsung);
1463error_platform:
Corentin Charya6df4892011-11-26 10:59:58 +01001464 kfree(samsung);
Corentin Chary5dea7a22011-11-26 10:59:59 +01001465 return ret;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001466}
1467
1468static void __exit samsung_exit(void)
1469{
Corentin Chary5dea7a22011-11-26 10:59:59 +01001470 struct samsung_laptop *samsung;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001471
Corentin Chary5dea7a22011-11-26 10:59:59 +01001472 samsung = platform_get_drvdata(samsung_platform_device);
Corentin Charya6df4892011-11-26 10:59:58 +01001473
Corentin Chary5b80fc42011-11-26 11:00:03 +01001474 samsung_debugfs_exit(samsung);
Corentin Charyf674ebf2011-11-26 11:00:08 +01001475 samsung_leds_exit(samsung);
Corentin Chary5dea7a22011-11-26 10:59:59 +01001476 samsung_rfkill_exit(samsung);
1477 samsung_backlight_exit(samsung);
1478 samsung_sysfs_exit(samsung);
1479 samsung_sabi_exit(samsung);
1480 samsung_platform_exit(samsung);
1481
Corentin Charya6df4892011-11-26 10:59:58 +01001482 kfree(samsung);
Corentin Chary5dea7a22011-11-26 10:59:59 +01001483 samsung_platform_device = NULL;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001484}
1485
1486module_init(samsung_init);
1487module_exit(samsung_exit);
1488
1489MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@suse.de>");
1490MODULE_DESCRIPTION("Samsung Backlight driver");
1491MODULE_LICENSE("GPL");