blob: 134444f176e60769209d52988cb57cb4ce784a73 [file] [log] [blame]
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001/*
2 * Samsung Laptop driver
3 *
4 * Copyright (C) 2009,2011 Greg Kroah-Hartman (gregkh@suse.de)
5 * Copyright (C) 2009,2011 Novell Inc.
6 *
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License version 2 as published by
9 * the Free Software Foundation.
10 *
11 */
12#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
13
14#include <linux/kernel.h>
15#include <linux/init.h>
16#include <linux/module.h>
17#include <linux/delay.h>
18#include <linux/pci.h>
19#include <linux/backlight.h>
20#include <linux/fb.h>
21#include <linux/dmi.h>
22#include <linux/platform_device.h>
23#include <linux/rfkill.h>
Corentin Charyf34cd9c2011-11-26 11:00:00 +010024#include <linux/acpi.h>
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -050025
26/*
27 * This driver is needed because a number of Samsung laptops do not hook
28 * their control settings through ACPI. So we have to poke around in the
29 * BIOS to do things like brightness values, and "special" key controls.
30 */
31
32/*
33 * We have 0 - 8 as valid brightness levels. The specs say that level 0 should
34 * be reserved by the BIOS (which really doesn't make much sense), we tell
35 * userspace that the value is 0 - 7 and then just tell the hardware 1 - 8
36 */
37#define MAX_BRIGHT 0x07
38
39
40#define SABI_IFACE_MAIN 0x00
41#define SABI_IFACE_SUB 0x02
42#define SABI_IFACE_COMPLETE 0x04
43#define SABI_IFACE_DATA 0x05
44
Corentin Chary7e960712011-11-26 11:00:02 +010045/* Structure get/set data using sabi */
46struct sabi_data {
47 union {
48 struct {
49 u32 d0;
50 u32 d1;
51 u16 d2;
52 u8 d3;
53 };
54 u8 data[11];
55 };
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -050056};
57
58struct sabi_header_offsets {
59 u8 port;
60 u8 re_mem;
61 u8 iface_func;
62 u8 en_mem;
63 u8 data_offset;
64 u8 data_segment;
65};
66
67struct sabi_commands {
68 /*
69 * Brightness is 0 - 8, as described above.
70 * Value 0 is for the BIOS to use
71 */
Corentin Chary7e960712011-11-26 11:00:02 +010072 u16 get_brightness;
73 u16 set_brightness;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -050074
75 /*
76 * first byte:
77 * 0x00 - wireless is off
78 * 0x01 - wireless is on
79 * second byte:
80 * 0x02 - 3G is off
81 * 0x03 - 3G is on
82 * TODO, verify 3G is correct, that doesn't seem right...
83 */
Corentin Chary7e960712011-11-26 11:00:02 +010084 u16 get_wireless_button;
85 u16 set_wireless_button;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -050086
87 /* 0 is off, 1 is on */
Corentin Chary7e960712011-11-26 11:00:02 +010088 u16 get_backlight;
89 u16 set_backlight;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -050090
91 /*
92 * 0x80 or 0x00 - no action
93 * 0x81 - recovery key pressed
94 */
Corentin Chary7e960712011-11-26 11:00:02 +010095 u16 get_recovery_mode;
96 u16 set_recovery_mode;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -050097
98 /*
99 * on seclinux: 0 is low, 1 is high,
100 * on swsmi: 0 is normal, 1 is silent, 2 is turbo
101 */
Corentin Chary7e960712011-11-26 11:00:02 +0100102 u16 get_performance_level;
103 u16 set_performance_level;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500104
105 /*
106 * Tell the BIOS that Linux is running on this machine.
107 * 81 is on, 80 is off
108 */
Corentin Chary7e960712011-11-26 11:00:02 +0100109 u16 set_linux;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500110};
111
112struct sabi_performance_level {
113 const char *name;
Corentin Chary7e960712011-11-26 11:00:02 +0100114 u16 value;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500115};
116
117struct sabi_config {
118 const char *test_string;
119 u16 main_function;
120 const struct sabi_header_offsets header_offsets;
121 const struct sabi_commands commands;
122 const struct sabi_performance_level performance_levels[4];
123 u8 min_brightness;
124 u8 max_brightness;
125};
126
127static const struct sabi_config sabi_configs[] = {
128 {
129 .test_string = "SECLINUX",
130
131 .main_function = 0x4c49,
132
133 .header_offsets = {
134 .port = 0x00,
135 .re_mem = 0x02,
136 .iface_func = 0x03,
137 .en_mem = 0x04,
138 .data_offset = 0x05,
139 .data_segment = 0x07,
140 },
141
142 .commands = {
143 .get_brightness = 0x00,
144 .set_brightness = 0x01,
145
146 .get_wireless_button = 0x02,
147 .set_wireless_button = 0x03,
148
149 .get_backlight = 0x04,
150 .set_backlight = 0x05,
151
152 .get_recovery_mode = 0x06,
153 .set_recovery_mode = 0x07,
154
155 .get_performance_level = 0x08,
156 .set_performance_level = 0x09,
157
158 .set_linux = 0x0a,
159 },
160
161 .performance_levels = {
162 {
163 .name = "silent",
164 .value = 0,
165 },
166 {
167 .name = "normal",
168 .value = 1,
169 },
170 { },
171 },
172 .min_brightness = 1,
173 .max_brightness = 8,
174 },
175 {
176 .test_string = "SwSmi@",
177
178 .main_function = 0x5843,
179
180 .header_offsets = {
181 .port = 0x00,
182 .re_mem = 0x04,
183 .iface_func = 0x02,
184 .en_mem = 0x03,
185 .data_offset = 0x05,
186 .data_segment = 0x07,
187 },
188
189 .commands = {
190 .get_brightness = 0x10,
191 .set_brightness = 0x11,
192
193 .get_wireless_button = 0x12,
194 .set_wireless_button = 0x13,
195
196 .get_backlight = 0x2d,
197 .set_backlight = 0x2e,
198
199 .get_recovery_mode = 0xff,
200 .set_recovery_mode = 0xff,
201
202 .get_performance_level = 0x31,
203 .set_performance_level = 0x32,
204
205 .set_linux = 0xff,
206 },
207
208 .performance_levels = {
209 {
210 .name = "normal",
211 .value = 0,
212 },
213 {
214 .name = "silent",
215 .value = 1,
216 },
217 {
218 .name = "overclock",
219 .value = 2,
220 },
221 { },
222 },
223 .min_brightness = 0,
224 .max_brightness = 8,
225 },
226 { },
227};
228
Corentin Charya6df4892011-11-26 10:59:58 +0100229struct samsung_laptop {
230 const struct sabi_config *config;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500231
Corentin Charya6df4892011-11-26 10:59:58 +0100232 void __iomem *sabi;
233 void __iomem *sabi_iface;
234 void __iomem *f0000_segment;
235
236 struct mutex sabi_mutex;
237
Corentin Chary5dea7a22011-11-26 10:59:59 +0100238 struct platform_device *platform_device;
Corentin Charya6df4892011-11-26 10:59:58 +0100239 struct backlight_device *backlight_device;
240 struct rfkill *rfk;
241
Corentin Charyf34cd9c2011-11-26 11:00:00 +0100242 bool handle_backlight;
Corentin Charya6df4892011-11-26 10:59:58 +0100243 bool has_stepping_quirk;
244};
245
Corentin Chary5dea7a22011-11-26 10:59:59 +0100246
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500247
Rusty Russell90ab5ee2012-01-13 09:32:20 +1030248static bool force;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500249module_param(force, bool, 0);
250MODULE_PARM_DESC(force,
251 "Disable the DMI check and forces the driver to be loaded");
252
Rusty Russell90ab5ee2012-01-13 09:32:20 +1030253static bool debug;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500254module_param(debug, bool, S_IRUGO | S_IWUSR);
255MODULE_PARM_DESC(debug, "Debug enabled or not");
256
Corentin Chary7e960712011-11-26 11:00:02 +0100257static int sabi_command(struct samsung_laptop *samsung, u16 command,
258 struct sabi_data *in,
259 struct sabi_data *out)
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500260{
Corentin Charya6df4892011-11-26 10:59:58 +0100261 const struct sabi_config *config = samsung->config;
Corentin Chary7e960712011-11-26 11:00:02 +0100262 int ret = 0;
Corentin Charya6df4892011-11-26 10:59:58 +0100263 u16 port = readw(samsung->sabi + config->header_offsets.port);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500264 u8 complete, iface_data;
265
Corentin Charya6df4892011-11-26 10:59:58 +0100266 mutex_lock(&samsung->sabi_mutex);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500267
Corentin Chary7e960712011-11-26 11:00:02 +0100268 if (debug) {
269 if (in)
270 pr_info("SABI 0x%04x {0x%08x, 0x%08x, 0x%04x, 0x%02x}",
271 command, in->d0, in->d1, in->d2, in->d3);
272 else
273 pr_info("SABI 0x%04x", command);
274 }
275
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500276 /* enable memory to be able to write to it */
Corentin Charya6df4892011-11-26 10:59:58 +0100277 outb(readb(samsung->sabi + config->header_offsets.en_mem), port);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500278
279 /* write out the command */
Corentin Charya6df4892011-11-26 10:59:58 +0100280 writew(config->main_function, samsung->sabi_iface + SABI_IFACE_MAIN);
281 writew(command, samsung->sabi_iface + SABI_IFACE_SUB);
282 writeb(0, samsung->sabi_iface + SABI_IFACE_COMPLETE);
Corentin Chary7e960712011-11-26 11:00:02 +0100283 if (in) {
284 writel(in->d0, samsung->sabi_iface + SABI_IFACE_DATA);
285 writel(in->d1, samsung->sabi_iface + SABI_IFACE_DATA + 4);
286 writew(in->d2, samsung->sabi_iface + SABI_IFACE_DATA + 8);
287 writeb(in->d3, samsung->sabi_iface + SABI_IFACE_DATA + 10);
288 }
Corentin Charya6df4892011-11-26 10:59:58 +0100289 outb(readb(samsung->sabi + config->header_offsets.iface_func), port);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500290
291 /* write protect memory to make it safe */
Corentin Charya6df4892011-11-26 10:59:58 +0100292 outb(readb(samsung->sabi + config->header_offsets.re_mem), port);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500293
294 /* see if the command actually succeeded */
Corentin Charya6df4892011-11-26 10:59:58 +0100295 complete = readb(samsung->sabi_iface + SABI_IFACE_COMPLETE);
296 iface_data = readb(samsung->sabi_iface + SABI_IFACE_DATA);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500297 if (complete != 0xaa || iface_data == 0xff) {
Corentin Chary7e960712011-11-26 11:00:02 +0100298 pr_warn("SABI command 0x%04x failed with"
299 " completion flag 0x%02x and interface data 0x%02x",
300 command, complete, iface_data);
301 ret = -EINVAL;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500302 goto exit;
303 }
Corentin Chary7e960712011-11-26 11:00:02 +0100304
305 if (out) {
306 out->d0 = readl(samsung->sabi_iface + SABI_IFACE_DATA);
307 out->d1 = readl(samsung->sabi_iface + SABI_IFACE_DATA + 4);
308 out->d2 = readw(samsung->sabi_iface + SABI_IFACE_DATA + 2);
309 out->d3 = readb(samsung->sabi_iface + SABI_IFACE_DATA + 1);
310 }
311
312 if (debug && out) {
313 pr_info("SABI {0x%08x, 0x%08x, 0x%04x, 0x%02x}",
314 out->d0, out->d1, out->d2, out->d3);
315 }
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500316
317exit:
Corentin Charya6df4892011-11-26 10:59:58 +0100318 mutex_unlock(&samsung->sabi_mutex);
Corentin Chary7e960712011-11-26 11:00:02 +0100319 return ret;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500320}
321
Corentin Chary7e960712011-11-26 11:00:02 +0100322/* simple wrappers usable with most commands */
323static int sabi_set_commandb(struct samsung_laptop *samsung,
324 u16 command, u8 data)
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500325{
Corentin Chary7e960712011-11-26 11:00:02 +0100326 struct sabi_data in = { .d0 = 0, .d1 = 0, .d2 = 0, .d3 = 0 };
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500327
Corentin Chary7e960712011-11-26 11:00:02 +0100328 in.data[0] = data;
329 return sabi_command(samsung, command, &in, NULL);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500330}
331
Corentin Chary5dea7a22011-11-26 10:59:59 +0100332static void test_backlight(struct samsung_laptop *samsung)
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500333{
Corentin Charya6df4892011-11-26 10:59:58 +0100334 const struct sabi_commands *commands = &samsung->config->commands;
Corentin Chary7e960712011-11-26 11:00:02 +0100335 struct sabi_data sretval;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500336
Corentin Chary7e960712011-11-26 11:00:02 +0100337 sabi_command(samsung, commands->get_backlight, NULL, &sretval);
338 printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.data[0]);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500339
Corentin Chary7e960712011-11-26 11:00:02 +0100340 sabi_set_commandb(samsung, commands->set_backlight, 0);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500341 printk(KERN_DEBUG "backlight should be off\n");
342
Corentin Chary7e960712011-11-26 11:00:02 +0100343 sabi_command(samsung, commands->get_backlight, NULL, &sretval);
344 printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.data[0]);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500345
346 msleep(1000);
347
Corentin Chary7e960712011-11-26 11:00:02 +0100348 sabi_set_commandb(samsung, commands->set_backlight, 1);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500349 printk(KERN_DEBUG "backlight should be on\n");
350
Corentin Chary7e960712011-11-26 11:00:02 +0100351 sabi_command(samsung, commands->get_backlight, NULL, &sretval);
352 printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.data[0]);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500353}
354
Corentin Chary5dea7a22011-11-26 10:59:59 +0100355static void test_wireless(struct samsung_laptop *samsung)
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500356{
Corentin Charya6df4892011-11-26 10:59:58 +0100357 const struct sabi_commands *commands = &samsung->config->commands;
Corentin Chary7e960712011-11-26 11:00:02 +0100358 struct sabi_data sretval;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500359
Corentin Chary7e960712011-11-26 11:00:02 +0100360 sabi_command(samsung, commands->get_wireless_button, NULL, &sretval);
361 printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.data[0]);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500362
Corentin Chary7e960712011-11-26 11:00:02 +0100363 sabi_set_commandb(samsung, commands->set_wireless_button, 0);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500364 printk(KERN_DEBUG "wireless led should be off\n");
365
Corentin Chary7e960712011-11-26 11:00:02 +0100366 sabi_command(samsung, commands->get_wireless_button, NULL, &sretval);
367 printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.data[0]);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500368
369 msleep(1000);
370
Corentin Chary7e960712011-11-26 11:00:02 +0100371 sabi_set_commandb(samsung, commands->set_wireless_button, 1);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500372 printk(KERN_DEBUG "wireless led should be on\n");
373
Corentin Chary7e960712011-11-26 11:00:02 +0100374 sabi_command(samsung, commands->get_wireless_button, NULL, &sretval);
375 printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.data[0]);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500376}
377
Corentin Chary5dea7a22011-11-26 10:59:59 +0100378static int read_brightness(struct samsung_laptop *samsung)
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500379{
Corentin Charya6df4892011-11-26 10:59:58 +0100380 const struct sabi_config *config = samsung->config;
381 const struct sabi_commands *commands = &samsung->config->commands;
Corentin Chary7e960712011-11-26 11:00:02 +0100382 struct sabi_data sretval;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500383 int user_brightness = 0;
384 int retval;
385
Corentin Chary7e960712011-11-26 11:00:02 +0100386 retval = sabi_command(samsung, commands->get_brightness,
387 NULL, &sretval);
388 if (retval)
389 return retval;
390
391 user_brightness = sretval.data[0];
392 if (user_brightness > config->min_brightness)
393 user_brightness -= config->min_brightness;
394 else
395 user_brightness = 0;
396
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500397 return user_brightness;
398}
399
Corentin Chary5dea7a22011-11-26 10:59:59 +0100400static void set_brightness(struct samsung_laptop *samsung, u8 user_brightness)
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500401{
Corentin Charya6df4892011-11-26 10:59:58 +0100402 const struct sabi_config *config = samsung->config;
403 const struct sabi_commands *commands = &samsung->config->commands;
404 u8 user_level = user_brightness + config->min_brightness;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500405
Corentin Charya6df4892011-11-26 10:59:58 +0100406 if (samsung->has_stepping_quirk && user_level != 0) {
Jason Stubbsac080522011-09-20 09:16:13 -0700407 /*
408 * short circuit if the specified level is what's already set
409 * to prevent the screen from flickering needlessly
410 */
Corentin Chary5dea7a22011-11-26 10:59:59 +0100411 if (user_brightness == read_brightness(samsung))
Jason Stubbsac080522011-09-20 09:16:13 -0700412 return;
413
Corentin Chary7e960712011-11-26 11:00:02 +0100414 sabi_set_commandb(samsung, commands->set_brightness, 0);
Jason Stubbsac080522011-09-20 09:16:13 -0700415 }
416
Corentin Chary7e960712011-11-26 11:00:02 +0100417 sabi_set_commandb(samsung, commands->set_brightness, user_level);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500418}
419
420static int get_brightness(struct backlight_device *bd)
421{
Corentin Chary5dea7a22011-11-26 10:59:59 +0100422 struct samsung_laptop *samsung = bl_get_data(bd);
423
424 return read_brightness(samsung);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500425}
426
Corentin Chary5dea7a22011-11-26 10:59:59 +0100427static void check_for_stepping_quirk(struct samsung_laptop *samsung)
Jason Stubbsac080522011-09-20 09:16:13 -0700428{
Corentin Chary5dea7a22011-11-26 10:59:59 +0100429 int initial_level;
430 int check_level;
431 int orig_level = read_brightness(samsung);
Jason Stubbsac080522011-09-20 09:16:13 -0700432
433 /*
434 * Some laptops exhibit the strange behaviour of stepping toward
435 * (rather than setting) the brightness except when changing to/from
436 * brightness level 0. This behaviour is checked for here and worked
437 * around in set_brightness.
438 */
439
John Serockba05b232011-10-13 06:42:01 -0400440 if (orig_level == 0)
Corentin Chary5dea7a22011-11-26 10:59:59 +0100441 set_brightness(samsung, 1);
John Serockba05b232011-10-13 06:42:01 -0400442
Corentin Chary5dea7a22011-11-26 10:59:59 +0100443 initial_level = read_brightness(samsung);
John Serockba05b232011-10-13 06:42:01 -0400444
Jason Stubbsac080522011-09-20 09:16:13 -0700445 if (initial_level <= 2)
446 check_level = initial_level + 2;
447 else
448 check_level = initial_level - 2;
449
Corentin Charya6df4892011-11-26 10:59:58 +0100450 samsung->has_stepping_quirk = false;
Corentin Chary5dea7a22011-11-26 10:59:59 +0100451 set_brightness(samsung, check_level);
Jason Stubbsac080522011-09-20 09:16:13 -0700452
Corentin Chary5dea7a22011-11-26 10:59:59 +0100453 if (read_brightness(samsung) != check_level) {
Corentin Charya6df4892011-11-26 10:59:58 +0100454 samsung->has_stepping_quirk = true;
Jason Stubbsac080522011-09-20 09:16:13 -0700455 pr_info("enabled workaround for brightness stepping quirk\n");
456 }
457
Corentin Chary5dea7a22011-11-26 10:59:59 +0100458 set_brightness(samsung, orig_level);
Jason Stubbsac080522011-09-20 09:16:13 -0700459}
460
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500461static int update_status(struct backlight_device *bd)
462{
Corentin Chary5dea7a22011-11-26 10:59:59 +0100463 struct samsung_laptop *samsung = bl_get_data(bd);
Corentin Charya6df4892011-11-26 10:59:58 +0100464 const struct sabi_commands *commands = &samsung->config->commands;
465
Corentin Chary5dea7a22011-11-26 10:59:59 +0100466 set_brightness(samsung, bd->props.brightness);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500467
468 if (bd->props.power == FB_BLANK_UNBLANK)
Corentin Chary7e960712011-11-26 11:00:02 +0100469 sabi_set_commandb(samsung, commands->set_backlight, 1);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500470 else
Corentin Chary7e960712011-11-26 11:00:02 +0100471 sabi_set_commandb(samsung, commands->set_backlight, 0);
Corentin Chary5dea7a22011-11-26 10:59:59 +0100472
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500473 return 0;
474}
475
476static const struct backlight_ops backlight_ops = {
477 .get_brightness = get_brightness,
478 .update_status = update_status,
479};
480
481static int rfkill_set(void *data, bool blocked)
482{
Corentin Chary5dea7a22011-11-26 10:59:59 +0100483 struct samsung_laptop *samsung = data;
Corentin Charya6df4892011-11-26 10:59:58 +0100484 const struct sabi_commands *commands = &samsung->config->commands;
485
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500486 /* Do something with blocked...*/
487 /*
488 * blocked == false is on
489 * blocked == true is off
490 */
491 if (blocked)
Corentin Chary7e960712011-11-26 11:00:02 +0100492 sabi_set_commandb(samsung, commands->set_wireless_button, 0);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500493 else
Corentin Chary7e960712011-11-26 11:00:02 +0100494 sabi_set_commandb(samsung, commands->set_wireless_button, 1);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500495
496 return 0;
497}
498
499static struct rfkill_ops rfkill_ops = {
500 .set_block = rfkill_set,
501};
502
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500503static ssize_t get_performance_level(struct device *dev,
504 struct device_attribute *attr, char *buf)
505{
Corentin Chary5dea7a22011-11-26 10:59:59 +0100506 struct samsung_laptop *samsung = dev_get_drvdata(dev);
Corentin Charya6df4892011-11-26 10:59:58 +0100507 const struct sabi_config *config = samsung->config;
Corentin Chary5dea7a22011-11-26 10:59:59 +0100508 const struct sabi_commands *commands = &config->commands;
Corentin Chary7e960712011-11-26 11:00:02 +0100509 struct sabi_data sretval;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500510 int retval;
511 int i;
512
513 /* Read the state */
Corentin Chary7e960712011-11-26 11:00:02 +0100514 retval = sabi_command(samsung, commands->get_performance_level,
515 NULL, &sretval);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500516 if (retval)
517 return retval;
518
519 /* The logic is backwards, yeah, lots of fun... */
Corentin Charya6df4892011-11-26 10:59:58 +0100520 for (i = 0; config->performance_levels[i].name; ++i) {
Corentin Chary7e960712011-11-26 11:00:02 +0100521 if (sretval.data[0] == config->performance_levels[i].value)
Corentin Charya6df4892011-11-26 10:59:58 +0100522 return sprintf(buf, "%s\n", config->performance_levels[i].name);
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500523 }
524 return sprintf(buf, "%s\n", "unknown");
525}
526
527static ssize_t set_performance_level(struct device *dev,
528 struct device_attribute *attr, const char *buf,
529 size_t count)
530{
Corentin Chary5dea7a22011-11-26 10:59:59 +0100531 struct samsung_laptop *samsung = dev_get_drvdata(dev);
Corentin Charya6df4892011-11-26 10:59:58 +0100532 const struct sabi_config *config = samsung->config;
Corentin Chary5dea7a22011-11-26 10:59:59 +0100533 const struct sabi_commands *commands = &config->commands;
534 int i;
Corentin Charya6df4892011-11-26 10:59:58 +0100535
Corentin Chary5dea7a22011-11-26 10:59:59 +0100536 if (count < 1)
537 return count;
538
539 for (i = 0; config->performance_levels[i].name; ++i) {
540 const struct sabi_performance_level *level =
541 &config->performance_levels[i];
542 if (!strncasecmp(level->name, buf, strlen(level->name))) {
Corentin Chary7e960712011-11-26 11:00:02 +0100543 sabi_set_commandb(samsung,
544 commands->set_performance_level,
545 level->value);
Corentin Chary5dea7a22011-11-26 10:59:59 +0100546 break;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500547 }
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500548 }
Corentin Chary5dea7a22011-11-26 10:59:59 +0100549
550 if (!config->performance_levels[i].name)
551 return -EINVAL;
552
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500553 return count;
554}
Corentin Chary5dea7a22011-11-26 10:59:59 +0100555
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500556static DEVICE_ATTR(performance_level, S_IWUSR | S_IRUGO,
557 get_performance_level, set_performance_level);
558
Corentin Charya66c1662011-11-26 11:00:01 +0100559static struct attribute *platform_attributes[] = {
560 &dev_attr_performance_level.attr,
561 NULL
562};
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500563
Corentin Chary5dea7a22011-11-26 10:59:59 +0100564static int find_signature(void __iomem *memcheck, const char *testStr)
565{
566 int i = 0;
567 int loca;
568
569 for (loca = 0; loca < 0xffff; loca++) {
570 char temp = readb(memcheck + loca);
571
572 if (temp == testStr[i]) {
573 if (i == strlen(testStr)-1)
574 break;
575 ++i;
576 } else {
577 i = 0;
578 }
579 }
580 return loca;
581}
582
583static void samsung_rfkill_exit(struct samsung_laptop *samsung)
584{
585 if (samsung->rfk) {
586 rfkill_unregister(samsung->rfk);
587 rfkill_destroy(samsung->rfk);
588 samsung->rfk = NULL;
589 }
590}
591
592static int __init samsung_rfkill_init(struct samsung_laptop *samsung)
593{
594 int retval;
595
596 samsung->rfk = rfkill_alloc("samsung-wifi",
597 &samsung->platform_device->dev,
598 RFKILL_TYPE_WLAN,
599 &rfkill_ops, samsung);
600 if (!samsung->rfk)
601 return -ENOMEM;
602
603 retval = rfkill_register(samsung->rfk);
604 if (retval) {
605 rfkill_destroy(samsung->rfk);
606 samsung->rfk = NULL;
607 return -ENODEV;
608 }
609
610 return 0;
611}
612
613static void samsung_backlight_exit(struct samsung_laptop *samsung)
614{
615 if (samsung->backlight_device) {
616 backlight_device_unregister(samsung->backlight_device);
617 samsung->backlight_device = NULL;
618 }
619}
620
621static int __init samsung_backlight_init(struct samsung_laptop *samsung)
622{
623 struct backlight_device *bd;
624 struct backlight_properties props;
625
Corentin Charyf34cd9c2011-11-26 11:00:00 +0100626 if (!samsung->handle_backlight)
627 return 0;
628
Corentin Chary5dea7a22011-11-26 10:59:59 +0100629 memset(&props, 0, sizeof(struct backlight_properties));
630 props.type = BACKLIGHT_PLATFORM;
631 props.max_brightness = samsung->config->max_brightness -
632 samsung->config->min_brightness;
633
634 bd = backlight_device_register("samsung",
635 &samsung->platform_device->dev,
636 samsung, &backlight_ops,
637 &props);
638 if (IS_ERR(bd))
639 return PTR_ERR(bd);
640
641 samsung->backlight_device = bd;
642 samsung->backlight_device->props.brightness = read_brightness(samsung);
643 samsung->backlight_device->props.power = FB_BLANK_UNBLANK;
644 backlight_update_status(samsung->backlight_device);
645
646 return 0;
647}
648
Corentin Charya66c1662011-11-26 11:00:01 +0100649static mode_t samsung_sysfs_is_visible(struct kobject *kobj,
650 struct attribute *attr, int idx)
651{
652 struct device *dev = container_of(kobj, struct device, kobj);
653 struct platform_device *pdev = to_platform_device(dev);
654 struct samsung_laptop *samsung = platform_get_drvdata(pdev);
655 bool ok = true;
656
657 if (attr == &dev_attr_performance_level.attr)
658 ok = !!samsung->config->performance_levels[0].name;
659
660 return ok ? attr->mode : 0;
661}
662
663static struct attribute_group platform_attribute_group = {
664 .is_visible = samsung_sysfs_is_visible,
665 .attrs = platform_attributes
666};
667
Corentin Chary5dea7a22011-11-26 10:59:59 +0100668static void samsung_sysfs_exit(struct samsung_laptop *samsung)
669{
Corentin Charya66c1662011-11-26 11:00:01 +0100670 struct platform_device *device = samsung->platform_device;
671
672 sysfs_remove_group(&device->dev.kobj, &platform_attribute_group);
Corentin Chary5dea7a22011-11-26 10:59:59 +0100673}
674
675static int __init samsung_sysfs_init(struct samsung_laptop *samsung)
676{
Corentin Charya66c1662011-11-26 11:00:01 +0100677 struct platform_device *device = samsung->platform_device;
678
679 return sysfs_create_group(&device->dev.kobj, &platform_attribute_group);
680
Corentin Chary5dea7a22011-11-26 10:59:59 +0100681}
682
683static void samsung_sabi_exit(struct samsung_laptop *samsung)
684{
685 const struct sabi_config *config = samsung->config;
686
687 /* Turn off "Linux" mode in the BIOS */
688 if (config && config->commands.set_linux != 0xff)
Corentin Chary7e960712011-11-26 11:00:02 +0100689 sabi_set_commandb(samsung, config->commands.set_linux, 0x80);
Corentin Chary5dea7a22011-11-26 10:59:59 +0100690
691 if (samsung->sabi_iface) {
692 iounmap(samsung->sabi_iface);
693 samsung->sabi_iface = NULL;
694 }
695 if (samsung->f0000_segment) {
696 iounmap(samsung->f0000_segment);
697 samsung->f0000_segment = NULL;
698 }
699
700 samsung->config = NULL;
701}
702
703static __init void samsung_sabi_infos(struct samsung_laptop *samsung, int loca)
704{
705 const struct sabi_config *config = samsung->config;
706
707 printk(KERN_DEBUG "This computer supports SABI==%x\n",
708 loca + 0xf0000 - 6);
709 printk(KERN_DEBUG "SABI header:\n");
710 printk(KERN_DEBUG " SMI Port Number = 0x%04x\n",
711 readw(samsung->sabi + config->header_offsets.port));
712 printk(KERN_DEBUG " SMI Interface Function = 0x%02x\n",
713 readb(samsung->sabi + config->header_offsets.iface_func));
714 printk(KERN_DEBUG " SMI enable memory buffer = 0x%02x\n",
715 readb(samsung->sabi + config->header_offsets.en_mem));
716 printk(KERN_DEBUG " SMI restore memory buffer = 0x%02x\n",
717 readb(samsung->sabi + config->header_offsets.re_mem));
718 printk(KERN_DEBUG " SABI data offset = 0x%04x\n",
719 readw(samsung->sabi + config->header_offsets.data_offset));
720 printk(KERN_DEBUG " SABI data segment = 0x%04x\n",
721 readw(samsung->sabi + config->header_offsets.data_segment));
722}
723
724static void __init samsung_sabi_selftest(struct samsung_laptop *samsung,
725 unsigned int ifaceP)
726{
727 const struct sabi_config *config = samsung->config;
Corentin Chary7e960712011-11-26 11:00:02 +0100728 struct sabi_data sretval;
Corentin Chary5dea7a22011-11-26 10:59:59 +0100729
730 printk(KERN_DEBUG "ifaceP = 0x%08x\n", ifaceP);
731 printk(KERN_DEBUG "sabi_iface = %p\n", samsung->sabi_iface);
732
Corentin Charyf34cd9c2011-11-26 11:00:00 +0100733 if (samsung->handle_backlight)
734 test_backlight(samsung);
Corentin Chary5dea7a22011-11-26 10:59:59 +0100735 test_wireless(samsung);
736
Corentin Chary7e960712011-11-26 11:00:02 +0100737 sabi_command(samsung, config->commands.get_brightness, NULL, &sretval);
738 printk(KERN_DEBUG "brightness = 0x%02x\n", sretval.data[0]);
Corentin Chary5dea7a22011-11-26 10:59:59 +0100739}
740
741static int __init samsung_sabi_init(struct samsung_laptop *samsung)
742{
743 const struct sabi_config *config = NULL;
744 const struct sabi_commands *commands;
745 unsigned int ifaceP;
746 int ret = 0;
747 int i;
748 int loca;
749
750 samsung->f0000_segment = ioremap_nocache(0xf0000, 0xffff);
751 if (!samsung->f0000_segment) {
752 pr_err("Can't map the segment at 0xf0000\n");
753 ret = -EINVAL;
754 goto exit;
755 }
756
757 /* Try to find one of the signatures in memory to find the header */
758 for (i = 0; sabi_configs[i].test_string != 0; ++i) {
759 samsung->config = &sabi_configs[i];
760 loca = find_signature(samsung->f0000_segment,
761 samsung->config->test_string);
762 if (loca != 0xffff)
763 break;
764 }
765
766 if (loca == 0xffff) {
767 pr_err("This computer does not support SABI\n");
768 ret = -ENODEV;
769 goto exit;
770 }
771
772 config = samsung->config;
773 commands = &config->commands;
774
775 /* point to the SMI port Number */
776 loca += 1;
777 samsung->sabi = (samsung->f0000_segment + loca);
778
779 if (debug)
780 samsung_sabi_infos(samsung, loca);
781
782 /* Get a pointer to the SABI Interface */
783 ifaceP = (readw(samsung->sabi + config->header_offsets.data_segment) & 0x0ffff) << 4;
784 ifaceP += readw(samsung->sabi + config->header_offsets.data_offset) & 0x0ffff;
785 samsung->sabi_iface = ioremap_nocache(ifaceP, 16);
786 if (!samsung->sabi_iface) {
787 pr_err("Can't remap %x\n", ifaceP);
788 ret = -EINVAL;
789 goto exit;
790 }
791
792 if (debug)
793 samsung_sabi_selftest(samsung, ifaceP);
794
795 /* Turn on "Linux" mode in the BIOS */
796 if (commands->set_linux != 0xff) {
Corentin Chary7e960712011-11-26 11:00:02 +0100797 int retval = sabi_set_commandb(samsung,
798 commands->set_linux, 0x81);
Corentin Chary5dea7a22011-11-26 10:59:59 +0100799 if (retval) {
800 pr_warn("Linux mode was not set!\n");
801 ret = -ENODEV;
802 goto exit;
803 }
804 }
805
806 /* Check for stepping quirk */
Corentin Charyf34cd9c2011-11-26 11:00:00 +0100807 if (samsung->handle_backlight)
808 check_for_stepping_quirk(samsung);
Corentin Chary5dea7a22011-11-26 10:59:59 +0100809
810exit:
811 if (ret)
812 samsung_sabi_exit(samsung);
813
814 return ret;
815}
816
817static void samsung_platform_exit(struct samsung_laptop *samsung)
818{
819 if (samsung->platform_device) {
820 platform_device_unregister(samsung->platform_device);
821 samsung->platform_device = NULL;
822 }
823}
824
825static int __init samsung_platform_init(struct samsung_laptop *samsung)
826{
827 struct platform_device *pdev;
828
829 pdev = platform_device_register_simple("samsung", -1, NULL, 0);
830 if (IS_ERR(pdev))
831 return PTR_ERR(pdev);
832
833 samsung->platform_device = pdev;
834 platform_set_drvdata(samsung->platform_device, samsung);
835 return 0;
836}
837
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500838static int __init dmi_check_cb(const struct dmi_system_id *id)
839{
Corentin Chary5dea7a22011-11-26 10:59:59 +0100840 pr_info("found laptop model '%s'\n", id->ident);
Axel Lin27836582011-03-14 18:56:18 +0800841 return 1;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500842}
843
844static struct dmi_system_id __initdata samsung_dmi_table[] = {
845 {
846 .ident = "N128",
847 .matches = {
848 DMI_MATCH(DMI_SYS_VENDOR,
849 "SAMSUNG ELECTRONICS CO., LTD."),
850 DMI_MATCH(DMI_PRODUCT_NAME, "N128"),
851 DMI_MATCH(DMI_BOARD_NAME, "N128"),
852 },
853 .callback = dmi_check_cb,
854 },
855 {
856 .ident = "N130",
857 .matches = {
858 DMI_MATCH(DMI_SYS_VENDOR,
859 "SAMSUNG ELECTRONICS CO., LTD."),
860 DMI_MATCH(DMI_PRODUCT_NAME, "N130"),
861 DMI_MATCH(DMI_BOARD_NAME, "N130"),
862 },
863 .callback = dmi_check_cb,
864 },
865 {
J Witteveen4e2441c2011-07-03 13:15:44 +0200866 .ident = "N510",
867 .matches = {
868 DMI_MATCH(DMI_SYS_VENDOR,
869 "SAMSUNG ELECTRONICS CO., LTD."),
870 DMI_MATCH(DMI_PRODUCT_NAME, "N510"),
871 DMI_MATCH(DMI_BOARD_NAME, "N510"),
872 },
873 .callback = dmi_check_cb,
874 },
875 {
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500876 .ident = "X125",
877 .matches = {
878 DMI_MATCH(DMI_SYS_VENDOR,
879 "SAMSUNG ELECTRONICS CO., LTD."),
880 DMI_MATCH(DMI_PRODUCT_NAME, "X125"),
881 DMI_MATCH(DMI_BOARD_NAME, "X125"),
882 },
883 .callback = dmi_check_cb,
884 },
885 {
886 .ident = "X120/X170",
887 .matches = {
888 DMI_MATCH(DMI_SYS_VENDOR,
889 "SAMSUNG ELECTRONICS CO., LTD."),
890 DMI_MATCH(DMI_PRODUCT_NAME, "X120/X170"),
891 DMI_MATCH(DMI_BOARD_NAME, "X120/X170"),
892 },
893 .callback = dmi_check_cb,
894 },
895 {
896 .ident = "NC10",
897 .matches = {
898 DMI_MATCH(DMI_SYS_VENDOR,
899 "SAMSUNG ELECTRONICS CO., LTD."),
900 DMI_MATCH(DMI_PRODUCT_NAME, "NC10"),
901 DMI_MATCH(DMI_BOARD_NAME, "NC10"),
902 },
903 .callback = dmi_check_cb,
904 },
905 {
906 .ident = "NP-Q45",
907 .matches = {
908 DMI_MATCH(DMI_SYS_VENDOR,
909 "SAMSUNG ELECTRONICS CO., LTD."),
910 DMI_MATCH(DMI_PRODUCT_NAME, "SQ45S70S"),
911 DMI_MATCH(DMI_BOARD_NAME, "SQ45S70S"),
912 },
913 .callback = dmi_check_cb,
914 },
915 {
916 .ident = "X360",
917 .matches = {
918 DMI_MATCH(DMI_SYS_VENDOR,
919 "SAMSUNG ELECTRONICS CO., LTD."),
920 DMI_MATCH(DMI_PRODUCT_NAME, "X360"),
921 DMI_MATCH(DMI_BOARD_NAME, "X360"),
922 },
923 .callback = dmi_check_cb,
924 },
925 {
Alberto Mardegan3d536ed2011-04-08 17:02:03 +0200926 .ident = "R410 Plus",
927 .matches = {
928 DMI_MATCH(DMI_SYS_VENDOR,
929 "SAMSUNG ELECTRONICS CO., LTD."),
930 DMI_MATCH(DMI_PRODUCT_NAME, "R410P"),
931 DMI_MATCH(DMI_BOARD_NAME, "R460"),
932 },
933 .callback = dmi_check_cb,
934 },
935 {
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500936 .ident = "R518",
937 .matches = {
938 DMI_MATCH(DMI_SYS_VENDOR,
939 "SAMSUNG ELECTRONICS CO., LTD."),
940 DMI_MATCH(DMI_PRODUCT_NAME, "R518"),
941 DMI_MATCH(DMI_BOARD_NAME, "R518"),
942 },
943 .callback = dmi_check_cb,
944 },
945 {
946 .ident = "R519/R719",
947 .matches = {
948 DMI_MATCH(DMI_SYS_VENDOR,
949 "SAMSUNG ELECTRONICS CO., LTD."),
950 DMI_MATCH(DMI_PRODUCT_NAME, "R519/R719"),
951 DMI_MATCH(DMI_BOARD_NAME, "R519/R719"),
952 },
953 .callback = dmi_check_cb,
954 },
955 {
Thomas Courbon78a75392011-07-20 22:57:44 +0200956 .ident = "N150/N210/N220",
957 .matches = {
958 DMI_MATCH(DMI_SYS_VENDOR,
959 "SAMSUNG ELECTRONICS CO., LTD."),
960 DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220"),
961 DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220"),
962 },
963 .callback = dmi_check_cb,
964 },
965 {
Raul Gutierrez Segalesf689c872011-09-20 09:16:15 -0700966 .ident = "N220",
967 .matches = {
968 DMI_MATCH(DMI_SYS_VENDOR,
969 "SAMSUNG ELECTRONICS CO., LTD."),
970 DMI_MATCH(DMI_PRODUCT_NAME, "N220"),
971 DMI_MATCH(DMI_BOARD_NAME, "N220"),
972 },
973 .callback = dmi_check_cb,
974 },
975 {
Greg Kroah-Hartman10165072011-04-08 17:02:04 +0200976 .ident = "N150/N210/N220/N230",
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500977 .matches = {
978 DMI_MATCH(DMI_SYS_VENDOR,
979 "SAMSUNG ELECTRONICS CO., LTD."),
Greg Kroah-Hartman10165072011-04-08 17:02:04 +0200980 DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220/N230"),
981 DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220/N230"),
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -0500982 },
983 .callback = dmi_check_cb,
984 },
985 {
986 .ident = "N150P/N210P/N220P",
987 .matches = {
988 DMI_MATCH(DMI_SYS_VENDOR,
989 "SAMSUNG ELECTRONICS CO., LTD."),
990 DMI_MATCH(DMI_PRODUCT_NAME, "N150P/N210P/N220P"),
991 DMI_MATCH(DMI_BOARD_NAME, "N150P/N210P/N220P"),
992 },
993 .callback = dmi_check_cb,
994 },
995 {
Stefan Bellerf87d0292011-09-20 09:16:08 -0700996 .ident = "R700",
997 .matches = {
998 DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
999 DMI_MATCH(DMI_PRODUCT_NAME, "SR700"),
1000 DMI_MATCH(DMI_BOARD_NAME, "SR700"),
1001 },
1002 .callback = dmi_check_cb,
1003 },
1004 {
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001005 .ident = "R530/R730",
1006 .matches = {
1007 DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1008 DMI_MATCH(DMI_PRODUCT_NAME, "R530/R730"),
1009 DMI_MATCH(DMI_BOARD_NAME, "R530/R730"),
1010 },
1011 .callback = dmi_check_cb,
1012 },
1013 {
1014 .ident = "NF110/NF210/NF310",
1015 .matches = {
1016 DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1017 DMI_MATCH(DMI_PRODUCT_NAME, "NF110/NF210/NF310"),
1018 DMI_MATCH(DMI_BOARD_NAME, "NF110/NF210/NF310"),
1019 },
1020 .callback = dmi_check_cb,
1021 },
1022 {
1023 .ident = "N145P/N250P/N260P",
1024 .matches = {
1025 DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1026 DMI_MATCH(DMI_PRODUCT_NAME, "N145P/N250P/N260P"),
1027 DMI_MATCH(DMI_BOARD_NAME, "N145P/N250P/N260P"),
1028 },
1029 .callback = dmi_check_cb,
1030 },
1031 {
1032 .ident = "R70/R71",
1033 .matches = {
1034 DMI_MATCH(DMI_SYS_VENDOR,
1035 "SAMSUNG ELECTRONICS CO., LTD."),
1036 DMI_MATCH(DMI_PRODUCT_NAME, "R70/R71"),
1037 DMI_MATCH(DMI_BOARD_NAME, "R70/R71"),
1038 },
1039 .callback = dmi_check_cb,
1040 },
1041 {
1042 .ident = "P460",
1043 .matches = {
1044 DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1045 DMI_MATCH(DMI_PRODUCT_NAME, "P460"),
1046 DMI_MATCH(DMI_BOARD_NAME, "P460"),
1047 },
1048 .callback = dmi_check_cb,
1049 },
Smelov Andrey093ed562011-09-20 09:16:10 -07001050 {
1051 .ident = "R528/R728",
1052 .matches = {
1053 DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1054 DMI_MATCH(DMI_PRODUCT_NAME, "R528/R728"),
1055 DMI_MATCH(DMI_BOARD_NAME, "R528/R728"),
1056 },
1057 .callback = dmi_check_cb,
1058 },
Jason Stubbs7b3c2572011-09-20 09:16:14 -07001059 {
1060 .ident = "NC210/NC110",
1061 .matches = {
1062 DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1063 DMI_MATCH(DMI_PRODUCT_NAME, "NC210/NC110"),
1064 DMI_MATCH(DMI_BOARD_NAME, "NC210/NC110"),
1065 },
1066 .callback = dmi_check_cb,
1067 },
Tommaso Massimi7500eeb2011-09-20 09:16:09 -07001068 {
1069 .ident = "X520",
1070 .matches = {
1071 DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1072 DMI_MATCH(DMI_PRODUCT_NAME, "X520"),
1073 DMI_MATCH(DMI_BOARD_NAME, "X520"),
1074 },
1075 .callback = dmi_check_cb,
1076 },
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001077 { },
1078};
1079MODULE_DEVICE_TABLE(dmi, samsung_dmi_table);
1080
Corentin Chary5dea7a22011-11-26 10:59:59 +01001081static struct platform_device *samsung_platform_device;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001082
1083static int __init samsung_init(void)
1084{
Corentin Chary5dea7a22011-11-26 10:59:59 +01001085 struct samsung_laptop *samsung;
1086 int ret;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001087
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001088 if (!force && !dmi_check_system(samsung_dmi_table))
1089 return -ENODEV;
1090
Corentin Charya6df4892011-11-26 10:59:58 +01001091 samsung = kzalloc(sizeof(*samsung), GFP_KERNEL);
1092 if (!samsung)
1093 return -ENOMEM;
1094
1095 mutex_init(&samsung->sabi_mutex);
Corentin Charyf34cd9c2011-11-26 11:00:00 +01001096 samsung->handle_backlight = true;
1097
1098#ifdef CONFIG_ACPI
1099 /* Don't handle backlight here if the acpi video already handle it */
1100 if (acpi_video_backlight_support()) {
1101 pr_info("Backlight controlled by ACPI video driver\n");
1102 samsung->handle_backlight = false;
1103 }
1104#endif
Corentin Charya6df4892011-11-26 10:59:58 +01001105
Corentin Chary5dea7a22011-11-26 10:59:59 +01001106 ret = samsung_platform_init(samsung);
1107 if (ret)
1108 goto error_platform;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001109
Corentin Chary5dea7a22011-11-26 10:59:59 +01001110 ret = samsung_sabi_init(samsung);
1111 if (ret)
1112 goto error_sabi;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001113
Corentin Chary5dea7a22011-11-26 10:59:59 +01001114 ret = samsung_sysfs_init(samsung);
1115 if (ret)
1116 goto error_sysfs;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001117
Corentin Chary5dea7a22011-11-26 10:59:59 +01001118 ret = samsung_backlight_init(samsung);
1119 if (ret)
1120 goto error_backlight;
Corentin Charya6df4892011-11-26 10:59:58 +01001121
Corentin Chary5dea7a22011-11-26 10:59:59 +01001122 ret = samsung_rfkill_init(samsung);
1123 if (ret)
1124 goto error_rfkill;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001125
Corentin Chary5dea7a22011-11-26 10:59:59 +01001126 samsung_platform_device = samsung->platform_device;
1127 return ret;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001128
Corentin Chary5dea7a22011-11-26 10:59:59 +01001129error_rfkill:
1130 samsung_backlight_exit(samsung);
1131error_backlight:
1132 samsung_sysfs_exit(samsung);
1133error_sysfs:
1134 samsung_sabi_exit(samsung);
1135error_sabi:
1136 samsung_platform_exit(samsung);
1137error_platform:
Corentin Charya6df4892011-11-26 10:59:58 +01001138 kfree(samsung);
Corentin Chary5dea7a22011-11-26 10:59:59 +01001139 return ret;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001140}
1141
1142static void __exit samsung_exit(void)
1143{
Corentin Chary5dea7a22011-11-26 10:59:59 +01001144 struct samsung_laptop *samsung;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001145
Corentin Chary5dea7a22011-11-26 10:59:59 +01001146 samsung = platform_get_drvdata(samsung_platform_device);
Corentin Charya6df4892011-11-26 10:59:58 +01001147
Corentin Chary5dea7a22011-11-26 10:59:59 +01001148 samsung_rfkill_exit(samsung);
1149 samsung_backlight_exit(samsung);
1150 samsung_sysfs_exit(samsung);
1151 samsung_sabi_exit(samsung);
1152 samsung_platform_exit(samsung);
1153
Corentin Charya6df4892011-11-26 10:59:58 +01001154 kfree(samsung);
Corentin Chary5dea7a22011-11-26 10:59:59 +01001155 samsung_platform_device = NULL;
Greg Kroah-Hartman2d70b732011-03-11 12:41:19 -05001156}
1157
1158module_init(samsung_init);
1159module_exit(samsung_exit);
1160
1161MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@suse.de>");
1162MODULE_DESCRIPTION("Samsung Backlight driver");
1163MODULE_LICENSE("GPL");