1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Topstar Laptop ACPI Extras driver 4 * 5 * Copyright (c) 2009 Herton Ronaldo Krzesinski <herton@mandriva.com.br> 6 * Copyright (c) 2018 Guillaume Douézan-Grard 7 * 8 * Implementation inspired by existing x86 platform drivers, in special 9 * asus/eepc/fujitsu-laptop, thanks to their authors. 10 */ 11 12 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 13 14 #include <linux/kernel.h> 15 #include <linux/module.h> 16 #include <linux/init.h> 17 #include <linux/slab.h> 18 #include <linux/acpi.h> 19 #include <linux/dmi.h> 20 #include <linux/input.h> 21 #include <linux/input/sparse-keymap.h> 22 #include <linux/leds.h> 23 #include <linux/platform_device.h> 24 25 #define TOPSTAR_LAPTOP_CLASS "topstar" 26 27 struct topstar_laptop { 28 struct acpi_device *device; 29 struct platform_device *platform; 30 struct input_dev *input; 31 struct led_classdev led; 32 }; 33 34 /* 35 * LED 36 */ 37 38 static enum led_brightness topstar_led_get(struct led_classdev *led) 39 { 40 return led->brightness; 41 } 42 43 static int topstar_led_set(struct led_classdev *led, 44 enum led_brightness state) 45 { 46 struct topstar_laptop *topstar = container_of(led, 47 struct topstar_laptop, led); 48 49 struct acpi_object_list params; 50 union acpi_object in_obj; 51 unsigned long long int ret; 52 acpi_status status; 53 54 params.count = 1; 55 params.pointer = &in_obj; 56 in_obj.type = ACPI_TYPE_INTEGER; 57 in_obj.integer.value = 0x83; 58 59 /* 60 * Topstar ACPI returns 0x30001 when the LED is ON and 0x30000 when it 61 * is OFF. 62 */ 63 status = acpi_evaluate_integer(topstar->device->handle, 64 "GETX", ¶ms, &ret); 65 if (ACPI_FAILURE(status)) 66 return -1; 67 68 /* 69 * FNCX(0x83) toggles the LED (more precisely, it is supposed to 70 * act as an hardware switch and disconnect the WLAN adapter but 71 * it seems to be faulty on some models like the Topstar U931 72 * Notebook). 73 */ 74 if ((ret == 0x30001 && state == LED_OFF) 75 || (ret == 0x30000 && state != LED_OFF)) { 76 status = acpi_execute_simple_method(topstar->device->handle, 77 "FNCX", 0x83); 78 if (ACPI_FAILURE(status)) 79 return -1; 80 } 81 82 return 0; 83 } 84 85 static int topstar_led_init(struct topstar_laptop *topstar) 86 { 87 topstar->led = (struct led_classdev) { 88 .default_trigger = "rfkill0", 89 .brightness_get = topstar_led_get, 90 .brightness_set_blocking = topstar_led_set, 91 .name = TOPSTAR_LAPTOP_CLASS "::wlan", 92 }; 93 94 return led_classdev_register(&topstar->platform->dev, &topstar->led); 95 } 96 97 static void topstar_led_exit(struct topstar_laptop *topstar) 98 { 99 led_classdev_unregister(&topstar->led); 100 } 101 102 /* 103 * Input 104 */ 105 106 static const struct key_entry topstar_keymap[] = { 107 { KE_KEY, 0x80, { KEY_BRIGHTNESSUP } }, 108 { KE_KEY, 0x81, { KEY_BRIGHTNESSDOWN } }, 109 { KE_KEY, 0x83, { KEY_VOLUMEUP } }, 110 { KE_KEY, 0x84, { KEY_VOLUMEDOWN } }, 111 { KE_KEY, 0x85, { KEY_MUTE } }, 112 { KE_KEY, 0x86, { KEY_SWITCHVIDEOMODE } }, 113 { KE_KEY, 0x87, { KEY_F13 } }, /* touchpad enable/disable key */ 114 { KE_KEY, 0x88, { KEY_WLAN } }, 115 { KE_KEY, 0x8a, { KEY_WWW } }, 116 { KE_KEY, 0x8b, { KEY_MAIL } }, 117 { KE_KEY, 0x8c, { KEY_MEDIA } }, 118 119 /* Known non hotkey events don't handled or that we don't care yet */ 120 { KE_IGNORE, 0x82, }, /* backlight event */ 121 { KE_IGNORE, 0x8e, }, 122 { KE_IGNORE, 0x8f, }, 123 { KE_IGNORE, 0x90, }, 124 125 /* 126 * 'G key' generate two event codes, convert to only 127 * one event/key code for now, consider replacing by 128 * a switch (3G switch - SW_3G?) 129 */ 130 { KE_KEY, 0x96, { KEY_F14 } }, 131 { KE_KEY, 0x97, { KEY_F14 } }, 132 133 { KE_END, 0 } 134 }; 135 136 static void topstar_input_notify(struct topstar_laptop *topstar, int event) 137 { 138 if (!sparse_keymap_report_event(topstar->input, event, 1, true)) 139 pr_info("unknown event = 0x%02x\n", event); 140 } 141 142 static int topstar_input_init(struct topstar_laptop *topstar) 143 { 144 struct input_dev *input; 145 int err; 146 147 input = input_allocate_device(); 148 if (!input) 149 return -ENOMEM; 150 151 input->name = "Topstar Laptop extra buttons"; 152 input->phys = TOPSTAR_LAPTOP_CLASS "/input0"; 153 input->id.bustype = BUS_HOST; 154 input->dev.parent = &topstar->platform->dev; 155 156 err = sparse_keymap_setup(input, topstar_keymap, NULL); 157 if (err) { 158 pr_err("Unable to setup input device keymap\n"); 159 goto err_free_dev; 160 } 161 162 err = input_register_device(input); 163 if (err) { 164 pr_err("Unable to register input device\n"); 165 goto err_free_dev; 166 } 167 168 topstar->input = input; 169 return 0; 170 171 err_free_dev: 172 input_free_device(input); 173 return err; 174 } 175 176 static void topstar_input_exit(struct topstar_laptop *topstar) 177 { 178 input_unregister_device(topstar->input); 179 } 180 181 /* 182 * Platform 183 */ 184 185 static struct platform_driver topstar_platform_driver = { 186 .driver = { 187 .name = TOPSTAR_LAPTOP_CLASS, 188 }, 189 }; 190 191 static int topstar_platform_init(struct topstar_laptop *topstar) 192 { 193 int err; 194 195 topstar->platform = platform_device_alloc(TOPSTAR_LAPTOP_CLASS, PLATFORM_DEVID_NONE); 196 if (!topstar->platform) 197 return -ENOMEM; 198 199 platform_set_drvdata(topstar->platform, topstar); 200 201 err = platform_device_add(topstar->platform); 202 if (err) 203 goto err_device_put; 204 205 return 0; 206 207 err_device_put: 208 platform_device_put(topstar->platform); 209 return err; 210 } 211 212 static void topstar_platform_exit(struct topstar_laptop *topstar) 213 { 214 platform_device_unregister(topstar->platform); 215 } 216 217 /* 218 * ACPI 219 */ 220 221 static int topstar_acpi_fncx_switch(struct acpi_device *device, bool state) 222 { 223 acpi_status status; 224 u64 arg = state ? 0x86 : 0x87; 225 226 status = acpi_execute_simple_method(device->handle, "FNCX", arg); 227 if (ACPI_FAILURE(status)) { 228 pr_err("Unable to switch FNCX notifications\n"); 229 return -ENODEV; 230 } 231 232 return 0; 233 } 234 235 static void topstar_acpi_notify(struct acpi_device *device, u32 event) 236 { 237 struct topstar_laptop *topstar = acpi_driver_data(device); 238 static bool dup_evnt[2]; 239 bool *dup; 240 241 /* 0x83 and 0x84 key events comes duplicated... */ 242 if (event == 0x83 || event == 0x84) { 243 dup = &dup_evnt[event - 0x83]; 244 if (*dup) { 245 *dup = false; 246 return; 247 } 248 *dup = true; 249 } 250 251 topstar_input_notify(topstar, event); 252 } 253 254 static int topstar_acpi_init(struct topstar_laptop *topstar) 255 { 256 return topstar_acpi_fncx_switch(topstar->device, true); 257 } 258 259 static void topstar_acpi_exit(struct topstar_laptop *topstar) 260 { 261 topstar_acpi_fncx_switch(topstar->device, false); 262 } 263 264 /* 265 * Enable software-based WLAN LED control on systems with defective 266 * hardware switch. 267 */ 268 static bool led_workaround; 269 270 static int dmi_led_workaround(const struct dmi_system_id *id) 271 { 272 led_workaround = true; 273 return 0; 274 } 275 276 static const struct dmi_system_id topstar_dmi_ids[] = { 277 { 278 .callback = dmi_led_workaround, 279 .ident = "Topstar U931/RVP7", 280 .matches = { 281 DMI_MATCH(DMI_BOARD_NAME, "U931"), 282 DMI_MATCH(DMI_BOARD_VERSION, "RVP7"), 283 }, 284 }, 285 {} 286 }; 287 288 static int topstar_acpi_add(struct acpi_device *device) 289 { 290 struct topstar_laptop *topstar; 291 int err; 292 293 dmi_check_system(topstar_dmi_ids); 294 295 topstar = kzalloc(sizeof(struct topstar_laptop), GFP_KERNEL); 296 if (!topstar) 297 return -ENOMEM; 298 299 strcpy(acpi_device_name(device), "Topstar TPSACPI"); 300 strcpy(acpi_device_class(device), TOPSTAR_LAPTOP_CLASS); 301 device->driver_data = topstar; 302 topstar->device = device; 303 304 err = topstar_acpi_init(topstar); 305 if (err) 306 goto err_free; 307 308 err = topstar_platform_init(topstar); 309 if (err) 310 goto err_acpi_exit; 311 312 err = topstar_input_init(topstar); 313 if (err) 314 goto err_platform_exit; 315 316 if (led_workaround) { 317 err = topstar_led_init(topstar); 318 if (err) 319 goto err_input_exit; 320 } 321 322 return 0; 323 324 err_input_exit: 325 topstar_input_exit(topstar); 326 err_platform_exit: 327 topstar_platform_exit(topstar); 328 err_acpi_exit: 329 topstar_acpi_exit(topstar); 330 err_free: 331 kfree(topstar); 332 return err; 333 } 334 335 static int topstar_acpi_remove(struct acpi_device *device) 336 { 337 struct topstar_laptop *topstar = acpi_driver_data(device); 338 339 if (led_workaround) 340 topstar_led_exit(topstar); 341 342 topstar_input_exit(topstar); 343 topstar_platform_exit(topstar); 344 topstar_acpi_exit(topstar); 345 346 kfree(topstar); 347 return 0; 348 } 349 350 static const struct acpi_device_id topstar_device_ids[] = { 351 { "TPS0001", 0 }, 352 { "TPSACPI01", 0 }, 353 { "", 0 }, 354 }; 355 MODULE_DEVICE_TABLE(acpi, topstar_device_ids); 356 357 static struct acpi_driver topstar_acpi_driver = { 358 .name = "Topstar laptop ACPI driver", 359 .class = TOPSTAR_LAPTOP_CLASS, 360 .ids = topstar_device_ids, 361 .ops = { 362 .add = topstar_acpi_add, 363 .remove = topstar_acpi_remove, 364 .notify = topstar_acpi_notify, 365 }, 366 }; 367 368 static int __init topstar_laptop_init(void) 369 { 370 int ret; 371 372 ret = platform_driver_register(&topstar_platform_driver); 373 if (ret < 0) 374 return ret; 375 376 ret = acpi_bus_register_driver(&topstar_acpi_driver); 377 if (ret < 0) 378 goto err_driver_unreg; 379 380 pr_info("ACPI extras driver loaded\n"); 381 return 0; 382 383 err_driver_unreg: 384 platform_driver_unregister(&topstar_platform_driver); 385 return ret; 386 } 387 388 static void __exit topstar_laptop_exit(void) 389 { 390 acpi_bus_unregister_driver(&topstar_acpi_driver); 391 platform_driver_unregister(&topstar_platform_driver); 392 } 393 394 module_init(topstar_laptop_init); 395 module_exit(topstar_laptop_exit); 396 397 MODULE_AUTHOR("Herton Ronaldo Krzesinski"); 398 MODULE_AUTHOR("Guillaume Douézan-Grard"); 399 MODULE_DESCRIPTION("Topstar Laptop ACPI Extras driver"); 400 MODULE_LICENSE("GPL"); 401