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