root/drivers/platform/x86/topstar-laptop.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. topstar_led_get
  2. topstar_led_set
  3. topstar_led_init
  4. topstar_led_exit
  5. topstar_input_notify
  6. topstar_input_init
  7. topstar_input_exit
  8. topstar_platform_init
  9. topstar_platform_exit
  10. topstar_acpi_fncx_switch
  11. topstar_acpi_notify
  12. topstar_acpi_init
  13. topstar_acpi_exit
  14. dmi_led_workaround
  15. topstar_acpi_add
  16. topstar_acpi_remove
  17. topstar_laptop_init
  18. topstar_laptop_exit

   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", &params, &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, -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 
 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");

/* [<][>][^][v][top][bottom][index][help] */