1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0-only
2*4882a593Smuzhiyun /*
3*4882a593Smuzhiyun * Samsung Laptop driver
4*4882a593Smuzhiyun *
5*4882a593Smuzhiyun * Copyright (C) 2009,2011 Greg Kroah-Hartman (gregkh@suse.de)
6*4882a593Smuzhiyun * Copyright (C) 2009,2011 Novell Inc.
7*4882a593Smuzhiyun */
8*4882a593Smuzhiyun #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
9*4882a593Smuzhiyun
10*4882a593Smuzhiyun #include <linux/kernel.h>
11*4882a593Smuzhiyun #include <linux/init.h>
12*4882a593Smuzhiyun #include <linux/module.h>
13*4882a593Smuzhiyun #include <linux/delay.h>
14*4882a593Smuzhiyun #include <linux/pci.h>
15*4882a593Smuzhiyun #include <linux/backlight.h>
16*4882a593Smuzhiyun #include <linux/leds.h>
17*4882a593Smuzhiyun #include <linux/fb.h>
18*4882a593Smuzhiyun #include <linux/dmi.h>
19*4882a593Smuzhiyun #include <linux/platform_device.h>
20*4882a593Smuzhiyun #include <linux/rfkill.h>
21*4882a593Smuzhiyun #include <linux/acpi.h>
22*4882a593Smuzhiyun #include <linux/seq_file.h>
23*4882a593Smuzhiyun #include <linux/debugfs.h>
24*4882a593Smuzhiyun #include <linux/ctype.h>
25*4882a593Smuzhiyun #include <linux/efi.h>
26*4882a593Smuzhiyun #include <linux/suspend.h>
27*4882a593Smuzhiyun #include <acpi/video.h>
28*4882a593Smuzhiyun
29*4882a593Smuzhiyun /*
30*4882a593Smuzhiyun * This driver is needed because a number of Samsung laptops do not hook
31*4882a593Smuzhiyun * their control settings through ACPI. So we have to poke around in the
32*4882a593Smuzhiyun * BIOS to do things like brightness values, and "special" key controls.
33*4882a593Smuzhiyun */
34*4882a593Smuzhiyun
35*4882a593Smuzhiyun /*
36*4882a593Smuzhiyun * We have 0 - 8 as valid brightness levels. The specs say that level 0 should
37*4882a593Smuzhiyun * be reserved by the BIOS (which really doesn't make much sense), we tell
38*4882a593Smuzhiyun * userspace that the value is 0 - 7 and then just tell the hardware 1 - 8
39*4882a593Smuzhiyun */
40*4882a593Smuzhiyun #define MAX_BRIGHT 0x07
41*4882a593Smuzhiyun
42*4882a593Smuzhiyun
43*4882a593Smuzhiyun #define SABI_IFACE_MAIN 0x00
44*4882a593Smuzhiyun #define SABI_IFACE_SUB 0x02
45*4882a593Smuzhiyun #define SABI_IFACE_COMPLETE 0x04
46*4882a593Smuzhiyun #define SABI_IFACE_DATA 0x05
47*4882a593Smuzhiyun
48*4882a593Smuzhiyun #define WL_STATUS_WLAN 0x0
49*4882a593Smuzhiyun #define WL_STATUS_BT 0x2
50*4882a593Smuzhiyun
51*4882a593Smuzhiyun /* Structure get/set data using sabi */
52*4882a593Smuzhiyun struct sabi_data {
53*4882a593Smuzhiyun union {
54*4882a593Smuzhiyun struct {
55*4882a593Smuzhiyun u32 d0;
56*4882a593Smuzhiyun u32 d1;
57*4882a593Smuzhiyun u16 d2;
58*4882a593Smuzhiyun u8 d3;
59*4882a593Smuzhiyun };
60*4882a593Smuzhiyun u8 data[11];
61*4882a593Smuzhiyun };
62*4882a593Smuzhiyun };
63*4882a593Smuzhiyun
64*4882a593Smuzhiyun struct sabi_header_offsets {
65*4882a593Smuzhiyun u8 port;
66*4882a593Smuzhiyun u8 re_mem;
67*4882a593Smuzhiyun u8 iface_func;
68*4882a593Smuzhiyun u8 en_mem;
69*4882a593Smuzhiyun u8 data_offset;
70*4882a593Smuzhiyun u8 data_segment;
71*4882a593Smuzhiyun };
72*4882a593Smuzhiyun
73*4882a593Smuzhiyun struct sabi_commands {
74*4882a593Smuzhiyun /*
75*4882a593Smuzhiyun * Brightness is 0 - 8, as described above.
76*4882a593Smuzhiyun * Value 0 is for the BIOS to use
77*4882a593Smuzhiyun */
78*4882a593Smuzhiyun u16 get_brightness;
79*4882a593Smuzhiyun u16 set_brightness;
80*4882a593Smuzhiyun
81*4882a593Smuzhiyun /*
82*4882a593Smuzhiyun * first byte:
83*4882a593Smuzhiyun * 0x00 - wireless is off
84*4882a593Smuzhiyun * 0x01 - wireless is on
85*4882a593Smuzhiyun * second byte:
86*4882a593Smuzhiyun * 0x02 - 3G is off
87*4882a593Smuzhiyun * 0x03 - 3G is on
88*4882a593Smuzhiyun * TODO, verify 3G is correct, that doesn't seem right...
89*4882a593Smuzhiyun */
90*4882a593Smuzhiyun u16 get_wireless_button;
91*4882a593Smuzhiyun u16 set_wireless_button;
92*4882a593Smuzhiyun
93*4882a593Smuzhiyun /* 0 is off, 1 is on */
94*4882a593Smuzhiyun u16 get_backlight;
95*4882a593Smuzhiyun u16 set_backlight;
96*4882a593Smuzhiyun
97*4882a593Smuzhiyun /*
98*4882a593Smuzhiyun * 0x80 or 0x00 - no action
99*4882a593Smuzhiyun * 0x81 - recovery key pressed
100*4882a593Smuzhiyun */
101*4882a593Smuzhiyun u16 get_recovery_mode;
102*4882a593Smuzhiyun u16 set_recovery_mode;
103*4882a593Smuzhiyun
104*4882a593Smuzhiyun /*
105*4882a593Smuzhiyun * on seclinux: 0 is low, 1 is high,
106*4882a593Smuzhiyun * on swsmi: 0 is normal, 1 is silent, 2 is turbo
107*4882a593Smuzhiyun */
108*4882a593Smuzhiyun u16 get_performance_level;
109*4882a593Smuzhiyun u16 set_performance_level;
110*4882a593Smuzhiyun
111*4882a593Smuzhiyun /* 0x80 is off, 0x81 is on */
112*4882a593Smuzhiyun u16 get_battery_life_extender;
113*4882a593Smuzhiyun u16 set_battery_life_extender;
114*4882a593Smuzhiyun
115*4882a593Smuzhiyun /* 0x80 is off, 0x81 is on */
116*4882a593Smuzhiyun u16 get_usb_charge;
117*4882a593Smuzhiyun u16 set_usb_charge;
118*4882a593Smuzhiyun
119*4882a593Smuzhiyun /* the first byte is for bluetooth and the third one is for wlan */
120*4882a593Smuzhiyun u16 get_wireless_status;
121*4882a593Smuzhiyun u16 set_wireless_status;
122*4882a593Smuzhiyun
123*4882a593Smuzhiyun /* 0x80 is off, 0x81 is on */
124*4882a593Smuzhiyun u16 get_lid_handling;
125*4882a593Smuzhiyun u16 set_lid_handling;
126*4882a593Smuzhiyun
127*4882a593Smuzhiyun /* 0x81 to read, (0x82 | level << 8) to set, 0xaabb to enable */
128*4882a593Smuzhiyun u16 kbd_backlight;
129*4882a593Smuzhiyun
130*4882a593Smuzhiyun /*
131*4882a593Smuzhiyun * Tell the BIOS that Linux is running on this machine.
132*4882a593Smuzhiyun * 81 is on, 80 is off
133*4882a593Smuzhiyun */
134*4882a593Smuzhiyun u16 set_linux;
135*4882a593Smuzhiyun };
136*4882a593Smuzhiyun
137*4882a593Smuzhiyun struct sabi_performance_level {
138*4882a593Smuzhiyun const char *name;
139*4882a593Smuzhiyun u16 value;
140*4882a593Smuzhiyun };
141*4882a593Smuzhiyun
142*4882a593Smuzhiyun struct sabi_config {
143*4882a593Smuzhiyun int sabi_version;
144*4882a593Smuzhiyun const char *test_string;
145*4882a593Smuzhiyun u16 main_function;
146*4882a593Smuzhiyun const struct sabi_header_offsets header_offsets;
147*4882a593Smuzhiyun const struct sabi_commands commands;
148*4882a593Smuzhiyun const struct sabi_performance_level performance_levels[4];
149*4882a593Smuzhiyun u8 min_brightness;
150*4882a593Smuzhiyun u8 max_brightness;
151*4882a593Smuzhiyun };
152*4882a593Smuzhiyun
153*4882a593Smuzhiyun static const struct sabi_config sabi_configs[] = {
154*4882a593Smuzhiyun {
155*4882a593Smuzhiyun /* I don't know if it is really 2, but it it is
156*4882a593Smuzhiyun * less than 3 anyway */
157*4882a593Smuzhiyun .sabi_version = 2,
158*4882a593Smuzhiyun
159*4882a593Smuzhiyun .test_string = "SECLINUX",
160*4882a593Smuzhiyun
161*4882a593Smuzhiyun .main_function = 0x4c49,
162*4882a593Smuzhiyun
163*4882a593Smuzhiyun .header_offsets = {
164*4882a593Smuzhiyun .port = 0x00,
165*4882a593Smuzhiyun .re_mem = 0x02,
166*4882a593Smuzhiyun .iface_func = 0x03,
167*4882a593Smuzhiyun .en_mem = 0x04,
168*4882a593Smuzhiyun .data_offset = 0x05,
169*4882a593Smuzhiyun .data_segment = 0x07,
170*4882a593Smuzhiyun },
171*4882a593Smuzhiyun
172*4882a593Smuzhiyun .commands = {
173*4882a593Smuzhiyun .get_brightness = 0x00,
174*4882a593Smuzhiyun .set_brightness = 0x01,
175*4882a593Smuzhiyun
176*4882a593Smuzhiyun .get_wireless_button = 0x02,
177*4882a593Smuzhiyun .set_wireless_button = 0x03,
178*4882a593Smuzhiyun
179*4882a593Smuzhiyun .get_backlight = 0x04,
180*4882a593Smuzhiyun .set_backlight = 0x05,
181*4882a593Smuzhiyun
182*4882a593Smuzhiyun .get_recovery_mode = 0x06,
183*4882a593Smuzhiyun .set_recovery_mode = 0x07,
184*4882a593Smuzhiyun
185*4882a593Smuzhiyun .get_performance_level = 0x08,
186*4882a593Smuzhiyun .set_performance_level = 0x09,
187*4882a593Smuzhiyun
188*4882a593Smuzhiyun .get_battery_life_extender = 0xFFFF,
189*4882a593Smuzhiyun .set_battery_life_extender = 0xFFFF,
190*4882a593Smuzhiyun
191*4882a593Smuzhiyun .get_usb_charge = 0xFFFF,
192*4882a593Smuzhiyun .set_usb_charge = 0xFFFF,
193*4882a593Smuzhiyun
194*4882a593Smuzhiyun .get_wireless_status = 0xFFFF,
195*4882a593Smuzhiyun .set_wireless_status = 0xFFFF,
196*4882a593Smuzhiyun
197*4882a593Smuzhiyun .get_lid_handling = 0xFFFF,
198*4882a593Smuzhiyun .set_lid_handling = 0xFFFF,
199*4882a593Smuzhiyun
200*4882a593Smuzhiyun .kbd_backlight = 0xFFFF,
201*4882a593Smuzhiyun
202*4882a593Smuzhiyun .set_linux = 0x0a,
203*4882a593Smuzhiyun },
204*4882a593Smuzhiyun
205*4882a593Smuzhiyun .performance_levels = {
206*4882a593Smuzhiyun {
207*4882a593Smuzhiyun .name = "silent",
208*4882a593Smuzhiyun .value = 0,
209*4882a593Smuzhiyun },
210*4882a593Smuzhiyun {
211*4882a593Smuzhiyun .name = "normal",
212*4882a593Smuzhiyun .value = 1,
213*4882a593Smuzhiyun },
214*4882a593Smuzhiyun { },
215*4882a593Smuzhiyun },
216*4882a593Smuzhiyun .min_brightness = 1,
217*4882a593Smuzhiyun .max_brightness = 8,
218*4882a593Smuzhiyun },
219*4882a593Smuzhiyun {
220*4882a593Smuzhiyun .sabi_version = 3,
221*4882a593Smuzhiyun
222*4882a593Smuzhiyun .test_string = "SwSmi@",
223*4882a593Smuzhiyun
224*4882a593Smuzhiyun .main_function = 0x5843,
225*4882a593Smuzhiyun
226*4882a593Smuzhiyun .header_offsets = {
227*4882a593Smuzhiyun .port = 0x00,
228*4882a593Smuzhiyun .re_mem = 0x04,
229*4882a593Smuzhiyun .iface_func = 0x02,
230*4882a593Smuzhiyun .en_mem = 0x03,
231*4882a593Smuzhiyun .data_offset = 0x05,
232*4882a593Smuzhiyun .data_segment = 0x07,
233*4882a593Smuzhiyun },
234*4882a593Smuzhiyun
235*4882a593Smuzhiyun .commands = {
236*4882a593Smuzhiyun .get_brightness = 0x10,
237*4882a593Smuzhiyun .set_brightness = 0x11,
238*4882a593Smuzhiyun
239*4882a593Smuzhiyun .get_wireless_button = 0x12,
240*4882a593Smuzhiyun .set_wireless_button = 0x13,
241*4882a593Smuzhiyun
242*4882a593Smuzhiyun .get_backlight = 0x2d,
243*4882a593Smuzhiyun .set_backlight = 0x2e,
244*4882a593Smuzhiyun
245*4882a593Smuzhiyun .get_recovery_mode = 0xff,
246*4882a593Smuzhiyun .set_recovery_mode = 0xff,
247*4882a593Smuzhiyun
248*4882a593Smuzhiyun .get_performance_level = 0x31,
249*4882a593Smuzhiyun .set_performance_level = 0x32,
250*4882a593Smuzhiyun
251*4882a593Smuzhiyun .get_battery_life_extender = 0x65,
252*4882a593Smuzhiyun .set_battery_life_extender = 0x66,
253*4882a593Smuzhiyun
254*4882a593Smuzhiyun .get_usb_charge = 0x67,
255*4882a593Smuzhiyun .set_usb_charge = 0x68,
256*4882a593Smuzhiyun
257*4882a593Smuzhiyun .get_wireless_status = 0x69,
258*4882a593Smuzhiyun .set_wireless_status = 0x6a,
259*4882a593Smuzhiyun
260*4882a593Smuzhiyun .get_lid_handling = 0x6d,
261*4882a593Smuzhiyun .set_lid_handling = 0x6e,
262*4882a593Smuzhiyun
263*4882a593Smuzhiyun .kbd_backlight = 0x78,
264*4882a593Smuzhiyun
265*4882a593Smuzhiyun .set_linux = 0xff,
266*4882a593Smuzhiyun },
267*4882a593Smuzhiyun
268*4882a593Smuzhiyun .performance_levels = {
269*4882a593Smuzhiyun {
270*4882a593Smuzhiyun .name = "normal",
271*4882a593Smuzhiyun .value = 0,
272*4882a593Smuzhiyun },
273*4882a593Smuzhiyun {
274*4882a593Smuzhiyun .name = "silent",
275*4882a593Smuzhiyun .value = 1,
276*4882a593Smuzhiyun },
277*4882a593Smuzhiyun {
278*4882a593Smuzhiyun .name = "overclock",
279*4882a593Smuzhiyun .value = 2,
280*4882a593Smuzhiyun },
281*4882a593Smuzhiyun { },
282*4882a593Smuzhiyun },
283*4882a593Smuzhiyun .min_brightness = 0,
284*4882a593Smuzhiyun .max_brightness = 8,
285*4882a593Smuzhiyun },
286*4882a593Smuzhiyun { },
287*4882a593Smuzhiyun };
288*4882a593Smuzhiyun
289*4882a593Smuzhiyun /*
290*4882a593Smuzhiyun * samsung-laptop/ - debugfs root directory
291*4882a593Smuzhiyun * f0000_segment - dump f0000 segment
292*4882a593Smuzhiyun * command - current command
293*4882a593Smuzhiyun * data - current data
294*4882a593Smuzhiyun * d0, d1, d2, d3 - data fields
295*4882a593Smuzhiyun * call - call SABI using command and data
296*4882a593Smuzhiyun *
297*4882a593Smuzhiyun * This allow to call arbitrary sabi commands wihout
298*4882a593Smuzhiyun * modifying the driver at all.
299*4882a593Smuzhiyun * For example, setting the keyboard backlight brightness to 5
300*4882a593Smuzhiyun *
301*4882a593Smuzhiyun * echo 0x78 > command
302*4882a593Smuzhiyun * echo 0x0582 > d0
303*4882a593Smuzhiyun * echo 0 > d1
304*4882a593Smuzhiyun * echo 0 > d2
305*4882a593Smuzhiyun * echo 0 > d3
306*4882a593Smuzhiyun * cat call
307*4882a593Smuzhiyun */
308*4882a593Smuzhiyun
309*4882a593Smuzhiyun struct samsung_laptop_debug {
310*4882a593Smuzhiyun struct dentry *root;
311*4882a593Smuzhiyun struct sabi_data data;
312*4882a593Smuzhiyun u16 command;
313*4882a593Smuzhiyun
314*4882a593Smuzhiyun struct debugfs_blob_wrapper f0000_wrapper;
315*4882a593Smuzhiyun struct debugfs_blob_wrapper data_wrapper;
316*4882a593Smuzhiyun struct debugfs_blob_wrapper sdiag_wrapper;
317*4882a593Smuzhiyun };
318*4882a593Smuzhiyun
319*4882a593Smuzhiyun struct samsung_laptop;
320*4882a593Smuzhiyun
321*4882a593Smuzhiyun struct samsung_rfkill {
322*4882a593Smuzhiyun struct samsung_laptop *samsung;
323*4882a593Smuzhiyun struct rfkill *rfkill;
324*4882a593Smuzhiyun enum rfkill_type type;
325*4882a593Smuzhiyun };
326*4882a593Smuzhiyun
327*4882a593Smuzhiyun struct samsung_laptop {
328*4882a593Smuzhiyun const struct sabi_config *config;
329*4882a593Smuzhiyun
330*4882a593Smuzhiyun void __iomem *sabi;
331*4882a593Smuzhiyun void __iomem *sabi_iface;
332*4882a593Smuzhiyun void __iomem *f0000_segment;
333*4882a593Smuzhiyun
334*4882a593Smuzhiyun struct mutex sabi_mutex;
335*4882a593Smuzhiyun
336*4882a593Smuzhiyun struct platform_device *platform_device;
337*4882a593Smuzhiyun struct backlight_device *backlight_device;
338*4882a593Smuzhiyun
339*4882a593Smuzhiyun struct samsung_rfkill wlan;
340*4882a593Smuzhiyun struct samsung_rfkill bluetooth;
341*4882a593Smuzhiyun
342*4882a593Smuzhiyun struct led_classdev kbd_led;
343*4882a593Smuzhiyun int kbd_led_wk;
344*4882a593Smuzhiyun struct workqueue_struct *led_workqueue;
345*4882a593Smuzhiyun struct work_struct kbd_led_work;
346*4882a593Smuzhiyun
347*4882a593Smuzhiyun struct samsung_laptop_debug debug;
348*4882a593Smuzhiyun struct samsung_quirks *quirks;
349*4882a593Smuzhiyun
350*4882a593Smuzhiyun struct notifier_block pm_nb;
351*4882a593Smuzhiyun
352*4882a593Smuzhiyun bool handle_backlight;
353*4882a593Smuzhiyun bool has_stepping_quirk;
354*4882a593Smuzhiyun
355*4882a593Smuzhiyun char sdiag[64];
356*4882a593Smuzhiyun };
357*4882a593Smuzhiyun
358*4882a593Smuzhiyun struct samsung_quirks {
359*4882a593Smuzhiyun bool broken_acpi_video;
360*4882a593Smuzhiyun bool four_kbd_backlight_levels;
361*4882a593Smuzhiyun bool enable_kbd_backlight;
362*4882a593Smuzhiyun bool use_native_backlight;
363*4882a593Smuzhiyun bool lid_handling;
364*4882a593Smuzhiyun };
365*4882a593Smuzhiyun
366*4882a593Smuzhiyun static struct samsung_quirks samsung_unknown = {};
367*4882a593Smuzhiyun
368*4882a593Smuzhiyun static struct samsung_quirks samsung_broken_acpi_video = {
369*4882a593Smuzhiyun .broken_acpi_video = true,
370*4882a593Smuzhiyun };
371*4882a593Smuzhiyun
372*4882a593Smuzhiyun static struct samsung_quirks samsung_use_native_backlight = {
373*4882a593Smuzhiyun .use_native_backlight = true,
374*4882a593Smuzhiyun };
375*4882a593Smuzhiyun
376*4882a593Smuzhiyun static struct samsung_quirks samsung_np740u3e = {
377*4882a593Smuzhiyun .four_kbd_backlight_levels = true,
378*4882a593Smuzhiyun .enable_kbd_backlight = true,
379*4882a593Smuzhiyun };
380*4882a593Smuzhiyun
381*4882a593Smuzhiyun static struct samsung_quirks samsung_lid_handling = {
382*4882a593Smuzhiyun .lid_handling = true,
383*4882a593Smuzhiyun };
384*4882a593Smuzhiyun
385*4882a593Smuzhiyun static bool force;
386*4882a593Smuzhiyun module_param(force, bool, 0);
387*4882a593Smuzhiyun MODULE_PARM_DESC(force,
388*4882a593Smuzhiyun "Disable the DMI check and forces the driver to be loaded");
389*4882a593Smuzhiyun
390*4882a593Smuzhiyun static bool debug;
391*4882a593Smuzhiyun module_param(debug, bool, S_IRUGO | S_IWUSR);
392*4882a593Smuzhiyun MODULE_PARM_DESC(debug, "Debug enabled or not");
393*4882a593Smuzhiyun
sabi_command(struct samsung_laptop * samsung,u16 command,struct sabi_data * in,struct sabi_data * out)394*4882a593Smuzhiyun static int sabi_command(struct samsung_laptop *samsung, u16 command,
395*4882a593Smuzhiyun struct sabi_data *in,
396*4882a593Smuzhiyun struct sabi_data *out)
397*4882a593Smuzhiyun {
398*4882a593Smuzhiyun const struct sabi_config *config = samsung->config;
399*4882a593Smuzhiyun int ret = 0;
400*4882a593Smuzhiyun u16 port = readw(samsung->sabi + config->header_offsets.port);
401*4882a593Smuzhiyun u8 complete, iface_data;
402*4882a593Smuzhiyun
403*4882a593Smuzhiyun mutex_lock(&samsung->sabi_mutex);
404*4882a593Smuzhiyun
405*4882a593Smuzhiyun if (debug) {
406*4882a593Smuzhiyun if (in)
407*4882a593Smuzhiyun pr_info("SABI command:0x%04x "
408*4882a593Smuzhiyun "data:{0x%08x, 0x%08x, 0x%04x, 0x%02x}",
409*4882a593Smuzhiyun command, in->d0, in->d1, in->d2, in->d3);
410*4882a593Smuzhiyun else
411*4882a593Smuzhiyun pr_info("SABI command:0x%04x", command);
412*4882a593Smuzhiyun }
413*4882a593Smuzhiyun
414*4882a593Smuzhiyun /* enable memory to be able to write to it */
415*4882a593Smuzhiyun outb(readb(samsung->sabi + config->header_offsets.en_mem), port);
416*4882a593Smuzhiyun
417*4882a593Smuzhiyun /* write out the command */
418*4882a593Smuzhiyun writew(config->main_function, samsung->sabi_iface + SABI_IFACE_MAIN);
419*4882a593Smuzhiyun writew(command, samsung->sabi_iface + SABI_IFACE_SUB);
420*4882a593Smuzhiyun writeb(0, samsung->sabi_iface + SABI_IFACE_COMPLETE);
421*4882a593Smuzhiyun if (in) {
422*4882a593Smuzhiyun writel(in->d0, samsung->sabi_iface + SABI_IFACE_DATA);
423*4882a593Smuzhiyun writel(in->d1, samsung->sabi_iface + SABI_IFACE_DATA + 4);
424*4882a593Smuzhiyun writew(in->d2, samsung->sabi_iface + SABI_IFACE_DATA + 8);
425*4882a593Smuzhiyun writeb(in->d3, samsung->sabi_iface + SABI_IFACE_DATA + 10);
426*4882a593Smuzhiyun }
427*4882a593Smuzhiyun outb(readb(samsung->sabi + config->header_offsets.iface_func), port);
428*4882a593Smuzhiyun
429*4882a593Smuzhiyun /* write protect memory to make it safe */
430*4882a593Smuzhiyun outb(readb(samsung->sabi + config->header_offsets.re_mem), port);
431*4882a593Smuzhiyun
432*4882a593Smuzhiyun /* see if the command actually succeeded */
433*4882a593Smuzhiyun complete = readb(samsung->sabi_iface + SABI_IFACE_COMPLETE);
434*4882a593Smuzhiyun iface_data = readb(samsung->sabi_iface + SABI_IFACE_DATA);
435*4882a593Smuzhiyun
436*4882a593Smuzhiyun /* iface_data = 0xFF happens when a command is not known
437*4882a593Smuzhiyun * so we only add a warning in debug mode since we will
438*4882a593Smuzhiyun * probably issue some unknown command at startup to find
439*4882a593Smuzhiyun * out which features are supported */
440*4882a593Smuzhiyun if (complete != 0xaa || (iface_data == 0xff && debug))
441*4882a593Smuzhiyun pr_warn("SABI command 0x%04x failed with"
442*4882a593Smuzhiyun " completion flag 0x%02x and interface data 0x%02x",
443*4882a593Smuzhiyun command, complete, iface_data);
444*4882a593Smuzhiyun
445*4882a593Smuzhiyun if (complete != 0xaa || iface_data == 0xff) {
446*4882a593Smuzhiyun ret = -EINVAL;
447*4882a593Smuzhiyun goto exit;
448*4882a593Smuzhiyun }
449*4882a593Smuzhiyun
450*4882a593Smuzhiyun if (out) {
451*4882a593Smuzhiyun out->d0 = readl(samsung->sabi_iface + SABI_IFACE_DATA);
452*4882a593Smuzhiyun out->d1 = readl(samsung->sabi_iface + SABI_IFACE_DATA + 4);
453*4882a593Smuzhiyun out->d2 = readw(samsung->sabi_iface + SABI_IFACE_DATA + 2);
454*4882a593Smuzhiyun out->d3 = readb(samsung->sabi_iface + SABI_IFACE_DATA + 1);
455*4882a593Smuzhiyun }
456*4882a593Smuzhiyun
457*4882a593Smuzhiyun if (debug && out) {
458*4882a593Smuzhiyun pr_info("SABI return data:{0x%08x, 0x%08x, 0x%04x, 0x%02x}",
459*4882a593Smuzhiyun out->d0, out->d1, out->d2, out->d3);
460*4882a593Smuzhiyun }
461*4882a593Smuzhiyun
462*4882a593Smuzhiyun exit:
463*4882a593Smuzhiyun mutex_unlock(&samsung->sabi_mutex);
464*4882a593Smuzhiyun return ret;
465*4882a593Smuzhiyun }
466*4882a593Smuzhiyun
467*4882a593Smuzhiyun /* simple wrappers usable with most commands */
sabi_set_commandb(struct samsung_laptop * samsung,u16 command,u8 data)468*4882a593Smuzhiyun static int sabi_set_commandb(struct samsung_laptop *samsung,
469*4882a593Smuzhiyun u16 command, u8 data)
470*4882a593Smuzhiyun {
471*4882a593Smuzhiyun struct sabi_data in = { { { .d0 = 0, .d1 = 0, .d2 = 0, .d3 = 0 } } };
472*4882a593Smuzhiyun
473*4882a593Smuzhiyun in.data[0] = data;
474*4882a593Smuzhiyun return sabi_command(samsung, command, &in, NULL);
475*4882a593Smuzhiyun }
476*4882a593Smuzhiyun
read_brightness(struct samsung_laptop * samsung)477*4882a593Smuzhiyun static int read_brightness(struct samsung_laptop *samsung)
478*4882a593Smuzhiyun {
479*4882a593Smuzhiyun const struct sabi_config *config = samsung->config;
480*4882a593Smuzhiyun const struct sabi_commands *commands = &samsung->config->commands;
481*4882a593Smuzhiyun struct sabi_data sretval;
482*4882a593Smuzhiyun int user_brightness = 0;
483*4882a593Smuzhiyun int retval;
484*4882a593Smuzhiyun
485*4882a593Smuzhiyun retval = sabi_command(samsung, commands->get_brightness,
486*4882a593Smuzhiyun NULL, &sretval);
487*4882a593Smuzhiyun if (retval)
488*4882a593Smuzhiyun return retval;
489*4882a593Smuzhiyun
490*4882a593Smuzhiyun user_brightness = sretval.data[0];
491*4882a593Smuzhiyun if (user_brightness > config->min_brightness)
492*4882a593Smuzhiyun user_brightness -= config->min_brightness;
493*4882a593Smuzhiyun else
494*4882a593Smuzhiyun user_brightness = 0;
495*4882a593Smuzhiyun
496*4882a593Smuzhiyun return user_brightness;
497*4882a593Smuzhiyun }
498*4882a593Smuzhiyun
set_brightness(struct samsung_laptop * samsung,u8 user_brightness)499*4882a593Smuzhiyun static void set_brightness(struct samsung_laptop *samsung, u8 user_brightness)
500*4882a593Smuzhiyun {
501*4882a593Smuzhiyun const struct sabi_config *config = samsung->config;
502*4882a593Smuzhiyun const struct sabi_commands *commands = &samsung->config->commands;
503*4882a593Smuzhiyun u8 user_level = user_brightness + config->min_brightness;
504*4882a593Smuzhiyun
505*4882a593Smuzhiyun if (samsung->has_stepping_quirk && user_level != 0) {
506*4882a593Smuzhiyun /*
507*4882a593Smuzhiyun * short circuit if the specified level is what's already set
508*4882a593Smuzhiyun * to prevent the screen from flickering needlessly
509*4882a593Smuzhiyun */
510*4882a593Smuzhiyun if (user_brightness == read_brightness(samsung))
511*4882a593Smuzhiyun return;
512*4882a593Smuzhiyun
513*4882a593Smuzhiyun sabi_set_commandb(samsung, commands->set_brightness, 0);
514*4882a593Smuzhiyun }
515*4882a593Smuzhiyun
516*4882a593Smuzhiyun sabi_set_commandb(samsung, commands->set_brightness, user_level);
517*4882a593Smuzhiyun }
518*4882a593Smuzhiyun
get_brightness(struct backlight_device * bd)519*4882a593Smuzhiyun static int get_brightness(struct backlight_device *bd)
520*4882a593Smuzhiyun {
521*4882a593Smuzhiyun struct samsung_laptop *samsung = bl_get_data(bd);
522*4882a593Smuzhiyun
523*4882a593Smuzhiyun return read_brightness(samsung);
524*4882a593Smuzhiyun }
525*4882a593Smuzhiyun
check_for_stepping_quirk(struct samsung_laptop * samsung)526*4882a593Smuzhiyun static void check_for_stepping_quirk(struct samsung_laptop *samsung)
527*4882a593Smuzhiyun {
528*4882a593Smuzhiyun int initial_level;
529*4882a593Smuzhiyun int check_level;
530*4882a593Smuzhiyun int orig_level = read_brightness(samsung);
531*4882a593Smuzhiyun
532*4882a593Smuzhiyun /*
533*4882a593Smuzhiyun * Some laptops exhibit the strange behaviour of stepping toward
534*4882a593Smuzhiyun * (rather than setting) the brightness except when changing to/from
535*4882a593Smuzhiyun * brightness level 0. This behaviour is checked for here and worked
536*4882a593Smuzhiyun * around in set_brightness.
537*4882a593Smuzhiyun */
538*4882a593Smuzhiyun
539*4882a593Smuzhiyun if (orig_level == 0)
540*4882a593Smuzhiyun set_brightness(samsung, 1);
541*4882a593Smuzhiyun
542*4882a593Smuzhiyun initial_level = read_brightness(samsung);
543*4882a593Smuzhiyun
544*4882a593Smuzhiyun if (initial_level <= 2)
545*4882a593Smuzhiyun check_level = initial_level + 2;
546*4882a593Smuzhiyun else
547*4882a593Smuzhiyun check_level = initial_level - 2;
548*4882a593Smuzhiyun
549*4882a593Smuzhiyun samsung->has_stepping_quirk = false;
550*4882a593Smuzhiyun set_brightness(samsung, check_level);
551*4882a593Smuzhiyun
552*4882a593Smuzhiyun if (read_brightness(samsung) != check_level) {
553*4882a593Smuzhiyun samsung->has_stepping_quirk = true;
554*4882a593Smuzhiyun pr_info("enabled workaround for brightness stepping quirk\n");
555*4882a593Smuzhiyun }
556*4882a593Smuzhiyun
557*4882a593Smuzhiyun set_brightness(samsung, orig_level);
558*4882a593Smuzhiyun }
559*4882a593Smuzhiyun
update_status(struct backlight_device * bd)560*4882a593Smuzhiyun static int update_status(struct backlight_device *bd)
561*4882a593Smuzhiyun {
562*4882a593Smuzhiyun struct samsung_laptop *samsung = bl_get_data(bd);
563*4882a593Smuzhiyun const struct sabi_commands *commands = &samsung->config->commands;
564*4882a593Smuzhiyun
565*4882a593Smuzhiyun set_brightness(samsung, bd->props.brightness);
566*4882a593Smuzhiyun
567*4882a593Smuzhiyun if (bd->props.power == FB_BLANK_UNBLANK)
568*4882a593Smuzhiyun sabi_set_commandb(samsung, commands->set_backlight, 1);
569*4882a593Smuzhiyun else
570*4882a593Smuzhiyun sabi_set_commandb(samsung, commands->set_backlight, 0);
571*4882a593Smuzhiyun
572*4882a593Smuzhiyun return 0;
573*4882a593Smuzhiyun }
574*4882a593Smuzhiyun
575*4882a593Smuzhiyun static const struct backlight_ops backlight_ops = {
576*4882a593Smuzhiyun .get_brightness = get_brightness,
577*4882a593Smuzhiyun .update_status = update_status,
578*4882a593Smuzhiyun };
579*4882a593Smuzhiyun
seclinux_rfkill_set(void * data,bool blocked)580*4882a593Smuzhiyun static int seclinux_rfkill_set(void *data, bool blocked)
581*4882a593Smuzhiyun {
582*4882a593Smuzhiyun struct samsung_rfkill *srfkill = data;
583*4882a593Smuzhiyun struct samsung_laptop *samsung = srfkill->samsung;
584*4882a593Smuzhiyun const struct sabi_commands *commands = &samsung->config->commands;
585*4882a593Smuzhiyun
586*4882a593Smuzhiyun return sabi_set_commandb(samsung, commands->set_wireless_button,
587*4882a593Smuzhiyun !blocked);
588*4882a593Smuzhiyun }
589*4882a593Smuzhiyun
590*4882a593Smuzhiyun static const struct rfkill_ops seclinux_rfkill_ops = {
591*4882a593Smuzhiyun .set_block = seclinux_rfkill_set,
592*4882a593Smuzhiyun };
593*4882a593Smuzhiyun
swsmi_wireless_status(struct samsung_laptop * samsung,struct sabi_data * data)594*4882a593Smuzhiyun static int swsmi_wireless_status(struct samsung_laptop *samsung,
595*4882a593Smuzhiyun struct sabi_data *data)
596*4882a593Smuzhiyun {
597*4882a593Smuzhiyun const struct sabi_commands *commands = &samsung->config->commands;
598*4882a593Smuzhiyun
599*4882a593Smuzhiyun return sabi_command(samsung, commands->get_wireless_status,
600*4882a593Smuzhiyun NULL, data);
601*4882a593Smuzhiyun }
602*4882a593Smuzhiyun
swsmi_rfkill_set(void * priv,bool blocked)603*4882a593Smuzhiyun static int swsmi_rfkill_set(void *priv, bool blocked)
604*4882a593Smuzhiyun {
605*4882a593Smuzhiyun struct samsung_rfkill *srfkill = priv;
606*4882a593Smuzhiyun struct samsung_laptop *samsung = srfkill->samsung;
607*4882a593Smuzhiyun const struct sabi_commands *commands = &samsung->config->commands;
608*4882a593Smuzhiyun struct sabi_data data;
609*4882a593Smuzhiyun int ret, i;
610*4882a593Smuzhiyun
611*4882a593Smuzhiyun ret = swsmi_wireless_status(samsung, &data);
612*4882a593Smuzhiyun if (ret)
613*4882a593Smuzhiyun return ret;
614*4882a593Smuzhiyun
615*4882a593Smuzhiyun /* Don't set the state for non-present devices */
616*4882a593Smuzhiyun for (i = 0; i < 4; i++)
617*4882a593Smuzhiyun if (data.data[i] == 0x02)
618*4882a593Smuzhiyun data.data[1] = 0;
619*4882a593Smuzhiyun
620*4882a593Smuzhiyun if (srfkill->type == RFKILL_TYPE_WLAN)
621*4882a593Smuzhiyun data.data[WL_STATUS_WLAN] = !blocked;
622*4882a593Smuzhiyun else if (srfkill->type == RFKILL_TYPE_BLUETOOTH)
623*4882a593Smuzhiyun data.data[WL_STATUS_BT] = !blocked;
624*4882a593Smuzhiyun
625*4882a593Smuzhiyun return sabi_command(samsung, commands->set_wireless_status,
626*4882a593Smuzhiyun &data, &data);
627*4882a593Smuzhiyun }
628*4882a593Smuzhiyun
swsmi_rfkill_query(struct rfkill * rfkill,void * priv)629*4882a593Smuzhiyun static void swsmi_rfkill_query(struct rfkill *rfkill, void *priv)
630*4882a593Smuzhiyun {
631*4882a593Smuzhiyun struct samsung_rfkill *srfkill = priv;
632*4882a593Smuzhiyun struct samsung_laptop *samsung = srfkill->samsung;
633*4882a593Smuzhiyun struct sabi_data data;
634*4882a593Smuzhiyun int ret;
635*4882a593Smuzhiyun
636*4882a593Smuzhiyun ret = swsmi_wireless_status(samsung, &data);
637*4882a593Smuzhiyun if (ret)
638*4882a593Smuzhiyun return ;
639*4882a593Smuzhiyun
640*4882a593Smuzhiyun if (srfkill->type == RFKILL_TYPE_WLAN)
641*4882a593Smuzhiyun ret = data.data[WL_STATUS_WLAN];
642*4882a593Smuzhiyun else if (srfkill->type == RFKILL_TYPE_BLUETOOTH)
643*4882a593Smuzhiyun ret = data.data[WL_STATUS_BT];
644*4882a593Smuzhiyun else
645*4882a593Smuzhiyun return ;
646*4882a593Smuzhiyun
647*4882a593Smuzhiyun rfkill_set_sw_state(rfkill, !ret);
648*4882a593Smuzhiyun }
649*4882a593Smuzhiyun
650*4882a593Smuzhiyun static const struct rfkill_ops swsmi_rfkill_ops = {
651*4882a593Smuzhiyun .set_block = swsmi_rfkill_set,
652*4882a593Smuzhiyun .query = swsmi_rfkill_query,
653*4882a593Smuzhiyun };
654*4882a593Smuzhiyun
get_performance_level(struct device * dev,struct device_attribute * attr,char * buf)655*4882a593Smuzhiyun static ssize_t get_performance_level(struct device *dev,
656*4882a593Smuzhiyun struct device_attribute *attr, char *buf)
657*4882a593Smuzhiyun {
658*4882a593Smuzhiyun struct samsung_laptop *samsung = dev_get_drvdata(dev);
659*4882a593Smuzhiyun const struct sabi_config *config = samsung->config;
660*4882a593Smuzhiyun const struct sabi_commands *commands = &config->commands;
661*4882a593Smuzhiyun struct sabi_data sretval;
662*4882a593Smuzhiyun int retval;
663*4882a593Smuzhiyun int i;
664*4882a593Smuzhiyun
665*4882a593Smuzhiyun /* Read the state */
666*4882a593Smuzhiyun retval = sabi_command(samsung, commands->get_performance_level,
667*4882a593Smuzhiyun NULL, &sretval);
668*4882a593Smuzhiyun if (retval)
669*4882a593Smuzhiyun return retval;
670*4882a593Smuzhiyun
671*4882a593Smuzhiyun /* The logic is backwards, yeah, lots of fun... */
672*4882a593Smuzhiyun for (i = 0; config->performance_levels[i].name; ++i) {
673*4882a593Smuzhiyun if (sretval.data[0] == config->performance_levels[i].value)
674*4882a593Smuzhiyun return sprintf(buf, "%s\n", config->performance_levels[i].name);
675*4882a593Smuzhiyun }
676*4882a593Smuzhiyun return sprintf(buf, "%s\n", "unknown");
677*4882a593Smuzhiyun }
678*4882a593Smuzhiyun
set_performance_level(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)679*4882a593Smuzhiyun static ssize_t set_performance_level(struct device *dev,
680*4882a593Smuzhiyun struct device_attribute *attr, const char *buf,
681*4882a593Smuzhiyun size_t count)
682*4882a593Smuzhiyun {
683*4882a593Smuzhiyun struct samsung_laptop *samsung = dev_get_drvdata(dev);
684*4882a593Smuzhiyun const struct sabi_config *config = samsung->config;
685*4882a593Smuzhiyun const struct sabi_commands *commands = &config->commands;
686*4882a593Smuzhiyun int i;
687*4882a593Smuzhiyun
688*4882a593Smuzhiyun if (count < 1)
689*4882a593Smuzhiyun return count;
690*4882a593Smuzhiyun
691*4882a593Smuzhiyun for (i = 0; config->performance_levels[i].name; ++i) {
692*4882a593Smuzhiyun const struct sabi_performance_level *level =
693*4882a593Smuzhiyun &config->performance_levels[i];
694*4882a593Smuzhiyun if (!strncasecmp(level->name, buf, strlen(level->name))) {
695*4882a593Smuzhiyun sabi_set_commandb(samsung,
696*4882a593Smuzhiyun commands->set_performance_level,
697*4882a593Smuzhiyun level->value);
698*4882a593Smuzhiyun break;
699*4882a593Smuzhiyun }
700*4882a593Smuzhiyun }
701*4882a593Smuzhiyun
702*4882a593Smuzhiyun if (!config->performance_levels[i].name)
703*4882a593Smuzhiyun return -EINVAL;
704*4882a593Smuzhiyun
705*4882a593Smuzhiyun return count;
706*4882a593Smuzhiyun }
707*4882a593Smuzhiyun
708*4882a593Smuzhiyun static DEVICE_ATTR(performance_level, S_IWUSR | S_IRUGO,
709*4882a593Smuzhiyun get_performance_level, set_performance_level);
710*4882a593Smuzhiyun
read_battery_life_extender(struct samsung_laptop * samsung)711*4882a593Smuzhiyun static int read_battery_life_extender(struct samsung_laptop *samsung)
712*4882a593Smuzhiyun {
713*4882a593Smuzhiyun const struct sabi_commands *commands = &samsung->config->commands;
714*4882a593Smuzhiyun struct sabi_data data;
715*4882a593Smuzhiyun int retval;
716*4882a593Smuzhiyun
717*4882a593Smuzhiyun if (commands->get_battery_life_extender == 0xFFFF)
718*4882a593Smuzhiyun return -ENODEV;
719*4882a593Smuzhiyun
720*4882a593Smuzhiyun memset(&data, 0, sizeof(data));
721*4882a593Smuzhiyun data.data[0] = 0x80;
722*4882a593Smuzhiyun retval = sabi_command(samsung, commands->get_battery_life_extender,
723*4882a593Smuzhiyun &data, &data);
724*4882a593Smuzhiyun
725*4882a593Smuzhiyun if (retval)
726*4882a593Smuzhiyun return retval;
727*4882a593Smuzhiyun
728*4882a593Smuzhiyun if (data.data[0] != 0 && data.data[0] != 1)
729*4882a593Smuzhiyun return -ENODEV;
730*4882a593Smuzhiyun
731*4882a593Smuzhiyun return data.data[0];
732*4882a593Smuzhiyun }
733*4882a593Smuzhiyun
write_battery_life_extender(struct samsung_laptop * samsung,int enabled)734*4882a593Smuzhiyun static int write_battery_life_extender(struct samsung_laptop *samsung,
735*4882a593Smuzhiyun int enabled)
736*4882a593Smuzhiyun {
737*4882a593Smuzhiyun const struct sabi_commands *commands = &samsung->config->commands;
738*4882a593Smuzhiyun struct sabi_data data;
739*4882a593Smuzhiyun
740*4882a593Smuzhiyun memset(&data, 0, sizeof(data));
741*4882a593Smuzhiyun data.data[0] = 0x80 | enabled;
742*4882a593Smuzhiyun return sabi_command(samsung, commands->set_battery_life_extender,
743*4882a593Smuzhiyun &data, NULL);
744*4882a593Smuzhiyun }
745*4882a593Smuzhiyun
get_battery_life_extender(struct device * dev,struct device_attribute * attr,char * buf)746*4882a593Smuzhiyun static ssize_t get_battery_life_extender(struct device *dev,
747*4882a593Smuzhiyun struct device_attribute *attr,
748*4882a593Smuzhiyun char *buf)
749*4882a593Smuzhiyun {
750*4882a593Smuzhiyun struct samsung_laptop *samsung = dev_get_drvdata(dev);
751*4882a593Smuzhiyun int ret;
752*4882a593Smuzhiyun
753*4882a593Smuzhiyun ret = read_battery_life_extender(samsung);
754*4882a593Smuzhiyun if (ret < 0)
755*4882a593Smuzhiyun return ret;
756*4882a593Smuzhiyun
757*4882a593Smuzhiyun return sprintf(buf, "%d\n", ret);
758*4882a593Smuzhiyun }
759*4882a593Smuzhiyun
set_battery_life_extender(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)760*4882a593Smuzhiyun static ssize_t set_battery_life_extender(struct device *dev,
761*4882a593Smuzhiyun struct device_attribute *attr,
762*4882a593Smuzhiyun const char *buf, size_t count)
763*4882a593Smuzhiyun {
764*4882a593Smuzhiyun struct samsung_laptop *samsung = dev_get_drvdata(dev);
765*4882a593Smuzhiyun int ret, value;
766*4882a593Smuzhiyun
767*4882a593Smuzhiyun if (!count || kstrtoint(buf, 0, &value) != 0)
768*4882a593Smuzhiyun return -EINVAL;
769*4882a593Smuzhiyun
770*4882a593Smuzhiyun ret = write_battery_life_extender(samsung, !!value);
771*4882a593Smuzhiyun if (ret < 0)
772*4882a593Smuzhiyun return ret;
773*4882a593Smuzhiyun
774*4882a593Smuzhiyun return count;
775*4882a593Smuzhiyun }
776*4882a593Smuzhiyun
777*4882a593Smuzhiyun static DEVICE_ATTR(battery_life_extender, S_IWUSR | S_IRUGO,
778*4882a593Smuzhiyun get_battery_life_extender, set_battery_life_extender);
779*4882a593Smuzhiyun
read_usb_charge(struct samsung_laptop * samsung)780*4882a593Smuzhiyun static int read_usb_charge(struct samsung_laptop *samsung)
781*4882a593Smuzhiyun {
782*4882a593Smuzhiyun const struct sabi_commands *commands = &samsung->config->commands;
783*4882a593Smuzhiyun struct sabi_data data;
784*4882a593Smuzhiyun int retval;
785*4882a593Smuzhiyun
786*4882a593Smuzhiyun if (commands->get_usb_charge == 0xFFFF)
787*4882a593Smuzhiyun return -ENODEV;
788*4882a593Smuzhiyun
789*4882a593Smuzhiyun memset(&data, 0, sizeof(data));
790*4882a593Smuzhiyun data.data[0] = 0x80;
791*4882a593Smuzhiyun retval = sabi_command(samsung, commands->get_usb_charge,
792*4882a593Smuzhiyun &data, &data);
793*4882a593Smuzhiyun
794*4882a593Smuzhiyun if (retval)
795*4882a593Smuzhiyun return retval;
796*4882a593Smuzhiyun
797*4882a593Smuzhiyun if (data.data[0] != 0 && data.data[0] != 1)
798*4882a593Smuzhiyun return -ENODEV;
799*4882a593Smuzhiyun
800*4882a593Smuzhiyun return data.data[0];
801*4882a593Smuzhiyun }
802*4882a593Smuzhiyun
write_usb_charge(struct samsung_laptop * samsung,int enabled)803*4882a593Smuzhiyun static int write_usb_charge(struct samsung_laptop *samsung,
804*4882a593Smuzhiyun int enabled)
805*4882a593Smuzhiyun {
806*4882a593Smuzhiyun const struct sabi_commands *commands = &samsung->config->commands;
807*4882a593Smuzhiyun struct sabi_data data;
808*4882a593Smuzhiyun
809*4882a593Smuzhiyun memset(&data, 0, sizeof(data));
810*4882a593Smuzhiyun data.data[0] = 0x80 | enabled;
811*4882a593Smuzhiyun return sabi_command(samsung, commands->set_usb_charge,
812*4882a593Smuzhiyun &data, NULL);
813*4882a593Smuzhiyun }
814*4882a593Smuzhiyun
get_usb_charge(struct device * dev,struct device_attribute * attr,char * buf)815*4882a593Smuzhiyun static ssize_t get_usb_charge(struct device *dev,
816*4882a593Smuzhiyun struct device_attribute *attr,
817*4882a593Smuzhiyun char *buf)
818*4882a593Smuzhiyun {
819*4882a593Smuzhiyun struct samsung_laptop *samsung = dev_get_drvdata(dev);
820*4882a593Smuzhiyun int ret;
821*4882a593Smuzhiyun
822*4882a593Smuzhiyun ret = read_usb_charge(samsung);
823*4882a593Smuzhiyun if (ret < 0)
824*4882a593Smuzhiyun return ret;
825*4882a593Smuzhiyun
826*4882a593Smuzhiyun return sprintf(buf, "%d\n", ret);
827*4882a593Smuzhiyun }
828*4882a593Smuzhiyun
set_usb_charge(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)829*4882a593Smuzhiyun static ssize_t set_usb_charge(struct device *dev,
830*4882a593Smuzhiyun struct device_attribute *attr,
831*4882a593Smuzhiyun const char *buf, size_t count)
832*4882a593Smuzhiyun {
833*4882a593Smuzhiyun struct samsung_laptop *samsung = dev_get_drvdata(dev);
834*4882a593Smuzhiyun int ret, value;
835*4882a593Smuzhiyun
836*4882a593Smuzhiyun if (!count || kstrtoint(buf, 0, &value) != 0)
837*4882a593Smuzhiyun return -EINVAL;
838*4882a593Smuzhiyun
839*4882a593Smuzhiyun ret = write_usb_charge(samsung, !!value);
840*4882a593Smuzhiyun if (ret < 0)
841*4882a593Smuzhiyun return ret;
842*4882a593Smuzhiyun
843*4882a593Smuzhiyun return count;
844*4882a593Smuzhiyun }
845*4882a593Smuzhiyun
846*4882a593Smuzhiyun static DEVICE_ATTR(usb_charge, S_IWUSR | S_IRUGO,
847*4882a593Smuzhiyun get_usb_charge, set_usb_charge);
848*4882a593Smuzhiyun
read_lid_handling(struct samsung_laptop * samsung)849*4882a593Smuzhiyun static int read_lid_handling(struct samsung_laptop *samsung)
850*4882a593Smuzhiyun {
851*4882a593Smuzhiyun const struct sabi_commands *commands = &samsung->config->commands;
852*4882a593Smuzhiyun struct sabi_data data;
853*4882a593Smuzhiyun int retval;
854*4882a593Smuzhiyun
855*4882a593Smuzhiyun if (commands->get_lid_handling == 0xFFFF)
856*4882a593Smuzhiyun return -ENODEV;
857*4882a593Smuzhiyun
858*4882a593Smuzhiyun memset(&data, 0, sizeof(data));
859*4882a593Smuzhiyun retval = sabi_command(samsung, commands->get_lid_handling,
860*4882a593Smuzhiyun &data, &data);
861*4882a593Smuzhiyun
862*4882a593Smuzhiyun if (retval)
863*4882a593Smuzhiyun return retval;
864*4882a593Smuzhiyun
865*4882a593Smuzhiyun return data.data[0] & 0x1;
866*4882a593Smuzhiyun }
867*4882a593Smuzhiyun
write_lid_handling(struct samsung_laptop * samsung,int enabled)868*4882a593Smuzhiyun static int write_lid_handling(struct samsung_laptop *samsung,
869*4882a593Smuzhiyun int enabled)
870*4882a593Smuzhiyun {
871*4882a593Smuzhiyun const struct sabi_commands *commands = &samsung->config->commands;
872*4882a593Smuzhiyun struct sabi_data data;
873*4882a593Smuzhiyun
874*4882a593Smuzhiyun memset(&data, 0, sizeof(data));
875*4882a593Smuzhiyun data.data[0] = 0x80 | enabled;
876*4882a593Smuzhiyun return sabi_command(samsung, commands->set_lid_handling,
877*4882a593Smuzhiyun &data, NULL);
878*4882a593Smuzhiyun }
879*4882a593Smuzhiyun
get_lid_handling(struct device * dev,struct device_attribute * attr,char * buf)880*4882a593Smuzhiyun static ssize_t get_lid_handling(struct device *dev,
881*4882a593Smuzhiyun struct device_attribute *attr,
882*4882a593Smuzhiyun char *buf)
883*4882a593Smuzhiyun {
884*4882a593Smuzhiyun struct samsung_laptop *samsung = dev_get_drvdata(dev);
885*4882a593Smuzhiyun int ret;
886*4882a593Smuzhiyun
887*4882a593Smuzhiyun ret = read_lid_handling(samsung);
888*4882a593Smuzhiyun if (ret < 0)
889*4882a593Smuzhiyun return ret;
890*4882a593Smuzhiyun
891*4882a593Smuzhiyun return sprintf(buf, "%d\n", ret);
892*4882a593Smuzhiyun }
893*4882a593Smuzhiyun
set_lid_handling(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)894*4882a593Smuzhiyun static ssize_t set_lid_handling(struct device *dev,
895*4882a593Smuzhiyun struct device_attribute *attr,
896*4882a593Smuzhiyun const char *buf, size_t count)
897*4882a593Smuzhiyun {
898*4882a593Smuzhiyun struct samsung_laptop *samsung = dev_get_drvdata(dev);
899*4882a593Smuzhiyun int ret, value;
900*4882a593Smuzhiyun
901*4882a593Smuzhiyun if (!count || kstrtoint(buf, 0, &value) != 0)
902*4882a593Smuzhiyun return -EINVAL;
903*4882a593Smuzhiyun
904*4882a593Smuzhiyun ret = write_lid_handling(samsung, !!value);
905*4882a593Smuzhiyun if (ret < 0)
906*4882a593Smuzhiyun return ret;
907*4882a593Smuzhiyun
908*4882a593Smuzhiyun return count;
909*4882a593Smuzhiyun }
910*4882a593Smuzhiyun
911*4882a593Smuzhiyun static DEVICE_ATTR(lid_handling, S_IWUSR | S_IRUGO,
912*4882a593Smuzhiyun get_lid_handling, set_lid_handling);
913*4882a593Smuzhiyun
914*4882a593Smuzhiyun static struct attribute *platform_attributes[] = {
915*4882a593Smuzhiyun &dev_attr_performance_level.attr,
916*4882a593Smuzhiyun &dev_attr_battery_life_extender.attr,
917*4882a593Smuzhiyun &dev_attr_usb_charge.attr,
918*4882a593Smuzhiyun &dev_attr_lid_handling.attr,
919*4882a593Smuzhiyun NULL
920*4882a593Smuzhiyun };
921*4882a593Smuzhiyun
find_signature(void __iomem * memcheck,const char * testStr)922*4882a593Smuzhiyun static int find_signature(void __iomem *memcheck, const char *testStr)
923*4882a593Smuzhiyun {
924*4882a593Smuzhiyun int i = 0;
925*4882a593Smuzhiyun int loca;
926*4882a593Smuzhiyun
927*4882a593Smuzhiyun for (loca = 0; loca < 0xffff; loca++) {
928*4882a593Smuzhiyun char temp = readb(memcheck + loca);
929*4882a593Smuzhiyun
930*4882a593Smuzhiyun if (temp == testStr[i]) {
931*4882a593Smuzhiyun if (i == strlen(testStr)-1)
932*4882a593Smuzhiyun break;
933*4882a593Smuzhiyun ++i;
934*4882a593Smuzhiyun } else {
935*4882a593Smuzhiyun i = 0;
936*4882a593Smuzhiyun }
937*4882a593Smuzhiyun }
938*4882a593Smuzhiyun return loca;
939*4882a593Smuzhiyun }
940*4882a593Smuzhiyun
samsung_rfkill_exit(struct samsung_laptop * samsung)941*4882a593Smuzhiyun static void samsung_rfkill_exit(struct samsung_laptop *samsung)
942*4882a593Smuzhiyun {
943*4882a593Smuzhiyun if (samsung->wlan.rfkill) {
944*4882a593Smuzhiyun rfkill_unregister(samsung->wlan.rfkill);
945*4882a593Smuzhiyun rfkill_destroy(samsung->wlan.rfkill);
946*4882a593Smuzhiyun samsung->wlan.rfkill = NULL;
947*4882a593Smuzhiyun }
948*4882a593Smuzhiyun if (samsung->bluetooth.rfkill) {
949*4882a593Smuzhiyun rfkill_unregister(samsung->bluetooth.rfkill);
950*4882a593Smuzhiyun rfkill_destroy(samsung->bluetooth.rfkill);
951*4882a593Smuzhiyun samsung->bluetooth.rfkill = NULL;
952*4882a593Smuzhiyun }
953*4882a593Smuzhiyun }
954*4882a593Smuzhiyun
samsung_new_rfkill(struct samsung_laptop * samsung,struct samsung_rfkill * arfkill,const char * name,enum rfkill_type type,const struct rfkill_ops * ops,int blocked)955*4882a593Smuzhiyun static int samsung_new_rfkill(struct samsung_laptop *samsung,
956*4882a593Smuzhiyun struct samsung_rfkill *arfkill,
957*4882a593Smuzhiyun const char *name, enum rfkill_type type,
958*4882a593Smuzhiyun const struct rfkill_ops *ops,
959*4882a593Smuzhiyun int blocked)
960*4882a593Smuzhiyun {
961*4882a593Smuzhiyun struct rfkill **rfkill = &arfkill->rfkill;
962*4882a593Smuzhiyun int ret;
963*4882a593Smuzhiyun
964*4882a593Smuzhiyun arfkill->type = type;
965*4882a593Smuzhiyun arfkill->samsung = samsung;
966*4882a593Smuzhiyun
967*4882a593Smuzhiyun *rfkill = rfkill_alloc(name, &samsung->platform_device->dev,
968*4882a593Smuzhiyun type, ops, arfkill);
969*4882a593Smuzhiyun
970*4882a593Smuzhiyun if (!*rfkill)
971*4882a593Smuzhiyun return -EINVAL;
972*4882a593Smuzhiyun
973*4882a593Smuzhiyun if (blocked != -1)
974*4882a593Smuzhiyun rfkill_init_sw_state(*rfkill, blocked);
975*4882a593Smuzhiyun
976*4882a593Smuzhiyun ret = rfkill_register(*rfkill);
977*4882a593Smuzhiyun if (ret) {
978*4882a593Smuzhiyun rfkill_destroy(*rfkill);
979*4882a593Smuzhiyun *rfkill = NULL;
980*4882a593Smuzhiyun return ret;
981*4882a593Smuzhiyun }
982*4882a593Smuzhiyun return 0;
983*4882a593Smuzhiyun }
984*4882a593Smuzhiyun
samsung_rfkill_init_seclinux(struct samsung_laptop * samsung)985*4882a593Smuzhiyun static int __init samsung_rfkill_init_seclinux(struct samsung_laptop *samsung)
986*4882a593Smuzhiyun {
987*4882a593Smuzhiyun return samsung_new_rfkill(samsung, &samsung->wlan, "samsung-wlan",
988*4882a593Smuzhiyun RFKILL_TYPE_WLAN, &seclinux_rfkill_ops, -1);
989*4882a593Smuzhiyun }
990*4882a593Smuzhiyun
samsung_rfkill_init_swsmi(struct samsung_laptop * samsung)991*4882a593Smuzhiyun static int __init samsung_rfkill_init_swsmi(struct samsung_laptop *samsung)
992*4882a593Smuzhiyun {
993*4882a593Smuzhiyun struct sabi_data data;
994*4882a593Smuzhiyun int ret;
995*4882a593Smuzhiyun
996*4882a593Smuzhiyun ret = swsmi_wireless_status(samsung, &data);
997*4882a593Smuzhiyun if (ret) {
998*4882a593Smuzhiyun /* Some swsmi laptops use the old seclinux way to control
999*4882a593Smuzhiyun * wireless devices */
1000*4882a593Smuzhiyun if (ret == -EINVAL)
1001*4882a593Smuzhiyun ret = samsung_rfkill_init_seclinux(samsung);
1002*4882a593Smuzhiyun return ret;
1003*4882a593Smuzhiyun }
1004*4882a593Smuzhiyun
1005*4882a593Smuzhiyun /* 0x02 seems to mean that the device is no present/available */
1006*4882a593Smuzhiyun
1007*4882a593Smuzhiyun if (data.data[WL_STATUS_WLAN] != 0x02)
1008*4882a593Smuzhiyun ret = samsung_new_rfkill(samsung, &samsung->wlan,
1009*4882a593Smuzhiyun "samsung-wlan",
1010*4882a593Smuzhiyun RFKILL_TYPE_WLAN,
1011*4882a593Smuzhiyun &swsmi_rfkill_ops,
1012*4882a593Smuzhiyun !data.data[WL_STATUS_WLAN]);
1013*4882a593Smuzhiyun if (ret)
1014*4882a593Smuzhiyun goto exit;
1015*4882a593Smuzhiyun
1016*4882a593Smuzhiyun if (data.data[WL_STATUS_BT] != 0x02)
1017*4882a593Smuzhiyun ret = samsung_new_rfkill(samsung, &samsung->bluetooth,
1018*4882a593Smuzhiyun "samsung-bluetooth",
1019*4882a593Smuzhiyun RFKILL_TYPE_BLUETOOTH,
1020*4882a593Smuzhiyun &swsmi_rfkill_ops,
1021*4882a593Smuzhiyun !data.data[WL_STATUS_BT]);
1022*4882a593Smuzhiyun if (ret)
1023*4882a593Smuzhiyun goto exit;
1024*4882a593Smuzhiyun
1025*4882a593Smuzhiyun exit:
1026*4882a593Smuzhiyun if (ret)
1027*4882a593Smuzhiyun samsung_rfkill_exit(samsung);
1028*4882a593Smuzhiyun
1029*4882a593Smuzhiyun return ret;
1030*4882a593Smuzhiyun }
1031*4882a593Smuzhiyun
samsung_rfkill_init(struct samsung_laptop * samsung)1032*4882a593Smuzhiyun static int __init samsung_rfkill_init(struct samsung_laptop *samsung)
1033*4882a593Smuzhiyun {
1034*4882a593Smuzhiyun if (samsung->config->sabi_version == 2)
1035*4882a593Smuzhiyun return samsung_rfkill_init_seclinux(samsung);
1036*4882a593Smuzhiyun if (samsung->config->sabi_version == 3)
1037*4882a593Smuzhiyun return samsung_rfkill_init_swsmi(samsung);
1038*4882a593Smuzhiyun return 0;
1039*4882a593Smuzhiyun }
1040*4882a593Smuzhiyun
samsung_lid_handling_exit(struct samsung_laptop * samsung)1041*4882a593Smuzhiyun static void samsung_lid_handling_exit(struct samsung_laptop *samsung)
1042*4882a593Smuzhiyun {
1043*4882a593Smuzhiyun if (samsung->quirks->lid_handling)
1044*4882a593Smuzhiyun write_lid_handling(samsung, 0);
1045*4882a593Smuzhiyun }
1046*4882a593Smuzhiyun
samsung_lid_handling_init(struct samsung_laptop * samsung)1047*4882a593Smuzhiyun static int __init samsung_lid_handling_init(struct samsung_laptop *samsung)
1048*4882a593Smuzhiyun {
1049*4882a593Smuzhiyun int retval = 0;
1050*4882a593Smuzhiyun
1051*4882a593Smuzhiyun if (samsung->quirks->lid_handling)
1052*4882a593Smuzhiyun retval = write_lid_handling(samsung, 1);
1053*4882a593Smuzhiyun
1054*4882a593Smuzhiyun return retval;
1055*4882a593Smuzhiyun }
1056*4882a593Smuzhiyun
kbd_backlight_enable(struct samsung_laptop * samsung)1057*4882a593Smuzhiyun static int kbd_backlight_enable(struct samsung_laptop *samsung)
1058*4882a593Smuzhiyun {
1059*4882a593Smuzhiyun const struct sabi_commands *commands = &samsung->config->commands;
1060*4882a593Smuzhiyun struct sabi_data data;
1061*4882a593Smuzhiyun int retval;
1062*4882a593Smuzhiyun
1063*4882a593Smuzhiyun if (commands->kbd_backlight == 0xFFFF)
1064*4882a593Smuzhiyun return -ENODEV;
1065*4882a593Smuzhiyun
1066*4882a593Smuzhiyun memset(&data, 0, sizeof(data));
1067*4882a593Smuzhiyun data.d0 = 0xaabb;
1068*4882a593Smuzhiyun retval = sabi_command(samsung, commands->kbd_backlight,
1069*4882a593Smuzhiyun &data, &data);
1070*4882a593Smuzhiyun
1071*4882a593Smuzhiyun if (retval)
1072*4882a593Smuzhiyun return retval;
1073*4882a593Smuzhiyun
1074*4882a593Smuzhiyun if (data.d0 != 0xccdd)
1075*4882a593Smuzhiyun return -ENODEV;
1076*4882a593Smuzhiyun return 0;
1077*4882a593Smuzhiyun }
1078*4882a593Smuzhiyun
kbd_backlight_read(struct samsung_laptop * samsung)1079*4882a593Smuzhiyun static int kbd_backlight_read(struct samsung_laptop *samsung)
1080*4882a593Smuzhiyun {
1081*4882a593Smuzhiyun const struct sabi_commands *commands = &samsung->config->commands;
1082*4882a593Smuzhiyun struct sabi_data data;
1083*4882a593Smuzhiyun int retval;
1084*4882a593Smuzhiyun
1085*4882a593Smuzhiyun memset(&data, 0, sizeof(data));
1086*4882a593Smuzhiyun data.data[0] = 0x81;
1087*4882a593Smuzhiyun retval = sabi_command(samsung, commands->kbd_backlight,
1088*4882a593Smuzhiyun &data, &data);
1089*4882a593Smuzhiyun
1090*4882a593Smuzhiyun if (retval)
1091*4882a593Smuzhiyun return retval;
1092*4882a593Smuzhiyun
1093*4882a593Smuzhiyun return data.data[0];
1094*4882a593Smuzhiyun }
1095*4882a593Smuzhiyun
kbd_backlight_write(struct samsung_laptop * samsung,int brightness)1096*4882a593Smuzhiyun static int kbd_backlight_write(struct samsung_laptop *samsung, int brightness)
1097*4882a593Smuzhiyun {
1098*4882a593Smuzhiyun const struct sabi_commands *commands = &samsung->config->commands;
1099*4882a593Smuzhiyun struct sabi_data data;
1100*4882a593Smuzhiyun
1101*4882a593Smuzhiyun memset(&data, 0, sizeof(data));
1102*4882a593Smuzhiyun data.d0 = 0x82 | ((brightness & 0xFF) << 8);
1103*4882a593Smuzhiyun return sabi_command(samsung, commands->kbd_backlight,
1104*4882a593Smuzhiyun &data, NULL);
1105*4882a593Smuzhiyun }
1106*4882a593Smuzhiyun
kbd_led_update(struct work_struct * work)1107*4882a593Smuzhiyun static void kbd_led_update(struct work_struct *work)
1108*4882a593Smuzhiyun {
1109*4882a593Smuzhiyun struct samsung_laptop *samsung;
1110*4882a593Smuzhiyun
1111*4882a593Smuzhiyun samsung = container_of(work, struct samsung_laptop, kbd_led_work);
1112*4882a593Smuzhiyun kbd_backlight_write(samsung, samsung->kbd_led_wk);
1113*4882a593Smuzhiyun }
1114*4882a593Smuzhiyun
kbd_led_set(struct led_classdev * led_cdev,enum led_brightness value)1115*4882a593Smuzhiyun static void kbd_led_set(struct led_classdev *led_cdev,
1116*4882a593Smuzhiyun enum led_brightness value)
1117*4882a593Smuzhiyun {
1118*4882a593Smuzhiyun struct samsung_laptop *samsung;
1119*4882a593Smuzhiyun
1120*4882a593Smuzhiyun samsung = container_of(led_cdev, struct samsung_laptop, kbd_led);
1121*4882a593Smuzhiyun
1122*4882a593Smuzhiyun if (value > samsung->kbd_led.max_brightness)
1123*4882a593Smuzhiyun value = samsung->kbd_led.max_brightness;
1124*4882a593Smuzhiyun
1125*4882a593Smuzhiyun samsung->kbd_led_wk = value;
1126*4882a593Smuzhiyun queue_work(samsung->led_workqueue, &samsung->kbd_led_work);
1127*4882a593Smuzhiyun }
1128*4882a593Smuzhiyun
kbd_led_get(struct led_classdev * led_cdev)1129*4882a593Smuzhiyun static enum led_brightness kbd_led_get(struct led_classdev *led_cdev)
1130*4882a593Smuzhiyun {
1131*4882a593Smuzhiyun struct samsung_laptop *samsung;
1132*4882a593Smuzhiyun
1133*4882a593Smuzhiyun samsung = container_of(led_cdev, struct samsung_laptop, kbd_led);
1134*4882a593Smuzhiyun return kbd_backlight_read(samsung);
1135*4882a593Smuzhiyun }
1136*4882a593Smuzhiyun
samsung_leds_exit(struct samsung_laptop * samsung)1137*4882a593Smuzhiyun static void samsung_leds_exit(struct samsung_laptop *samsung)
1138*4882a593Smuzhiyun {
1139*4882a593Smuzhiyun led_classdev_unregister(&samsung->kbd_led);
1140*4882a593Smuzhiyun if (samsung->led_workqueue)
1141*4882a593Smuzhiyun destroy_workqueue(samsung->led_workqueue);
1142*4882a593Smuzhiyun }
1143*4882a593Smuzhiyun
samsung_leds_init(struct samsung_laptop * samsung)1144*4882a593Smuzhiyun static int __init samsung_leds_init(struct samsung_laptop *samsung)
1145*4882a593Smuzhiyun {
1146*4882a593Smuzhiyun int ret = 0;
1147*4882a593Smuzhiyun
1148*4882a593Smuzhiyun samsung->led_workqueue = create_singlethread_workqueue("led_workqueue");
1149*4882a593Smuzhiyun if (!samsung->led_workqueue)
1150*4882a593Smuzhiyun return -ENOMEM;
1151*4882a593Smuzhiyun
1152*4882a593Smuzhiyun if (kbd_backlight_enable(samsung) >= 0) {
1153*4882a593Smuzhiyun INIT_WORK(&samsung->kbd_led_work, kbd_led_update);
1154*4882a593Smuzhiyun
1155*4882a593Smuzhiyun samsung->kbd_led.name = "samsung::kbd_backlight";
1156*4882a593Smuzhiyun samsung->kbd_led.brightness_set = kbd_led_set;
1157*4882a593Smuzhiyun samsung->kbd_led.brightness_get = kbd_led_get;
1158*4882a593Smuzhiyun samsung->kbd_led.max_brightness = 8;
1159*4882a593Smuzhiyun if (samsung->quirks->four_kbd_backlight_levels)
1160*4882a593Smuzhiyun samsung->kbd_led.max_brightness = 4;
1161*4882a593Smuzhiyun
1162*4882a593Smuzhiyun ret = led_classdev_register(&samsung->platform_device->dev,
1163*4882a593Smuzhiyun &samsung->kbd_led);
1164*4882a593Smuzhiyun }
1165*4882a593Smuzhiyun
1166*4882a593Smuzhiyun if (ret)
1167*4882a593Smuzhiyun samsung_leds_exit(samsung);
1168*4882a593Smuzhiyun
1169*4882a593Smuzhiyun return ret;
1170*4882a593Smuzhiyun }
1171*4882a593Smuzhiyun
samsung_backlight_exit(struct samsung_laptop * samsung)1172*4882a593Smuzhiyun static void samsung_backlight_exit(struct samsung_laptop *samsung)
1173*4882a593Smuzhiyun {
1174*4882a593Smuzhiyun if (samsung->backlight_device) {
1175*4882a593Smuzhiyun backlight_device_unregister(samsung->backlight_device);
1176*4882a593Smuzhiyun samsung->backlight_device = NULL;
1177*4882a593Smuzhiyun }
1178*4882a593Smuzhiyun }
1179*4882a593Smuzhiyun
samsung_backlight_init(struct samsung_laptop * samsung)1180*4882a593Smuzhiyun static int __init samsung_backlight_init(struct samsung_laptop *samsung)
1181*4882a593Smuzhiyun {
1182*4882a593Smuzhiyun struct backlight_device *bd;
1183*4882a593Smuzhiyun struct backlight_properties props;
1184*4882a593Smuzhiyun
1185*4882a593Smuzhiyun if (!samsung->handle_backlight)
1186*4882a593Smuzhiyun return 0;
1187*4882a593Smuzhiyun
1188*4882a593Smuzhiyun memset(&props, 0, sizeof(struct backlight_properties));
1189*4882a593Smuzhiyun props.type = BACKLIGHT_PLATFORM;
1190*4882a593Smuzhiyun props.max_brightness = samsung->config->max_brightness -
1191*4882a593Smuzhiyun samsung->config->min_brightness;
1192*4882a593Smuzhiyun
1193*4882a593Smuzhiyun bd = backlight_device_register("samsung",
1194*4882a593Smuzhiyun &samsung->platform_device->dev,
1195*4882a593Smuzhiyun samsung, &backlight_ops,
1196*4882a593Smuzhiyun &props);
1197*4882a593Smuzhiyun if (IS_ERR(bd))
1198*4882a593Smuzhiyun return PTR_ERR(bd);
1199*4882a593Smuzhiyun
1200*4882a593Smuzhiyun samsung->backlight_device = bd;
1201*4882a593Smuzhiyun samsung->backlight_device->props.brightness = read_brightness(samsung);
1202*4882a593Smuzhiyun samsung->backlight_device->props.power = FB_BLANK_UNBLANK;
1203*4882a593Smuzhiyun backlight_update_status(samsung->backlight_device);
1204*4882a593Smuzhiyun
1205*4882a593Smuzhiyun return 0;
1206*4882a593Smuzhiyun }
1207*4882a593Smuzhiyun
samsung_sysfs_is_visible(struct kobject * kobj,struct attribute * attr,int idx)1208*4882a593Smuzhiyun static umode_t samsung_sysfs_is_visible(struct kobject *kobj,
1209*4882a593Smuzhiyun struct attribute *attr, int idx)
1210*4882a593Smuzhiyun {
1211*4882a593Smuzhiyun struct device *dev = container_of(kobj, struct device, kobj);
1212*4882a593Smuzhiyun struct samsung_laptop *samsung = dev_get_drvdata(dev);
1213*4882a593Smuzhiyun bool ok = true;
1214*4882a593Smuzhiyun
1215*4882a593Smuzhiyun if (attr == &dev_attr_performance_level.attr)
1216*4882a593Smuzhiyun ok = !!samsung->config->performance_levels[0].name;
1217*4882a593Smuzhiyun if (attr == &dev_attr_battery_life_extender.attr)
1218*4882a593Smuzhiyun ok = !!(read_battery_life_extender(samsung) >= 0);
1219*4882a593Smuzhiyun if (attr == &dev_attr_usb_charge.attr)
1220*4882a593Smuzhiyun ok = !!(read_usb_charge(samsung) >= 0);
1221*4882a593Smuzhiyun if (attr == &dev_attr_lid_handling.attr)
1222*4882a593Smuzhiyun ok = !!(read_lid_handling(samsung) >= 0);
1223*4882a593Smuzhiyun
1224*4882a593Smuzhiyun return ok ? attr->mode : 0;
1225*4882a593Smuzhiyun }
1226*4882a593Smuzhiyun
1227*4882a593Smuzhiyun static const struct attribute_group platform_attribute_group = {
1228*4882a593Smuzhiyun .is_visible = samsung_sysfs_is_visible,
1229*4882a593Smuzhiyun .attrs = platform_attributes
1230*4882a593Smuzhiyun };
1231*4882a593Smuzhiyun
samsung_sysfs_exit(struct samsung_laptop * samsung)1232*4882a593Smuzhiyun static void samsung_sysfs_exit(struct samsung_laptop *samsung)
1233*4882a593Smuzhiyun {
1234*4882a593Smuzhiyun struct platform_device *device = samsung->platform_device;
1235*4882a593Smuzhiyun
1236*4882a593Smuzhiyun sysfs_remove_group(&device->dev.kobj, &platform_attribute_group);
1237*4882a593Smuzhiyun }
1238*4882a593Smuzhiyun
samsung_sysfs_init(struct samsung_laptop * samsung)1239*4882a593Smuzhiyun static int __init samsung_sysfs_init(struct samsung_laptop *samsung)
1240*4882a593Smuzhiyun {
1241*4882a593Smuzhiyun struct platform_device *device = samsung->platform_device;
1242*4882a593Smuzhiyun
1243*4882a593Smuzhiyun return sysfs_create_group(&device->dev.kobj, &platform_attribute_group);
1244*4882a593Smuzhiyun
1245*4882a593Smuzhiyun }
1246*4882a593Smuzhiyun
samsung_laptop_call_show(struct seq_file * m,void * data)1247*4882a593Smuzhiyun static int samsung_laptop_call_show(struct seq_file *m, void *data)
1248*4882a593Smuzhiyun {
1249*4882a593Smuzhiyun struct samsung_laptop *samsung = m->private;
1250*4882a593Smuzhiyun struct sabi_data *sdata = &samsung->debug.data;
1251*4882a593Smuzhiyun int ret;
1252*4882a593Smuzhiyun
1253*4882a593Smuzhiyun seq_printf(m, "SABI 0x%04x {0x%08x, 0x%08x, 0x%04x, 0x%02x}\n",
1254*4882a593Smuzhiyun samsung->debug.command,
1255*4882a593Smuzhiyun sdata->d0, sdata->d1, sdata->d2, sdata->d3);
1256*4882a593Smuzhiyun
1257*4882a593Smuzhiyun ret = sabi_command(samsung, samsung->debug.command, sdata, sdata);
1258*4882a593Smuzhiyun
1259*4882a593Smuzhiyun if (ret) {
1260*4882a593Smuzhiyun seq_printf(m, "SABI command 0x%04x failed\n",
1261*4882a593Smuzhiyun samsung->debug.command);
1262*4882a593Smuzhiyun return ret;
1263*4882a593Smuzhiyun }
1264*4882a593Smuzhiyun
1265*4882a593Smuzhiyun seq_printf(m, "SABI {0x%08x, 0x%08x, 0x%04x, 0x%02x}\n",
1266*4882a593Smuzhiyun sdata->d0, sdata->d1, sdata->d2, sdata->d3);
1267*4882a593Smuzhiyun return 0;
1268*4882a593Smuzhiyun }
1269*4882a593Smuzhiyun DEFINE_SHOW_ATTRIBUTE(samsung_laptop_call);
1270*4882a593Smuzhiyun
samsung_debugfs_exit(struct samsung_laptop * samsung)1271*4882a593Smuzhiyun static void samsung_debugfs_exit(struct samsung_laptop *samsung)
1272*4882a593Smuzhiyun {
1273*4882a593Smuzhiyun debugfs_remove_recursive(samsung->debug.root);
1274*4882a593Smuzhiyun }
1275*4882a593Smuzhiyun
samsung_debugfs_init(struct samsung_laptop * samsung)1276*4882a593Smuzhiyun static void samsung_debugfs_init(struct samsung_laptop *samsung)
1277*4882a593Smuzhiyun {
1278*4882a593Smuzhiyun struct dentry *root;
1279*4882a593Smuzhiyun
1280*4882a593Smuzhiyun root = debugfs_create_dir("samsung-laptop", NULL);
1281*4882a593Smuzhiyun samsung->debug.root = root;
1282*4882a593Smuzhiyun
1283*4882a593Smuzhiyun samsung->debug.f0000_wrapper.data = samsung->f0000_segment;
1284*4882a593Smuzhiyun samsung->debug.f0000_wrapper.size = 0xffff;
1285*4882a593Smuzhiyun
1286*4882a593Smuzhiyun samsung->debug.data_wrapper.data = &samsung->debug.data;
1287*4882a593Smuzhiyun samsung->debug.data_wrapper.size = sizeof(samsung->debug.data);
1288*4882a593Smuzhiyun
1289*4882a593Smuzhiyun samsung->debug.sdiag_wrapper.data = samsung->sdiag;
1290*4882a593Smuzhiyun samsung->debug.sdiag_wrapper.size = strlen(samsung->sdiag);
1291*4882a593Smuzhiyun
1292*4882a593Smuzhiyun debugfs_create_u16("command", S_IRUGO | S_IWUSR, root,
1293*4882a593Smuzhiyun &samsung->debug.command);
1294*4882a593Smuzhiyun debugfs_create_u32("d0", S_IRUGO | S_IWUSR, root,
1295*4882a593Smuzhiyun &samsung->debug.data.d0);
1296*4882a593Smuzhiyun debugfs_create_u32("d1", S_IRUGO | S_IWUSR, root,
1297*4882a593Smuzhiyun &samsung->debug.data.d1);
1298*4882a593Smuzhiyun debugfs_create_u16("d2", S_IRUGO | S_IWUSR, root,
1299*4882a593Smuzhiyun &samsung->debug.data.d2);
1300*4882a593Smuzhiyun debugfs_create_u8("d3", S_IRUGO | S_IWUSR, root,
1301*4882a593Smuzhiyun &samsung->debug.data.d3);
1302*4882a593Smuzhiyun debugfs_create_blob("data", S_IRUGO | S_IWUSR, root,
1303*4882a593Smuzhiyun &samsung->debug.data_wrapper);
1304*4882a593Smuzhiyun debugfs_create_blob("f0000_segment", S_IRUSR | S_IWUSR, root,
1305*4882a593Smuzhiyun &samsung->debug.f0000_wrapper);
1306*4882a593Smuzhiyun debugfs_create_file("call", S_IFREG | S_IRUGO, root, samsung,
1307*4882a593Smuzhiyun &samsung_laptop_call_fops);
1308*4882a593Smuzhiyun debugfs_create_blob("sdiag", S_IRUGO | S_IWUSR, root,
1309*4882a593Smuzhiyun &samsung->debug.sdiag_wrapper);
1310*4882a593Smuzhiyun }
1311*4882a593Smuzhiyun
samsung_sabi_exit(struct samsung_laptop * samsung)1312*4882a593Smuzhiyun static void samsung_sabi_exit(struct samsung_laptop *samsung)
1313*4882a593Smuzhiyun {
1314*4882a593Smuzhiyun const struct sabi_config *config = samsung->config;
1315*4882a593Smuzhiyun
1316*4882a593Smuzhiyun /* Turn off "Linux" mode in the BIOS */
1317*4882a593Smuzhiyun if (config && config->commands.set_linux != 0xff)
1318*4882a593Smuzhiyun sabi_set_commandb(samsung, config->commands.set_linux, 0x80);
1319*4882a593Smuzhiyun
1320*4882a593Smuzhiyun if (samsung->sabi_iface) {
1321*4882a593Smuzhiyun iounmap(samsung->sabi_iface);
1322*4882a593Smuzhiyun samsung->sabi_iface = NULL;
1323*4882a593Smuzhiyun }
1324*4882a593Smuzhiyun if (samsung->f0000_segment) {
1325*4882a593Smuzhiyun iounmap(samsung->f0000_segment);
1326*4882a593Smuzhiyun samsung->f0000_segment = NULL;
1327*4882a593Smuzhiyun }
1328*4882a593Smuzhiyun
1329*4882a593Smuzhiyun samsung->config = NULL;
1330*4882a593Smuzhiyun }
1331*4882a593Smuzhiyun
samsung_sabi_infos(struct samsung_laptop * samsung,int loca,unsigned int ifaceP)1332*4882a593Smuzhiyun static __init void samsung_sabi_infos(struct samsung_laptop *samsung, int loca,
1333*4882a593Smuzhiyun unsigned int ifaceP)
1334*4882a593Smuzhiyun {
1335*4882a593Smuzhiyun const struct sabi_config *config = samsung->config;
1336*4882a593Smuzhiyun
1337*4882a593Smuzhiyun printk(KERN_DEBUG "This computer supports SABI==%x\n",
1338*4882a593Smuzhiyun loca + 0xf0000 - 6);
1339*4882a593Smuzhiyun
1340*4882a593Smuzhiyun printk(KERN_DEBUG "SABI header:\n");
1341*4882a593Smuzhiyun printk(KERN_DEBUG " SMI Port Number = 0x%04x\n",
1342*4882a593Smuzhiyun readw(samsung->sabi + config->header_offsets.port));
1343*4882a593Smuzhiyun printk(KERN_DEBUG " SMI Interface Function = 0x%02x\n",
1344*4882a593Smuzhiyun readb(samsung->sabi + config->header_offsets.iface_func));
1345*4882a593Smuzhiyun printk(KERN_DEBUG " SMI enable memory buffer = 0x%02x\n",
1346*4882a593Smuzhiyun readb(samsung->sabi + config->header_offsets.en_mem));
1347*4882a593Smuzhiyun printk(KERN_DEBUG " SMI restore memory buffer = 0x%02x\n",
1348*4882a593Smuzhiyun readb(samsung->sabi + config->header_offsets.re_mem));
1349*4882a593Smuzhiyun printk(KERN_DEBUG " SABI data offset = 0x%04x\n",
1350*4882a593Smuzhiyun readw(samsung->sabi + config->header_offsets.data_offset));
1351*4882a593Smuzhiyun printk(KERN_DEBUG " SABI data segment = 0x%04x\n",
1352*4882a593Smuzhiyun readw(samsung->sabi + config->header_offsets.data_segment));
1353*4882a593Smuzhiyun
1354*4882a593Smuzhiyun printk(KERN_DEBUG " SABI pointer = 0x%08x\n", ifaceP);
1355*4882a593Smuzhiyun }
1356*4882a593Smuzhiyun
samsung_sabi_diag(struct samsung_laptop * samsung)1357*4882a593Smuzhiyun static void __init samsung_sabi_diag(struct samsung_laptop *samsung)
1358*4882a593Smuzhiyun {
1359*4882a593Smuzhiyun int loca = find_signature(samsung->f0000_segment, "SDiaG@");
1360*4882a593Smuzhiyun int i;
1361*4882a593Smuzhiyun
1362*4882a593Smuzhiyun if (loca == 0xffff)
1363*4882a593Smuzhiyun return ;
1364*4882a593Smuzhiyun
1365*4882a593Smuzhiyun /* Example:
1366*4882a593Smuzhiyun * Ident: @SDiaG@686XX-N90X3A/966-SEC-07HL-S90X3A
1367*4882a593Smuzhiyun *
1368*4882a593Smuzhiyun * Product name: 90X3A
1369*4882a593Smuzhiyun * BIOS Version: 07HL
1370*4882a593Smuzhiyun */
1371*4882a593Smuzhiyun loca += 1;
1372*4882a593Smuzhiyun for (i = 0; loca < 0xffff && i < sizeof(samsung->sdiag) - 1; loca++) {
1373*4882a593Smuzhiyun char temp = readb(samsung->f0000_segment + loca);
1374*4882a593Smuzhiyun
1375*4882a593Smuzhiyun if (isalnum(temp) || temp == '/' || temp == '-')
1376*4882a593Smuzhiyun samsung->sdiag[i++] = temp;
1377*4882a593Smuzhiyun else
1378*4882a593Smuzhiyun break ;
1379*4882a593Smuzhiyun }
1380*4882a593Smuzhiyun
1381*4882a593Smuzhiyun if (debug && samsung->sdiag[0])
1382*4882a593Smuzhiyun pr_info("sdiag: %s", samsung->sdiag);
1383*4882a593Smuzhiyun }
1384*4882a593Smuzhiyun
samsung_sabi_init(struct samsung_laptop * samsung)1385*4882a593Smuzhiyun static int __init samsung_sabi_init(struct samsung_laptop *samsung)
1386*4882a593Smuzhiyun {
1387*4882a593Smuzhiyun const struct sabi_config *config = NULL;
1388*4882a593Smuzhiyun const struct sabi_commands *commands;
1389*4882a593Smuzhiyun unsigned int ifaceP;
1390*4882a593Smuzhiyun int loca = 0xffff;
1391*4882a593Smuzhiyun int ret = 0;
1392*4882a593Smuzhiyun int i;
1393*4882a593Smuzhiyun
1394*4882a593Smuzhiyun samsung->f0000_segment = ioremap(0xf0000, 0xffff);
1395*4882a593Smuzhiyun if (!samsung->f0000_segment) {
1396*4882a593Smuzhiyun if (debug || force)
1397*4882a593Smuzhiyun pr_err("Can't map the segment at 0xf0000\n");
1398*4882a593Smuzhiyun ret = -EINVAL;
1399*4882a593Smuzhiyun goto exit;
1400*4882a593Smuzhiyun }
1401*4882a593Smuzhiyun
1402*4882a593Smuzhiyun samsung_sabi_diag(samsung);
1403*4882a593Smuzhiyun
1404*4882a593Smuzhiyun /* Try to find one of the signatures in memory to find the header */
1405*4882a593Smuzhiyun for (i = 0; sabi_configs[i].test_string != NULL; ++i) {
1406*4882a593Smuzhiyun samsung->config = &sabi_configs[i];
1407*4882a593Smuzhiyun loca = find_signature(samsung->f0000_segment,
1408*4882a593Smuzhiyun samsung->config->test_string);
1409*4882a593Smuzhiyun if (loca != 0xffff)
1410*4882a593Smuzhiyun break;
1411*4882a593Smuzhiyun }
1412*4882a593Smuzhiyun
1413*4882a593Smuzhiyun if (loca == 0xffff) {
1414*4882a593Smuzhiyun if (debug || force)
1415*4882a593Smuzhiyun pr_err("This computer does not support SABI\n");
1416*4882a593Smuzhiyun ret = -ENODEV;
1417*4882a593Smuzhiyun goto exit;
1418*4882a593Smuzhiyun }
1419*4882a593Smuzhiyun
1420*4882a593Smuzhiyun config = samsung->config;
1421*4882a593Smuzhiyun commands = &config->commands;
1422*4882a593Smuzhiyun
1423*4882a593Smuzhiyun /* point to the SMI port Number */
1424*4882a593Smuzhiyun loca += 1;
1425*4882a593Smuzhiyun samsung->sabi = (samsung->f0000_segment + loca);
1426*4882a593Smuzhiyun
1427*4882a593Smuzhiyun /* Get a pointer to the SABI Interface */
1428*4882a593Smuzhiyun ifaceP = (readw(samsung->sabi + config->header_offsets.data_segment) & 0x0ffff) << 4;
1429*4882a593Smuzhiyun ifaceP += readw(samsung->sabi + config->header_offsets.data_offset) & 0x0ffff;
1430*4882a593Smuzhiyun
1431*4882a593Smuzhiyun if (debug)
1432*4882a593Smuzhiyun samsung_sabi_infos(samsung, loca, ifaceP);
1433*4882a593Smuzhiyun
1434*4882a593Smuzhiyun samsung->sabi_iface = ioremap(ifaceP, 16);
1435*4882a593Smuzhiyun if (!samsung->sabi_iface) {
1436*4882a593Smuzhiyun pr_err("Can't remap %x\n", ifaceP);
1437*4882a593Smuzhiyun ret = -EINVAL;
1438*4882a593Smuzhiyun goto exit;
1439*4882a593Smuzhiyun }
1440*4882a593Smuzhiyun
1441*4882a593Smuzhiyun /* Turn on "Linux" mode in the BIOS */
1442*4882a593Smuzhiyun if (commands->set_linux != 0xff) {
1443*4882a593Smuzhiyun int retval = sabi_set_commandb(samsung,
1444*4882a593Smuzhiyun commands->set_linux, 0x81);
1445*4882a593Smuzhiyun if (retval) {
1446*4882a593Smuzhiyun pr_warn("Linux mode was not set!\n");
1447*4882a593Smuzhiyun ret = -ENODEV;
1448*4882a593Smuzhiyun goto exit;
1449*4882a593Smuzhiyun }
1450*4882a593Smuzhiyun }
1451*4882a593Smuzhiyun
1452*4882a593Smuzhiyun /* Check for stepping quirk */
1453*4882a593Smuzhiyun if (samsung->handle_backlight)
1454*4882a593Smuzhiyun check_for_stepping_quirk(samsung);
1455*4882a593Smuzhiyun
1456*4882a593Smuzhiyun pr_info("detected SABI interface: %s\n",
1457*4882a593Smuzhiyun samsung->config->test_string);
1458*4882a593Smuzhiyun
1459*4882a593Smuzhiyun exit:
1460*4882a593Smuzhiyun if (ret)
1461*4882a593Smuzhiyun samsung_sabi_exit(samsung);
1462*4882a593Smuzhiyun
1463*4882a593Smuzhiyun return ret;
1464*4882a593Smuzhiyun }
1465*4882a593Smuzhiyun
samsung_platform_exit(struct samsung_laptop * samsung)1466*4882a593Smuzhiyun static void samsung_platform_exit(struct samsung_laptop *samsung)
1467*4882a593Smuzhiyun {
1468*4882a593Smuzhiyun if (samsung->platform_device) {
1469*4882a593Smuzhiyun platform_device_unregister(samsung->platform_device);
1470*4882a593Smuzhiyun samsung->platform_device = NULL;
1471*4882a593Smuzhiyun }
1472*4882a593Smuzhiyun }
1473*4882a593Smuzhiyun
samsung_pm_notification(struct notifier_block * nb,unsigned long val,void * ptr)1474*4882a593Smuzhiyun static int samsung_pm_notification(struct notifier_block *nb,
1475*4882a593Smuzhiyun unsigned long val, void *ptr)
1476*4882a593Smuzhiyun {
1477*4882a593Smuzhiyun struct samsung_laptop *samsung;
1478*4882a593Smuzhiyun
1479*4882a593Smuzhiyun samsung = container_of(nb, struct samsung_laptop, pm_nb);
1480*4882a593Smuzhiyun if (val == PM_POST_HIBERNATION &&
1481*4882a593Smuzhiyun samsung->quirks->enable_kbd_backlight)
1482*4882a593Smuzhiyun kbd_backlight_enable(samsung);
1483*4882a593Smuzhiyun
1484*4882a593Smuzhiyun if (val == PM_POST_HIBERNATION && samsung->quirks->lid_handling)
1485*4882a593Smuzhiyun write_lid_handling(samsung, 1);
1486*4882a593Smuzhiyun
1487*4882a593Smuzhiyun return 0;
1488*4882a593Smuzhiyun }
1489*4882a593Smuzhiyun
samsung_platform_init(struct samsung_laptop * samsung)1490*4882a593Smuzhiyun static int __init samsung_platform_init(struct samsung_laptop *samsung)
1491*4882a593Smuzhiyun {
1492*4882a593Smuzhiyun struct platform_device *pdev;
1493*4882a593Smuzhiyun
1494*4882a593Smuzhiyun pdev = platform_device_register_simple("samsung", -1, NULL, 0);
1495*4882a593Smuzhiyun if (IS_ERR(pdev))
1496*4882a593Smuzhiyun return PTR_ERR(pdev);
1497*4882a593Smuzhiyun
1498*4882a593Smuzhiyun samsung->platform_device = pdev;
1499*4882a593Smuzhiyun platform_set_drvdata(samsung->platform_device, samsung);
1500*4882a593Smuzhiyun return 0;
1501*4882a593Smuzhiyun }
1502*4882a593Smuzhiyun
1503*4882a593Smuzhiyun static struct samsung_quirks *quirks;
1504*4882a593Smuzhiyun
samsung_dmi_matched(const struct dmi_system_id * d)1505*4882a593Smuzhiyun static int __init samsung_dmi_matched(const struct dmi_system_id *d)
1506*4882a593Smuzhiyun {
1507*4882a593Smuzhiyun quirks = d->driver_data;
1508*4882a593Smuzhiyun return 0;
1509*4882a593Smuzhiyun }
1510*4882a593Smuzhiyun
1511*4882a593Smuzhiyun static const struct dmi_system_id samsung_dmi_table[] __initconst = {
1512*4882a593Smuzhiyun {
1513*4882a593Smuzhiyun .matches = {
1514*4882a593Smuzhiyun DMI_MATCH(DMI_SYS_VENDOR,
1515*4882a593Smuzhiyun "SAMSUNG ELECTRONICS CO., LTD."),
1516*4882a593Smuzhiyun DMI_MATCH(DMI_CHASSIS_TYPE, "8"), /* Portable */
1517*4882a593Smuzhiyun },
1518*4882a593Smuzhiyun },
1519*4882a593Smuzhiyun {
1520*4882a593Smuzhiyun .matches = {
1521*4882a593Smuzhiyun DMI_MATCH(DMI_SYS_VENDOR,
1522*4882a593Smuzhiyun "SAMSUNG ELECTRONICS CO., LTD."),
1523*4882a593Smuzhiyun DMI_MATCH(DMI_CHASSIS_TYPE, "9"), /* Laptop */
1524*4882a593Smuzhiyun },
1525*4882a593Smuzhiyun },
1526*4882a593Smuzhiyun {
1527*4882a593Smuzhiyun .matches = {
1528*4882a593Smuzhiyun DMI_MATCH(DMI_SYS_VENDOR,
1529*4882a593Smuzhiyun "SAMSUNG ELECTRONICS CO., LTD."),
1530*4882a593Smuzhiyun DMI_MATCH(DMI_CHASSIS_TYPE, "10"), /* Notebook */
1531*4882a593Smuzhiyun },
1532*4882a593Smuzhiyun },
1533*4882a593Smuzhiyun {
1534*4882a593Smuzhiyun .matches = {
1535*4882a593Smuzhiyun DMI_MATCH(DMI_SYS_VENDOR,
1536*4882a593Smuzhiyun "SAMSUNG ELECTRONICS CO., LTD."),
1537*4882a593Smuzhiyun DMI_MATCH(DMI_CHASSIS_TYPE, "14"), /* Sub-Notebook */
1538*4882a593Smuzhiyun },
1539*4882a593Smuzhiyun },
1540*4882a593Smuzhiyun /* DMI ids for laptops with bad Chassis Type */
1541*4882a593Smuzhiyun {
1542*4882a593Smuzhiyun .ident = "R40/R41",
1543*4882a593Smuzhiyun .matches = {
1544*4882a593Smuzhiyun DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1545*4882a593Smuzhiyun DMI_MATCH(DMI_PRODUCT_NAME, "R40/R41"),
1546*4882a593Smuzhiyun DMI_MATCH(DMI_BOARD_NAME, "R40/R41"),
1547*4882a593Smuzhiyun },
1548*4882a593Smuzhiyun },
1549*4882a593Smuzhiyun /* Specific DMI ids for laptop with quirks */
1550*4882a593Smuzhiyun {
1551*4882a593Smuzhiyun .callback = samsung_dmi_matched,
1552*4882a593Smuzhiyun .ident = "N150P",
1553*4882a593Smuzhiyun .matches = {
1554*4882a593Smuzhiyun DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1555*4882a593Smuzhiyun DMI_MATCH(DMI_PRODUCT_NAME, "N150P"),
1556*4882a593Smuzhiyun DMI_MATCH(DMI_BOARD_NAME, "N150P"),
1557*4882a593Smuzhiyun },
1558*4882a593Smuzhiyun .driver_data = &samsung_use_native_backlight,
1559*4882a593Smuzhiyun },
1560*4882a593Smuzhiyun {
1561*4882a593Smuzhiyun .callback = samsung_dmi_matched,
1562*4882a593Smuzhiyun .ident = "N145P/N250P/N260P",
1563*4882a593Smuzhiyun .matches = {
1564*4882a593Smuzhiyun DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1565*4882a593Smuzhiyun DMI_MATCH(DMI_PRODUCT_NAME, "N145P/N250P/N260P"),
1566*4882a593Smuzhiyun DMI_MATCH(DMI_BOARD_NAME, "N145P/N250P/N260P"),
1567*4882a593Smuzhiyun },
1568*4882a593Smuzhiyun .driver_data = &samsung_use_native_backlight,
1569*4882a593Smuzhiyun },
1570*4882a593Smuzhiyun {
1571*4882a593Smuzhiyun .callback = samsung_dmi_matched,
1572*4882a593Smuzhiyun .ident = "N150/N210/N220",
1573*4882a593Smuzhiyun .matches = {
1574*4882a593Smuzhiyun DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1575*4882a593Smuzhiyun DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220"),
1576*4882a593Smuzhiyun DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220"),
1577*4882a593Smuzhiyun },
1578*4882a593Smuzhiyun .driver_data = &samsung_broken_acpi_video,
1579*4882a593Smuzhiyun },
1580*4882a593Smuzhiyun {
1581*4882a593Smuzhiyun .callback = samsung_dmi_matched,
1582*4882a593Smuzhiyun .ident = "NF110/NF210/NF310",
1583*4882a593Smuzhiyun .matches = {
1584*4882a593Smuzhiyun DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1585*4882a593Smuzhiyun DMI_MATCH(DMI_PRODUCT_NAME, "NF110/NF210/NF310"),
1586*4882a593Smuzhiyun DMI_MATCH(DMI_BOARD_NAME, "NF110/NF210/NF310"),
1587*4882a593Smuzhiyun },
1588*4882a593Smuzhiyun .driver_data = &samsung_broken_acpi_video,
1589*4882a593Smuzhiyun },
1590*4882a593Smuzhiyun {
1591*4882a593Smuzhiyun .callback = samsung_dmi_matched,
1592*4882a593Smuzhiyun .ident = "X360",
1593*4882a593Smuzhiyun .matches = {
1594*4882a593Smuzhiyun DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1595*4882a593Smuzhiyun DMI_MATCH(DMI_PRODUCT_NAME, "X360"),
1596*4882a593Smuzhiyun DMI_MATCH(DMI_BOARD_NAME, "X360"),
1597*4882a593Smuzhiyun },
1598*4882a593Smuzhiyun .driver_data = &samsung_broken_acpi_video,
1599*4882a593Smuzhiyun },
1600*4882a593Smuzhiyun {
1601*4882a593Smuzhiyun .callback = samsung_dmi_matched,
1602*4882a593Smuzhiyun .ident = "N250P",
1603*4882a593Smuzhiyun .matches = {
1604*4882a593Smuzhiyun DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1605*4882a593Smuzhiyun DMI_MATCH(DMI_PRODUCT_NAME, "N250P"),
1606*4882a593Smuzhiyun DMI_MATCH(DMI_BOARD_NAME, "N250P"),
1607*4882a593Smuzhiyun },
1608*4882a593Smuzhiyun .driver_data = &samsung_use_native_backlight,
1609*4882a593Smuzhiyun },
1610*4882a593Smuzhiyun {
1611*4882a593Smuzhiyun .callback = samsung_dmi_matched,
1612*4882a593Smuzhiyun .ident = "NC210",
1613*4882a593Smuzhiyun .matches = {
1614*4882a593Smuzhiyun DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1615*4882a593Smuzhiyun DMI_MATCH(DMI_PRODUCT_NAME, "NC210/NC110"),
1616*4882a593Smuzhiyun DMI_MATCH(DMI_BOARD_NAME, "NC210/NC110"),
1617*4882a593Smuzhiyun },
1618*4882a593Smuzhiyun .driver_data = &samsung_broken_acpi_video,
1619*4882a593Smuzhiyun },
1620*4882a593Smuzhiyun {
1621*4882a593Smuzhiyun .callback = samsung_dmi_matched,
1622*4882a593Smuzhiyun .ident = "730U3E/740U3E",
1623*4882a593Smuzhiyun .matches = {
1624*4882a593Smuzhiyun DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1625*4882a593Smuzhiyun DMI_MATCH(DMI_PRODUCT_NAME, "730U3E/740U3E"),
1626*4882a593Smuzhiyun },
1627*4882a593Smuzhiyun .driver_data = &samsung_np740u3e,
1628*4882a593Smuzhiyun },
1629*4882a593Smuzhiyun {
1630*4882a593Smuzhiyun .callback = samsung_dmi_matched,
1631*4882a593Smuzhiyun .ident = "300V3Z/300V4Z/300V5Z",
1632*4882a593Smuzhiyun .matches = {
1633*4882a593Smuzhiyun DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1634*4882a593Smuzhiyun DMI_MATCH(DMI_PRODUCT_NAME, "300V3Z/300V4Z/300V5Z"),
1635*4882a593Smuzhiyun },
1636*4882a593Smuzhiyun .driver_data = &samsung_lid_handling,
1637*4882a593Smuzhiyun },
1638*4882a593Smuzhiyun { },
1639*4882a593Smuzhiyun };
1640*4882a593Smuzhiyun MODULE_DEVICE_TABLE(dmi, samsung_dmi_table);
1641*4882a593Smuzhiyun
1642*4882a593Smuzhiyun static struct platform_device *samsung_platform_device;
1643*4882a593Smuzhiyun
samsung_init(void)1644*4882a593Smuzhiyun static int __init samsung_init(void)
1645*4882a593Smuzhiyun {
1646*4882a593Smuzhiyun struct samsung_laptop *samsung;
1647*4882a593Smuzhiyun int ret;
1648*4882a593Smuzhiyun
1649*4882a593Smuzhiyun if (efi_enabled(EFI_BOOT))
1650*4882a593Smuzhiyun return -ENODEV;
1651*4882a593Smuzhiyun
1652*4882a593Smuzhiyun quirks = &samsung_unknown;
1653*4882a593Smuzhiyun if (!force && !dmi_check_system(samsung_dmi_table))
1654*4882a593Smuzhiyun return -ENODEV;
1655*4882a593Smuzhiyun
1656*4882a593Smuzhiyun samsung = kzalloc(sizeof(*samsung), GFP_KERNEL);
1657*4882a593Smuzhiyun if (!samsung)
1658*4882a593Smuzhiyun return -ENOMEM;
1659*4882a593Smuzhiyun
1660*4882a593Smuzhiyun mutex_init(&samsung->sabi_mutex);
1661*4882a593Smuzhiyun samsung->handle_backlight = true;
1662*4882a593Smuzhiyun samsung->quirks = quirks;
1663*4882a593Smuzhiyun
1664*4882a593Smuzhiyun #ifdef CONFIG_ACPI
1665*4882a593Smuzhiyun if (samsung->quirks->broken_acpi_video)
1666*4882a593Smuzhiyun acpi_video_set_dmi_backlight_type(acpi_backlight_vendor);
1667*4882a593Smuzhiyun if (samsung->quirks->use_native_backlight)
1668*4882a593Smuzhiyun acpi_video_set_dmi_backlight_type(acpi_backlight_native);
1669*4882a593Smuzhiyun
1670*4882a593Smuzhiyun if (acpi_video_get_backlight_type() != acpi_backlight_vendor)
1671*4882a593Smuzhiyun samsung->handle_backlight = false;
1672*4882a593Smuzhiyun #endif
1673*4882a593Smuzhiyun
1674*4882a593Smuzhiyun ret = samsung_platform_init(samsung);
1675*4882a593Smuzhiyun if (ret)
1676*4882a593Smuzhiyun goto error_platform;
1677*4882a593Smuzhiyun
1678*4882a593Smuzhiyun ret = samsung_sabi_init(samsung);
1679*4882a593Smuzhiyun if (ret)
1680*4882a593Smuzhiyun goto error_sabi;
1681*4882a593Smuzhiyun
1682*4882a593Smuzhiyun ret = samsung_sysfs_init(samsung);
1683*4882a593Smuzhiyun if (ret)
1684*4882a593Smuzhiyun goto error_sysfs;
1685*4882a593Smuzhiyun
1686*4882a593Smuzhiyun ret = samsung_backlight_init(samsung);
1687*4882a593Smuzhiyun if (ret)
1688*4882a593Smuzhiyun goto error_backlight;
1689*4882a593Smuzhiyun
1690*4882a593Smuzhiyun ret = samsung_rfkill_init(samsung);
1691*4882a593Smuzhiyun if (ret)
1692*4882a593Smuzhiyun goto error_rfkill;
1693*4882a593Smuzhiyun
1694*4882a593Smuzhiyun ret = samsung_leds_init(samsung);
1695*4882a593Smuzhiyun if (ret)
1696*4882a593Smuzhiyun goto error_leds;
1697*4882a593Smuzhiyun
1698*4882a593Smuzhiyun ret = samsung_lid_handling_init(samsung);
1699*4882a593Smuzhiyun if (ret)
1700*4882a593Smuzhiyun goto error_lid_handling;
1701*4882a593Smuzhiyun
1702*4882a593Smuzhiyun samsung_debugfs_init(samsung);
1703*4882a593Smuzhiyun
1704*4882a593Smuzhiyun samsung->pm_nb.notifier_call = samsung_pm_notification;
1705*4882a593Smuzhiyun register_pm_notifier(&samsung->pm_nb);
1706*4882a593Smuzhiyun
1707*4882a593Smuzhiyun samsung_platform_device = samsung->platform_device;
1708*4882a593Smuzhiyun return ret;
1709*4882a593Smuzhiyun
1710*4882a593Smuzhiyun error_lid_handling:
1711*4882a593Smuzhiyun samsung_leds_exit(samsung);
1712*4882a593Smuzhiyun error_leds:
1713*4882a593Smuzhiyun samsung_rfkill_exit(samsung);
1714*4882a593Smuzhiyun error_rfkill:
1715*4882a593Smuzhiyun samsung_backlight_exit(samsung);
1716*4882a593Smuzhiyun error_backlight:
1717*4882a593Smuzhiyun samsung_sysfs_exit(samsung);
1718*4882a593Smuzhiyun error_sysfs:
1719*4882a593Smuzhiyun samsung_sabi_exit(samsung);
1720*4882a593Smuzhiyun error_sabi:
1721*4882a593Smuzhiyun samsung_platform_exit(samsung);
1722*4882a593Smuzhiyun error_platform:
1723*4882a593Smuzhiyun kfree(samsung);
1724*4882a593Smuzhiyun return ret;
1725*4882a593Smuzhiyun }
1726*4882a593Smuzhiyun
samsung_exit(void)1727*4882a593Smuzhiyun static void __exit samsung_exit(void)
1728*4882a593Smuzhiyun {
1729*4882a593Smuzhiyun struct samsung_laptop *samsung;
1730*4882a593Smuzhiyun
1731*4882a593Smuzhiyun samsung = platform_get_drvdata(samsung_platform_device);
1732*4882a593Smuzhiyun unregister_pm_notifier(&samsung->pm_nb);
1733*4882a593Smuzhiyun
1734*4882a593Smuzhiyun samsung_debugfs_exit(samsung);
1735*4882a593Smuzhiyun samsung_lid_handling_exit(samsung);
1736*4882a593Smuzhiyun samsung_leds_exit(samsung);
1737*4882a593Smuzhiyun samsung_rfkill_exit(samsung);
1738*4882a593Smuzhiyun samsung_backlight_exit(samsung);
1739*4882a593Smuzhiyun samsung_sysfs_exit(samsung);
1740*4882a593Smuzhiyun samsung_sabi_exit(samsung);
1741*4882a593Smuzhiyun samsung_platform_exit(samsung);
1742*4882a593Smuzhiyun
1743*4882a593Smuzhiyun kfree(samsung);
1744*4882a593Smuzhiyun samsung_platform_device = NULL;
1745*4882a593Smuzhiyun }
1746*4882a593Smuzhiyun
1747*4882a593Smuzhiyun module_init(samsung_init);
1748*4882a593Smuzhiyun module_exit(samsung_exit);
1749*4882a593Smuzhiyun
1750*4882a593Smuzhiyun MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@suse.de>");
1751*4882a593Smuzhiyun MODULE_DESCRIPTION("Samsung Backlight driver");
1752*4882a593Smuzhiyun MODULE_LICENSE("GPL");
1753