topstar-laptop.c (8877B)
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 27struct 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 38static enum led_brightness topstar_led_get(struct led_classdev *led) 39{ 40 return led->brightness; 41} 42 43static 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 85static 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 97static void topstar_led_exit(struct topstar_laptop *topstar) 98{ 99 led_classdev_unregister(&topstar->led); 100} 101 102/* 103 * Input 104 */ 105 106static 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 136static 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 142static 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 171err_free_dev: 172 input_free_device(input); 173 return err; 174} 175 176static void topstar_input_exit(struct topstar_laptop *topstar) 177{ 178 input_unregister_device(topstar->input); 179} 180 181/* 182 * Platform 183 */ 184 185static struct platform_driver topstar_platform_driver = { 186 .driver = { 187 .name = TOPSTAR_LAPTOP_CLASS, 188 }, 189}; 190 191static int topstar_platform_init(struct topstar_laptop *topstar) 192{ 193 int err; 194 195 topstar->platform = platform_device_alloc(TOPSTAR_LAPTOP_CLASS, -1); 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 207err_device_put: 208 platform_device_put(topstar->platform); 209 return err; 210} 211 212static void topstar_platform_exit(struct topstar_laptop *topstar) 213{ 214 platform_device_unregister(topstar->platform); 215} 216 217/* 218 * ACPI 219 */ 220 221static 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 235static 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 254static int topstar_acpi_init(struct topstar_laptop *topstar) 255{ 256 return topstar_acpi_fncx_switch(topstar->device, true); 257} 258 259static 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 */ 268static bool led_workaround; 269 270static int dmi_led_workaround(const struct dmi_system_id *id) 271{ 272 led_workaround = true; 273 return 0; 274} 275 276static 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 288static 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 324err_input_exit: 325 topstar_input_exit(topstar); 326err_platform_exit: 327 topstar_platform_exit(topstar); 328err_acpi_exit: 329 topstar_acpi_exit(topstar); 330err_free: 331 kfree(topstar); 332 return err; 333} 334 335static 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 350static const struct acpi_device_id topstar_device_ids[] = { 351 { "TPS0001", 0 }, 352 { "TPSACPI01", 0 }, 353 { "", 0 }, 354}; 355MODULE_DEVICE_TABLE(acpi, topstar_device_ids); 356 357static 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 368static 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 383err_driver_unreg: 384 platform_driver_unregister(&topstar_platform_driver); 385 return ret; 386} 387 388static void __exit topstar_laptop_exit(void) 389{ 390 acpi_bus_unregister_driver(&topstar_acpi_driver); 391 platform_driver_unregister(&topstar_platform_driver); 392} 393 394module_init(topstar_laptop_init); 395module_exit(topstar_laptop_exit); 396 397MODULE_AUTHOR("Herton Ronaldo Krzesinski"); 398MODULE_AUTHOR("Guillaume Douézan-Grard"); 399MODULE_DESCRIPTION("Topstar Laptop ACPI Extras driver"); 400MODULE_LICENSE("GPL");