blob: 1dd81d148cb7351c2029bdf38d2f5367c286cfd8 [file] [log] [blame]
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001/*
2 * Samsung Laptop driver
3 *
4 * Copyright (C) 2009,2011 Greg Kroah-Hartman (gregkh@suse.de)
5 * Copyright (C) 2009,2011 Novell Inc.
6 *
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License version 2 as published by
9 * the Free Software Foundation.
10 *
11 */
12#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
13
14#include <linux/kernel.h>
15#include <linux/init.h>
16#include <linux/module.h>
17#include <linux/delay.h>
18#include <linux/pci.h>
19#include <linux/backlight.h>
20#include <linux/fb.h>
21#include <linux/dmi.h>
22#include <linux/platform_device.h>
23#include <linux/rfkill.h>
24
25/*
26 * This driver is needed because a number of Samsung laptops do not hook
27 * their control settings through ACPI. So we have to poke around in the
28 * BIOS to do things like brightness values, and "special" key controls.
29 */
30
31/*
32 * We have 0 - 8 as valid brightness levels. The specs say that level 0 should
33 * be reserved by the BIOS (which really doesn't make much sense), we tell
34 * userspace that the value is 0 - 7 and then just tell the hardware 1 - 8
35 */
36#define MAX_BRIGHT 0x07
37
38
39#define SABI_IFACE_MAIN 0x00
40#define SABI_IFACE_SUB 0x02
41#define SABI_IFACE_COMPLETE 0x04
42#define SABI_IFACE_DATA 0x05
43
44/* Structure to get data back to the calling function */
45struct sabi_retval {
46 u8 retval[20];
47};
48
49struct sabi_header_offsets {
50 u8 port;
51 u8 re_mem;
52 u8 iface_func;
53 u8 en_mem;
54 u8 data_offset;
55 u8 data_segment;
56};
57
58struct sabi_commands {
59 /*
60 * Brightness is 0 - 8, as described above.
61 * Value 0 is for the BIOS to use
62 */
63 u8 get_brightness;
64 u8 set_brightness;
65
66 /*
67 * first byte:
68 * 0x00 - wireless is off
69 * 0x01 - wireless is on
70 * second byte:
71 * 0x02 - 3G is off
72 * 0x03 - 3G is on
73 * TODO, verify 3G is correct, that doesn't seem right...
74 */
75 u8 get_wireless_button;
76 u8 set_wireless_button;
77
78 /* 0 is off, 1 is on */
79 u8 get_backlight;
80 u8 set_backlight;
81
82 /*
83 * 0x80 or 0x00 - no action
84 * 0x81 - recovery key pressed
85 */
86 u8 get_recovery_mode;
87 u8 set_recovery_mode;
88
89 /*
90 * on seclinux: 0 is low, 1 is high,
91 * on swsmi: 0 is normal, 1 is silent, 2 is turbo
92 */
93 u8 get_performance_level;
94 u8 set_performance_level;
95
96 /*
97 * Tell the BIOS that Linux is running on this machine.
98 * 81 is on, 80 is off
99 */
100 u8 set_linux;
101};
102
103struct sabi_performance_level {
104 const char *name;
105 u8 value;
106};
107
108struct sabi_config {
109 const char *test_string;
110 u16 main_function;
111 const struct sabi_header_offsets header_offsets;
112 const struct sabi_commands commands;
113 const struct sabi_performance_level performance_levels[4];
114 u8 min_brightness;
115 u8 max_brightness;
116};
117
118static const struct sabi_config sabi_configs[] = {
119 {
120 .test_string = "SECLINUX",
121
122 .main_function = 0x4c49,
123
124 .header_offsets = {
125 .port = 0x00,
126 .re_mem = 0x02,
127 .iface_func = 0x03,
128 .en_mem = 0x04,
129 .data_offset = 0x05,
130 .data_segment = 0x07,
131 },
132
133 .commands = {
134 .get_brightness = 0x00,
135 .set_brightness = 0x01,
136
137 .get_wireless_button = 0x02,
138 .set_wireless_button = 0x03,
139
140 .get_backlight = 0x04,
141 .set_backlight = 0x05,
142
143 .get_recovery_mode = 0x06,
144 .set_recovery_mode = 0x07,
145
146 .get_performance_level = 0x08,
147 .set_performance_level = 0x09,
148
149 .set_linux = 0x0a,
150 },
151
152 .performance_levels = {
153 {
154 .name = "silent",
155 .value = 0,
156 },
157 {
158 .name = "normal",
159 .value = 1,
160 },
161 { },
162 },
163 .min_brightness = 1,
164 .max_brightness = 8,
165 },
166 {
167 .test_string = "SwSmi@",
168
169 .main_function = 0x5843,
170
171 .header_offsets = {
172 .port = 0x00,
173 .re_mem = 0x04,
174 .iface_func = 0x02,
175 .en_mem = 0x03,
176 .data_offset = 0x05,
177 .data_segment = 0x07,
178 },
179
180 .commands = {
181 .get_brightness = 0x10,
182 .set_brightness = 0x11,
183
184 .get_wireless_button = 0x12,
185 .set_wireless_button = 0x13,
186
187 .get_backlight = 0x2d,
188 .set_backlight = 0x2e,
189
190 .get_recovery_mode = 0xff,
191 .set_recovery_mode = 0xff,
192
193 .get_performance_level = 0x31,
194 .set_performance_level = 0x32,
195
196 .set_linux = 0xff,
197 },
198
199 .performance_levels = {
200 {
201 .name = "normal",
202 .value = 0,
203 },
204 {
205 .name = "silent",
206 .value = 1,
207 },
208 {
209 .name = "overclock",
210 .value = 2,
211 },
212 { },
213 },
214 .min_brightness = 0,
215 .max_brightness = 8,
216 },
217 { },
218};
219
Corentin Charya6df4892011-11-26 10:59:58 +0100220struct samsung_laptop {
221 const struct sabi_config *config;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500222
Corentin Charya6df4892011-11-26 10:59:58 +0100223 void __iomem *sabi;
224 void __iomem *sabi_iface;
225 void __iomem *f0000_segment;
226
227 struct mutex sabi_mutex;
228
Corentin Chary5dea7a22011-11-26 10:59:59 +0100229 struct platform_device *platform_device;
Corentin Charya6df4892011-11-26 10:59:58 +0100230 struct backlight_device *backlight_device;
231 struct rfkill *rfk;
232
233 bool has_stepping_quirk;
234};
235
Corentin Chary5dea7a22011-11-26 10:59:59 +0100236
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500237
Rusty Russell90ab5ee2012-01-13 09:32:20 +1030238static bool force;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500239module_param(force, bool, 0);
240MODULE_PARM_DESC(force,
241 "Disable the DMI check and forces the driver to be loaded");
242
Rusty Russell90ab5ee2012-01-13 09:32:20 +1030243static bool debug;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500244module_param(debug, bool, S_IRUGO | S_IWUSR);
245MODULE_PARM_DESC(debug, "Debug enabled or not");
246
Corentin Chary5dea7a22011-11-26 10:59:59 +0100247static int sabi_get_command(struct samsung_laptop *samsung,
248 u8 command, struct sabi_retval *sretval)
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500249{
Corentin Charya6df4892011-11-26 10:59:58 +0100250 const struct sabi_config *config = samsung->config;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500251 int retval = 0;
Corentin Charya6df4892011-11-26 10:59:58 +0100252 u16 port = readw(samsung->sabi + config->header_offsets.port);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500253 u8 complete, iface_data;
254
Corentin Charya6df4892011-11-26 10:59:58 +0100255 mutex_lock(&samsung->sabi_mutex);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500256
257 /* enable memory to be able to write to it */
Corentin Charya6df4892011-11-26 10:59:58 +0100258 outb(readb(samsung->sabi + config->header_offsets.en_mem), port);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500259
260 /* write out the command */
Corentin Charya6df4892011-11-26 10:59:58 +0100261 writew(config->main_function, samsung->sabi_iface + SABI_IFACE_MAIN);
262 writew(command, samsung->sabi_iface + SABI_IFACE_SUB);
263 writeb(0, samsung->sabi_iface + SABI_IFACE_COMPLETE);
264 outb(readb(samsung->sabi + config->header_offsets.iface_func), port);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500265
266 /* write protect memory to make it safe */
Corentin Charya6df4892011-11-26 10:59:58 +0100267 outb(readb(samsung->sabi + config->header_offsets.re_mem), port);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500268
269 /* see if the command actually succeeded */
Corentin Charya6df4892011-11-26 10:59:58 +0100270 complete = readb(samsung->sabi_iface + SABI_IFACE_COMPLETE);
271 iface_data = readb(samsung->sabi_iface + SABI_IFACE_DATA);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500272 if (complete != 0xaa || iface_data == 0xff) {
273 pr_warn("SABI get command 0x%02x failed with completion flag 0x%02x and data 0x%02x\n",
274 command, complete, iface_data);
275 retval = -EINVAL;
276 goto exit;
277 }
278 /*
279 * Save off the data into a structure so the caller use it.
280 * Right now we only want the first 4 bytes,
281 * There are commands that need more, but not for the ones we
282 * currently care about.
283 */
Corentin Charya6df4892011-11-26 10:59:58 +0100284 sretval->retval[0] = readb(samsung->sabi_iface + SABI_IFACE_DATA);
285 sretval->retval[1] = readb(samsung->sabi_iface + SABI_IFACE_DATA + 1);
286 sretval->retval[2] = readb(samsung->sabi_iface + SABI_IFACE_DATA + 2);
287 sretval->retval[3] = readb(samsung->sabi_iface + SABI_IFACE_DATA + 3);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500288
289exit:
Corentin Charya6df4892011-11-26 10:59:58 +0100290 mutex_unlock(&samsung->sabi_mutex);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500291 return retval;
292
293}
294
Corentin Chary5dea7a22011-11-26 10:59:59 +0100295static int sabi_set_command(struct samsung_laptop *samsung,
296 u8 command, u8 data)
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500297{
Corentin Charya6df4892011-11-26 10:59:58 +0100298 const struct sabi_config *config = samsung->config;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500299 int retval = 0;
Corentin Charya6df4892011-11-26 10:59:58 +0100300 u16 port = readw(samsung->sabi + config->header_offsets.port);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500301 u8 complete, iface_data;
302
Corentin Charya6df4892011-11-26 10:59:58 +0100303 mutex_lock(&samsung->sabi_mutex);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500304
305 /* enable memory to be able to write to it */
Corentin Charya6df4892011-11-26 10:59:58 +0100306 outb(readb(samsung->sabi + config->header_offsets.en_mem), port);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500307
308 /* write out the command */
Corentin Charya6df4892011-11-26 10:59:58 +0100309 writew(config->main_function, samsung->sabi_iface + SABI_IFACE_MAIN);
310 writew(command, samsung->sabi_iface + SABI_IFACE_SUB);
311 writeb(0, samsung->sabi_iface + SABI_IFACE_COMPLETE);
312 writeb(data, samsung->sabi_iface + SABI_IFACE_DATA);
313 outb(readb(samsung->sabi + config->header_offsets.iface_func), port);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500314
315 /* write protect memory to make it safe */
Corentin Charya6df4892011-11-26 10:59:58 +0100316 outb(readb(samsung->sabi + config->header_offsets.re_mem), port);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500317
318 /* see if the command actually succeeded */
Corentin Charya6df4892011-11-26 10:59:58 +0100319 complete = readb(samsung->sabi_iface + SABI_IFACE_COMPLETE);
320 iface_data = readb(samsung->sabi_iface + SABI_IFACE_DATA);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500321 if (complete != 0xaa || iface_data == 0xff) {
322 pr_warn("SABI set command 0x%02x failed with completion flag 0x%02x and data 0x%02x\n",
323 command, complete, iface_data);
324 retval = -EINVAL;
325 }
326
Corentin Charya6df4892011-11-26 10:59:58 +0100327 mutex_unlock(&samsung->sabi_mutex);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500328 return retval;
329}
330
Corentin Chary5dea7a22011-11-26 10:59:59 +0100331static void test_backlight(struct samsung_laptop *samsung)
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500332{
Corentin Charya6df4892011-11-26 10:59:58 +0100333 const struct sabi_commands *commands = &samsung->config->commands;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500334 struct sabi_retval sretval;
335
Corentin Chary5dea7a22011-11-26 10:59:59 +0100336 sabi_get_command(samsung, commands->get_backlight, &sretval);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500337 printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]);
338
Corentin Chary5dea7a22011-11-26 10:59:59 +0100339 sabi_set_command(samsung, commands->set_backlight, 0);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500340 printk(KERN_DEBUG "backlight should be off\n");
341
Corentin Chary5dea7a22011-11-26 10:59:59 +0100342 sabi_get_command(samsung, commands->get_backlight, &sretval);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500343 printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]);
344
345 msleep(1000);
346
Corentin Chary5dea7a22011-11-26 10:59:59 +0100347 sabi_set_command(samsung, commands->set_backlight, 1);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500348 printk(KERN_DEBUG "backlight should be on\n");
349
Corentin Chary5dea7a22011-11-26 10:59:59 +0100350 sabi_get_command(samsung, commands->get_backlight, &sretval);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500351 printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]);
352}
353
Corentin Chary5dea7a22011-11-26 10:59:59 +0100354static void test_wireless(struct samsung_laptop *samsung)
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500355{
Corentin Charya6df4892011-11-26 10:59:58 +0100356 const struct sabi_commands *commands = &samsung->config->commands;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500357 struct sabi_retval sretval;
358
Corentin Chary5dea7a22011-11-26 10:59:59 +0100359 sabi_get_command(samsung, commands->get_wireless_button, &sretval);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500360 printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]);
361
Corentin Chary5dea7a22011-11-26 10:59:59 +0100362 sabi_set_command(samsung, commands->set_wireless_button, 0);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500363 printk(KERN_DEBUG "wireless led should be off\n");
364
Corentin Chary5dea7a22011-11-26 10:59:59 +0100365 sabi_get_command(samsung, commands->get_wireless_button, &sretval);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500366 printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]);
367
368 msleep(1000);
369
Corentin Chary5dea7a22011-11-26 10:59:59 +0100370 sabi_set_command(samsung, commands->set_wireless_button, 1);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500371 printk(KERN_DEBUG "wireless led should be on\n");
372
Corentin Chary5dea7a22011-11-26 10:59:59 +0100373 sabi_get_command(samsung, commands->get_wireless_button, &sretval);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500374 printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]);
375}
376
Corentin Chary5dea7a22011-11-26 10:59:59 +0100377static int read_brightness(struct samsung_laptop *samsung)
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500378{
Corentin Charya6df4892011-11-26 10:59:58 +0100379 const struct sabi_config *config = samsung->config;
380 const struct sabi_commands *commands = &samsung->config->commands;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500381 struct sabi_retval sretval;
382 int user_brightness = 0;
383 int retval;
384
Corentin Chary5dea7a22011-11-26 10:59:59 +0100385 retval = sabi_get_command(samsung, commands->get_brightness,
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500386 &sretval);
387 if (!retval) {
388 user_brightness = sretval.retval[0];
Corentin Charya6df4892011-11-26 10:59:58 +0100389 if (user_brightness > config->min_brightness)
390 user_brightness -= config->min_brightness;
Jason Stubbsbee460b2011-09-20 09:16:11 -0700391 else
392 user_brightness = 0;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500393 }
394 return user_brightness;
395}
396
Corentin Chary5dea7a22011-11-26 10:59:59 +0100397static void set_brightness(struct samsung_laptop *samsung, u8 user_brightness)
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500398{
Corentin Charya6df4892011-11-26 10:59:58 +0100399 const struct sabi_config *config = samsung->config;
400 const struct sabi_commands *commands = &samsung->config->commands;
401 u8 user_level = user_brightness + config->min_brightness;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500402
Corentin Charya6df4892011-11-26 10:59:58 +0100403 if (samsung->has_stepping_quirk && user_level != 0) {
Jason Stubbsac080522011-09-20 09:16:13 -0700404 /*
405 * short circuit if the specified level is what's already set
406 * to prevent the screen from flickering needlessly
407 */
Corentin Chary5dea7a22011-11-26 10:59:59 +0100408 if (user_brightness == read_brightness(samsung))
Jason Stubbsac080522011-09-20 09:16:13 -0700409 return;
410
Corentin Chary5dea7a22011-11-26 10:59:59 +0100411 sabi_set_command(samsung, commands->set_brightness, 0);
Jason Stubbsac080522011-09-20 09:16:13 -0700412 }
413
Corentin Chary5dea7a22011-11-26 10:59:59 +0100414 sabi_set_command(samsung, commands->set_brightness, user_level);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500415}
416
417static int get_brightness(struct backlight_device *bd)
418{
Corentin Chary5dea7a22011-11-26 10:59:59 +0100419 struct samsung_laptop *samsung = bl_get_data(bd);
420
421 return read_brightness(samsung);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500422}
423
Corentin Chary5dea7a22011-11-26 10:59:59 +0100424static void check_for_stepping_quirk(struct samsung_laptop *samsung)
Jason Stubbsac080522011-09-20 09:16:13 -0700425{
Corentin Chary5dea7a22011-11-26 10:59:59 +0100426 int initial_level;
427 int check_level;
428 int orig_level = read_brightness(samsung);
Jason Stubbsac080522011-09-20 09:16:13 -0700429
430 /*
431 * Some laptops exhibit the strange behaviour of stepping toward
432 * (rather than setting) the brightness except when changing to/from
433 * brightness level 0. This behaviour is checked for here and worked
434 * around in set_brightness.
435 */
436
John Serockba05b232011-10-13 06:42:01 -0400437 if (orig_level == 0)
Corentin Chary5dea7a22011-11-26 10:59:59 +0100438 set_brightness(samsung, 1);
John Serockba05b232011-10-13 06:42:01 -0400439
Corentin Chary5dea7a22011-11-26 10:59:59 +0100440 initial_level = read_brightness(samsung);
John Serockba05b232011-10-13 06:42:01 -0400441
Jason Stubbsac080522011-09-20 09:16:13 -0700442 if (initial_level <= 2)
443 check_level = initial_level + 2;
444 else
445 check_level = initial_level - 2;
446
Corentin Charya6df4892011-11-26 10:59:58 +0100447 samsung->has_stepping_quirk = false;
Corentin Chary5dea7a22011-11-26 10:59:59 +0100448 set_brightness(samsung, check_level);
Jason Stubbsac080522011-09-20 09:16:13 -0700449
Corentin Chary5dea7a22011-11-26 10:59:59 +0100450 if (read_brightness(samsung) != check_level) {
Corentin Charya6df4892011-11-26 10:59:58 +0100451 samsung->has_stepping_quirk = true;
Jason Stubbsac080522011-09-20 09:16:13 -0700452 pr_info("enabled workaround for brightness stepping quirk\n");
453 }
454
Corentin Chary5dea7a22011-11-26 10:59:59 +0100455 set_brightness(samsung, orig_level);
Jason Stubbsac080522011-09-20 09:16:13 -0700456}
457
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500458static int update_status(struct backlight_device *bd)
459{
Corentin Chary5dea7a22011-11-26 10:59:59 +0100460 struct samsung_laptop *samsung = bl_get_data(bd);
Corentin Charya6df4892011-11-26 10:59:58 +0100461 const struct sabi_commands *commands = &samsung->config->commands;
462
Corentin Chary5dea7a22011-11-26 10:59:59 +0100463 set_brightness(samsung, bd->props.brightness);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500464
465 if (bd->props.power == FB_BLANK_UNBLANK)
Corentin Chary5dea7a22011-11-26 10:59:59 +0100466 sabi_set_command(samsung, commands->set_backlight, 1);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500467 else
Corentin Chary5dea7a22011-11-26 10:59:59 +0100468 sabi_set_command(samsung, commands->set_backlight, 0);
469
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500470 return 0;
471}
472
473static const struct backlight_ops backlight_ops = {
474 .get_brightness = get_brightness,
475 .update_status = update_status,
476};
477
478static int rfkill_set(void *data, bool blocked)
479{
Corentin Chary5dea7a22011-11-26 10:59:59 +0100480 struct samsung_laptop *samsung = data;
Corentin Charya6df4892011-11-26 10:59:58 +0100481 const struct sabi_commands *commands = &samsung->config->commands;
482
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500483 /* Do something with blocked...*/
484 /*
485 * blocked == false is on
486 * blocked == true is off
487 */
488 if (blocked)
Corentin Chary5dea7a22011-11-26 10:59:59 +0100489 sabi_set_command(samsung, commands->set_wireless_button, 0);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500490 else
Corentin Chary5dea7a22011-11-26 10:59:59 +0100491 sabi_set_command(samsung, commands->set_wireless_button, 1);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500492
493 return 0;
494}
495
496static struct rfkill_ops rfkill_ops = {
497 .set_block = rfkill_set,
498};
499
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500500static ssize_t get_performance_level(struct device *dev,
501 struct device_attribute *attr, char *buf)
502{
Corentin Chary5dea7a22011-11-26 10:59:59 +0100503 struct samsung_laptop *samsung = dev_get_drvdata(dev);
Corentin Charya6df4892011-11-26 10:59:58 +0100504 const struct sabi_config *config = samsung->config;
Corentin Chary5dea7a22011-11-26 10:59:59 +0100505 const struct sabi_commands *commands = &config->commands;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500506 struct sabi_retval sretval;
507 int retval;
508 int i;
509
510 /* Read the state */
Corentin Chary5dea7a22011-11-26 10:59:59 +0100511 retval = sabi_get_command(samsung, commands->get_performance_level,
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500512 &sretval);
513 if (retval)
514 return retval;
515
516 /* The logic is backwards, yeah, lots of fun... */
Corentin Charya6df4892011-11-26 10:59:58 +0100517 for (i = 0; config->performance_levels[i].name; ++i) {
518 if (sretval.retval[0] == config->performance_levels[i].value)
519 return sprintf(buf, "%s\n", config->performance_levels[i].name);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500520 }
521 return sprintf(buf, "%s\n", "unknown");
522}
523
524static ssize_t set_performance_level(struct device *dev,
525 struct device_attribute *attr, const char *buf,
526 size_t count)
527{
Corentin Chary5dea7a22011-11-26 10:59:59 +0100528 struct samsung_laptop *samsung = dev_get_drvdata(dev);
Corentin Charya6df4892011-11-26 10:59:58 +0100529 const struct sabi_config *config = samsung->config;
Corentin Chary5dea7a22011-11-26 10:59:59 +0100530 const struct sabi_commands *commands = &config->commands;
531 int i;
Corentin Charya6df4892011-11-26 10:59:58 +0100532
Corentin Chary5dea7a22011-11-26 10:59:59 +0100533 if (count < 1)
534 return count;
535
536 for (i = 0; config->performance_levels[i].name; ++i) {
537 const struct sabi_performance_level *level =
538 &config->performance_levels[i];
539 if (!strncasecmp(level->name, buf, strlen(level->name))) {
540 sabi_set_command(samsung,
541 commands->set_performance_level,
542 level->value);
543 break;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500544 }
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500545 }
Corentin Chary5dea7a22011-11-26 10:59:59 +0100546
547 if (!config->performance_levels[i].name)
548 return -EINVAL;
549
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500550 return count;
551}
Corentin Chary5dea7a22011-11-26 10:59:59 +0100552
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500553static DEVICE_ATTR(performance_level, S_IWUSR | S_IRUGO,
554 get_performance_level, set_performance_level);
555
556
Corentin Chary5dea7a22011-11-26 10:59:59 +0100557static int find_signature(void __iomem *memcheck, const char *testStr)
558{
559 int i = 0;
560 int loca;
561
562 for (loca = 0; loca < 0xffff; loca++) {
563 char temp = readb(memcheck + loca);
564
565 if (temp == testStr[i]) {
566 if (i == strlen(testStr)-1)
567 break;
568 ++i;
569 } else {
570 i = 0;
571 }
572 }
573 return loca;
574}
575
576static void samsung_rfkill_exit(struct samsung_laptop *samsung)
577{
578 if (samsung->rfk) {
579 rfkill_unregister(samsung->rfk);
580 rfkill_destroy(samsung->rfk);
581 samsung->rfk = NULL;
582 }
583}
584
585static int __init samsung_rfkill_init(struct samsung_laptop *samsung)
586{
587 int retval;
588
589 samsung->rfk = rfkill_alloc("samsung-wifi",
590 &samsung->platform_device->dev,
591 RFKILL_TYPE_WLAN,
592 &rfkill_ops, samsung);
593 if (!samsung->rfk)
594 return -ENOMEM;
595
596 retval = rfkill_register(samsung->rfk);
597 if (retval) {
598 rfkill_destroy(samsung->rfk);
599 samsung->rfk = NULL;
600 return -ENODEV;
601 }
602
603 return 0;
604}
605
606static void samsung_backlight_exit(struct samsung_laptop *samsung)
607{
608 if (samsung->backlight_device) {
609 backlight_device_unregister(samsung->backlight_device);
610 samsung->backlight_device = NULL;
611 }
612}
613
614static int __init samsung_backlight_init(struct samsung_laptop *samsung)
615{
616 struct backlight_device *bd;
617 struct backlight_properties props;
618
619 memset(&props, 0, sizeof(struct backlight_properties));
620 props.type = BACKLIGHT_PLATFORM;
621 props.max_brightness = samsung->config->max_brightness -
622 samsung->config->min_brightness;
623
624 bd = backlight_device_register("samsung",
625 &samsung->platform_device->dev,
626 samsung, &backlight_ops,
627 &props);
628 if (IS_ERR(bd))
629 return PTR_ERR(bd);
630
631 samsung->backlight_device = bd;
632 samsung->backlight_device->props.brightness = read_brightness(samsung);
633 samsung->backlight_device->props.power = FB_BLANK_UNBLANK;
634 backlight_update_status(samsung->backlight_device);
635
636 return 0;
637}
638
639static void samsung_sysfs_exit(struct samsung_laptop *samsung)
640{
641 device_remove_file(&samsung->platform_device->dev,
642 &dev_attr_performance_level);
643}
644
645static int __init samsung_sysfs_init(struct samsung_laptop *samsung)
646{
647 return device_create_file(&samsung->platform_device->dev,
648 &dev_attr_performance_level);
649}
650
651static void samsung_sabi_exit(struct samsung_laptop *samsung)
652{
653 const struct sabi_config *config = samsung->config;
654
655 /* Turn off "Linux" mode in the BIOS */
656 if (config && config->commands.set_linux != 0xff)
657 sabi_set_command(samsung, config->commands.set_linux, 0x80);
658
659 if (samsung->sabi_iface) {
660 iounmap(samsung->sabi_iface);
661 samsung->sabi_iface = NULL;
662 }
663 if (samsung->f0000_segment) {
664 iounmap(samsung->f0000_segment);
665 samsung->f0000_segment = NULL;
666 }
667
668 samsung->config = NULL;
669}
670
671static __init void samsung_sabi_infos(struct samsung_laptop *samsung, int loca)
672{
673 const struct sabi_config *config = samsung->config;
674
675 printk(KERN_DEBUG "This computer supports SABI==%x\n",
676 loca + 0xf0000 - 6);
677 printk(KERN_DEBUG "SABI header:\n");
678 printk(KERN_DEBUG " SMI Port Number = 0x%04x\n",
679 readw(samsung->sabi + config->header_offsets.port));
680 printk(KERN_DEBUG " SMI Interface Function = 0x%02x\n",
681 readb(samsung->sabi + config->header_offsets.iface_func));
682 printk(KERN_DEBUG " SMI enable memory buffer = 0x%02x\n",
683 readb(samsung->sabi + config->header_offsets.en_mem));
684 printk(KERN_DEBUG " SMI restore memory buffer = 0x%02x\n",
685 readb(samsung->sabi + config->header_offsets.re_mem));
686 printk(KERN_DEBUG " SABI data offset = 0x%04x\n",
687 readw(samsung->sabi + config->header_offsets.data_offset));
688 printk(KERN_DEBUG " SABI data segment = 0x%04x\n",
689 readw(samsung->sabi + config->header_offsets.data_segment));
690}
691
692static void __init samsung_sabi_selftest(struct samsung_laptop *samsung,
693 unsigned int ifaceP)
694{
695 const struct sabi_config *config = samsung->config;
696 struct sabi_retval sretval;
697
698 printk(KERN_DEBUG "ifaceP = 0x%08x\n", ifaceP);
699 printk(KERN_DEBUG "sabi_iface = %p\n", samsung->sabi_iface);
700
701 test_backlight(samsung);
702 test_wireless(samsung);
703
704 sabi_get_command(samsung, config->commands.get_brightness, &sretval);
705 printk(KERN_DEBUG "brightness = 0x%02x\n", sretval.retval[0]);
706}
707
708static int __init samsung_sabi_init(struct samsung_laptop *samsung)
709{
710 const struct sabi_config *config = NULL;
711 const struct sabi_commands *commands;
712 unsigned int ifaceP;
713 int ret = 0;
714 int i;
715 int loca;
716
717 samsung->f0000_segment = ioremap_nocache(0xf0000, 0xffff);
718 if (!samsung->f0000_segment) {
719 pr_err("Can't map the segment at 0xf0000\n");
720 ret = -EINVAL;
721 goto exit;
722 }
723
724 /* Try to find one of the signatures in memory to find the header */
725 for (i = 0; sabi_configs[i].test_string != 0; ++i) {
726 samsung->config = &sabi_configs[i];
727 loca = find_signature(samsung->f0000_segment,
728 samsung->config->test_string);
729 if (loca != 0xffff)
730 break;
731 }
732
733 if (loca == 0xffff) {
734 pr_err("This computer does not support SABI\n");
735 ret = -ENODEV;
736 goto exit;
737 }
738
739 config = samsung->config;
740 commands = &config->commands;
741
742 /* point to the SMI port Number */
743 loca += 1;
744 samsung->sabi = (samsung->f0000_segment + loca);
745
746 if (debug)
747 samsung_sabi_infos(samsung, loca);
748
749 /* Get a pointer to the SABI Interface */
750 ifaceP = (readw(samsung->sabi + config->header_offsets.data_segment) & 0x0ffff) << 4;
751 ifaceP += readw(samsung->sabi + config->header_offsets.data_offset) & 0x0ffff;
752 samsung->sabi_iface = ioremap_nocache(ifaceP, 16);
753 if (!samsung->sabi_iface) {
754 pr_err("Can't remap %x\n", ifaceP);
755 ret = -EINVAL;
756 goto exit;
757 }
758
759 if (debug)
760 samsung_sabi_selftest(samsung, ifaceP);
761
762 /* Turn on "Linux" mode in the BIOS */
763 if (commands->set_linux != 0xff) {
764 int retval = sabi_set_command(samsung,
765 commands->set_linux, 0x81);
766 if (retval) {
767 pr_warn("Linux mode was not set!\n");
768 ret = -ENODEV;
769 goto exit;
770 }
771 }
772
773 /* Check for stepping quirk */
774 check_for_stepping_quirk(samsung);
775
776exit:
777 if (ret)
778 samsung_sabi_exit(samsung);
779
780 return ret;
781}
782
783static void samsung_platform_exit(struct samsung_laptop *samsung)
784{
785 if (samsung->platform_device) {
786 platform_device_unregister(samsung->platform_device);
787 samsung->platform_device = NULL;
788 }
789}
790
791static int __init samsung_platform_init(struct samsung_laptop *samsung)
792{
793 struct platform_device *pdev;
794
795 pdev = platform_device_register_simple("samsung", -1, NULL, 0);
796 if (IS_ERR(pdev))
797 return PTR_ERR(pdev);
798
799 samsung->platform_device = pdev;
800 platform_set_drvdata(samsung->platform_device, samsung);
801 return 0;
802}
803
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500804static int __init dmi_check_cb(const struct dmi_system_id *id)
805{
Corentin Chary5dea7a22011-11-26 10:59:59 +0100806 pr_info("found laptop model '%s'\n", id->ident);
Axel Lin27836582011-03-14 18:56:18 +0800807 return 1;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500808}
809
810static struct dmi_system_id __initdata samsung_dmi_table[] = {
811 {
812 .ident = "N128",
813 .matches = {
814 DMI_MATCH(DMI_SYS_VENDOR,
815 "SAMSUNG ELECTRONICS CO., LTD."),
816 DMI_MATCH(DMI_PRODUCT_NAME, "N128"),
817 DMI_MATCH(DMI_BOARD_NAME, "N128"),
818 },
819 .callback = dmi_check_cb,
820 },
821 {
822 .ident = "N130",
823 .matches = {
824 DMI_MATCH(DMI_SYS_VENDOR,
825 "SAMSUNG ELECTRONICS CO., LTD."),
826 DMI_MATCH(DMI_PRODUCT_NAME, "N130"),
827 DMI_MATCH(DMI_BOARD_NAME, "N130"),
828 },
829 .callback = dmi_check_cb,
830 },
831 {
J Witteveen4e2441c2011-07-03 13:15:44 +0200832 .ident = "N510",
833 .matches = {
834 DMI_MATCH(DMI_SYS_VENDOR,
835 "SAMSUNG ELECTRONICS CO., LTD."),
836 DMI_MATCH(DMI_PRODUCT_NAME, "N510"),
837 DMI_MATCH(DMI_BOARD_NAME, "N510"),
838 },
839 .callback = dmi_check_cb,
840 },
841 {
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500842 .ident = "X125",
843 .matches = {
844 DMI_MATCH(DMI_SYS_VENDOR,
845 "SAMSUNG ELECTRONICS CO., LTD."),
846 DMI_MATCH(DMI_PRODUCT_NAME, "X125"),
847 DMI_MATCH(DMI_BOARD_NAME, "X125"),
848 },
849 .callback = dmi_check_cb,
850 },
851 {
852 .ident = "X120/X170",
853 .matches = {
854 DMI_MATCH(DMI_SYS_VENDOR,
855 "SAMSUNG ELECTRONICS CO., LTD."),
856 DMI_MATCH(DMI_PRODUCT_NAME, "X120/X170"),
857 DMI_MATCH(DMI_BOARD_NAME, "X120/X170"),
858 },
859 .callback = dmi_check_cb,
860 },
861 {
862 .ident = "NC10",
863 .matches = {
864 DMI_MATCH(DMI_SYS_VENDOR,
865 "SAMSUNG ELECTRONICS CO., LTD."),
866 DMI_MATCH(DMI_PRODUCT_NAME, "NC10"),
867 DMI_MATCH(DMI_BOARD_NAME, "NC10"),
868 },
869 .callback = dmi_check_cb,
870 },
871 {
872 .ident = "NP-Q45",
873 .matches = {
874 DMI_MATCH(DMI_SYS_VENDOR,
875 "SAMSUNG ELECTRONICS CO., LTD."),
876 DMI_MATCH(DMI_PRODUCT_NAME, "SQ45S70S"),
877 DMI_MATCH(DMI_BOARD_NAME, "SQ45S70S"),
878 },
879 .callback = dmi_check_cb,
880 },
881 {
882 .ident = "X360",
883 .matches = {
884 DMI_MATCH(DMI_SYS_VENDOR,
885 "SAMSUNG ELECTRONICS CO., LTD."),
886 DMI_MATCH(DMI_PRODUCT_NAME, "X360"),
887 DMI_MATCH(DMI_BOARD_NAME, "X360"),
888 },
889 .callback = dmi_check_cb,
890 },
891 {
Alberto Mardegan3d536ed2011-04-08 17:02:03 +0200892 .ident = "R410 Plus",
893 .matches = {
894 DMI_MATCH(DMI_SYS_VENDOR,
895 "SAMSUNG ELECTRONICS CO., LTD."),
896 DMI_MATCH(DMI_PRODUCT_NAME, "R410P"),
897 DMI_MATCH(DMI_BOARD_NAME, "R460"),
898 },
899 .callback = dmi_check_cb,
900 },
901 {
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500902 .ident = "R518",
903 .matches = {
904 DMI_MATCH(DMI_SYS_VENDOR,
905 "SAMSUNG ELECTRONICS CO., LTD."),
906 DMI_MATCH(DMI_PRODUCT_NAME, "R518"),
907 DMI_MATCH(DMI_BOARD_NAME, "R518"),
908 },
909 .callback = dmi_check_cb,
910 },
911 {
912 .ident = "R519/R719",
913 .matches = {
914 DMI_MATCH(DMI_SYS_VENDOR,
915 "SAMSUNG ELECTRONICS CO., LTD."),
916 DMI_MATCH(DMI_PRODUCT_NAME, "R519/R719"),
917 DMI_MATCH(DMI_BOARD_NAME, "R519/R719"),
918 },
919 .callback = dmi_check_cb,
920 },
921 {
Thomas Courbon78a75392011-07-20 22:57:44 +0200922 .ident = "N150/N210/N220",
923 .matches = {
924 DMI_MATCH(DMI_SYS_VENDOR,
925 "SAMSUNG ELECTRONICS CO., LTD."),
926 DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220"),
927 DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220"),
928 },
929 .callback = dmi_check_cb,
930 },
931 {
Raul Gutierrez Segalesf689c872011-09-20 09:16:15 -0700932 .ident = "N220",
933 .matches = {
934 DMI_MATCH(DMI_SYS_VENDOR,
935 "SAMSUNG ELECTRONICS CO., LTD."),
936 DMI_MATCH(DMI_PRODUCT_NAME, "N220"),
937 DMI_MATCH(DMI_BOARD_NAME, "N220"),
938 },
939 .callback = dmi_check_cb,
940 },
941 {
Greg Kroah-Hartman10165072011-04-08 17:02:04 +0200942 .ident = "N150/N210/N220/N230",
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500943 .matches = {
944 DMI_MATCH(DMI_SYS_VENDOR,
945 "SAMSUNG ELECTRONICS CO., LTD."),
Greg Kroah-Hartman10165072011-04-08 17:02:04 +0200946 DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220/N230"),
947 DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220/N230"),
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500948 },
949 .callback = dmi_check_cb,
950 },
951 {
952 .ident = "N150P/N210P/N220P",
953 .matches = {
954 DMI_MATCH(DMI_SYS_VENDOR,
955 "SAMSUNG ELECTRONICS CO., LTD."),
956 DMI_MATCH(DMI_PRODUCT_NAME, "N150P/N210P/N220P"),
957 DMI_MATCH(DMI_BOARD_NAME, "N150P/N210P/N220P"),
958 },
959 .callback = dmi_check_cb,
960 },
961 {
Stefan Bellerf87d0292011-09-20 09:16:08 -0700962 .ident = "R700",
963 .matches = {
964 DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
965 DMI_MATCH(DMI_PRODUCT_NAME, "SR700"),
966 DMI_MATCH(DMI_BOARD_NAME, "SR700"),
967 },
968 .callback = dmi_check_cb,
969 },
970 {
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500971 .ident = "R530/R730",
972 .matches = {
973 DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
974 DMI_MATCH(DMI_PRODUCT_NAME, "R530/R730"),
975 DMI_MATCH(DMI_BOARD_NAME, "R530/R730"),
976 },
977 .callback = dmi_check_cb,
978 },
979 {
980 .ident = "NF110/NF210/NF310",
981 .matches = {
982 DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
983 DMI_MATCH(DMI_PRODUCT_NAME, "NF110/NF210/NF310"),
984 DMI_MATCH(DMI_BOARD_NAME, "NF110/NF210/NF310"),
985 },
986 .callback = dmi_check_cb,
987 },
988 {
989 .ident = "N145P/N250P/N260P",
990 .matches = {
991 DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
992 DMI_MATCH(DMI_PRODUCT_NAME, "N145P/N250P/N260P"),
993 DMI_MATCH(DMI_BOARD_NAME, "N145P/N250P/N260P"),
994 },
995 .callback = dmi_check_cb,
996 },
997 {
998 .ident = "R70/R71",
999 .matches = {
1000 DMI_MATCH(DMI_SYS_VENDOR,
1001 "SAMSUNG ELECTRONICS CO., LTD."),
1002 DMI_MATCH(DMI_PRODUCT_NAME, "R70/R71"),
1003 DMI_MATCH(DMI_BOARD_NAME, "R70/R71"),
1004 },
1005 .callback = dmi_check_cb,
1006 },
1007 {
1008 .ident = "P460",
1009 .matches = {
1010 DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1011 DMI_MATCH(DMI_PRODUCT_NAME, "P460"),
1012 DMI_MATCH(DMI_BOARD_NAME, "P460"),
1013 },
1014 .callback = dmi_check_cb,
1015 },
Smelov Andrey093ed562011-09-20 09:16:10 -07001016 {
1017 .ident = "R528/R728",
1018 .matches = {
1019 DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1020 DMI_MATCH(DMI_PRODUCT_NAME, "R528/R728"),
1021 DMI_MATCH(DMI_BOARD_NAME, "R528/R728"),
1022 },
1023 .callback = dmi_check_cb,
1024 },
Jason Stubbs7b3c2572011-09-20 09:16:14 -07001025 {
1026 .ident = "NC210/NC110",
1027 .matches = {
1028 DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1029 DMI_MATCH(DMI_PRODUCT_NAME, "NC210/NC110"),
1030 DMI_MATCH(DMI_BOARD_NAME, "NC210/NC110"),
1031 },
1032 .callback = dmi_check_cb,
1033 },
Tommaso Massimi7500eeb2011-09-20 09:16:09 -07001034 {
1035 .ident = "X520",
1036 .matches = {
1037 DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1038 DMI_MATCH(DMI_PRODUCT_NAME, "X520"),
1039 DMI_MATCH(DMI_BOARD_NAME, "X520"),
1040 },
1041 .callback = dmi_check_cb,
1042 },
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001043 { },
1044};
1045MODULE_DEVICE_TABLE(dmi, samsung_dmi_table);
1046
Corentin Chary5dea7a22011-11-26 10:59:59 +01001047static struct platform_device *samsung_platform_device;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001048
1049static int __init samsung_init(void)
1050{
Corentin Chary5dea7a22011-11-26 10:59:59 +01001051 struct samsung_laptop *samsung;
1052 int ret;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001053
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001054 if (!force && !dmi_check_system(samsung_dmi_table))
1055 return -ENODEV;
1056
Corentin Charya6df4892011-11-26 10:59:58 +01001057 samsung = kzalloc(sizeof(*samsung), GFP_KERNEL);
1058 if (!samsung)
1059 return -ENOMEM;
1060
1061 mutex_init(&samsung->sabi_mutex);
1062
Corentin Chary5dea7a22011-11-26 10:59:59 +01001063 ret = samsung_platform_init(samsung);
1064 if (ret)
1065 goto error_platform;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001066
Corentin Chary5dea7a22011-11-26 10:59:59 +01001067 ret = samsung_sabi_init(samsung);
1068 if (ret)
1069 goto error_sabi;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001070
Corentin Chary5dea7a22011-11-26 10:59:59 +01001071 ret = samsung_sysfs_init(samsung);
1072 if (ret)
1073 goto error_sysfs;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001074
Corentin Chary5dea7a22011-11-26 10:59:59 +01001075 ret = samsung_backlight_init(samsung);
1076 if (ret)
1077 goto error_backlight;
Corentin Charya6df4892011-11-26 10:59:58 +01001078
Corentin Chary5dea7a22011-11-26 10:59:59 +01001079 ret = samsung_rfkill_init(samsung);
1080 if (ret)
1081 goto error_rfkill;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001082
Corentin Chary5dea7a22011-11-26 10:59:59 +01001083 samsung_platform_device = samsung->platform_device;
1084 return ret;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001085
Corentin Chary5dea7a22011-11-26 10:59:59 +01001086error_rfkill:
1087 samsung_backlight_exit(samsung);
1088error_backlight:
1089 samsung_sysfs_exit(samsung);
1090error_sysfs:
1091 samsung_sabi_exit(samsung);
1092error_sabi:
1093 samsung_platform_exit(samsung);
1094error_platform:
Corentin Charya6df4892011-11-26 10:59:58 +01001095 kfree(samsung);
Corentin Chary5dea7a22011-11-26 10:59:59 +01001096 return ret;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001097}
1098
1099static void __exit samsung_exit(void)
1100{
Corentin Chary5dea7a22011-11-26 10:59:59 +01001101 struct samsung_laptop *samsung;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001102
Corentin Chary5dea7a22011-11-26 10:59:59 +01001103 samsung = platform_get_drvdata(samsung_platform_device);
Corentin Charya6df4892011-11-26 10:59:58 +01001104
Corentin Chary5dea7a22011-11-26 10:59:59 +01001105 samsung_rfkill_exit(samsung);
1106 samsung_backlight_exit(samsung);
1107 samsung_sysfs_exit(samsung);
1108 samsung_sabi_exit(samsung);
1109 samsung_platform_exit(samsung);
1110
Corentin Charya6df4892011-11-26 10:59:58 +01001111 kfree(samsung);
Corentin Chary5dea7a22011-11-26 10:59:59 +01001112 samsung_platform_device = NULL;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001113}
1114
1115module_init(samsung_init);
1116module_exit(samsung_exit);
1117
1118MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@suse.de>");
1119MODULE_DESCRIPTION("Samsung Backlight driver");
1120MODULE_LICENSE("GPL");