Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0
0002 /*
0003  * Topstar Laptop ACPI Extras driver
0004  *
0005  * Copyright (c) 2009 Herton Ronaldo Krzesinski <herton@mandriva.com.br>
0006  * Copyright (c) 2018 Guillaume Douézan-Grard
0007  *
0008  * Implementation inspired by existing x86 platform drivers, in special
0009  * asus/eepc/fujitsu-laptop, thanks to their authors.
0010  */
0011 
0012 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
0013 
0014 #include <linux/kernel.h>
0015 #include <linux/module.h>
0016 #include <linux/init.h>
0017 #include <linux/slab.h>
0018 #include <linux/acpi.h>
0019 #include <linux/dmi.h>
0020 #include <linux/input.h>
0021 #include <linux/input/sparse-keymap.h>
0022 #include <linux/leds.h>
0023 #include <linux/platform_device.h>
0024 
0025 #define TOPSTAR_LAPTOP_CLASS "topstar"
0026 
0027 struct topstar_laptop {
0028     struct acpi_device *device;
0029     struct platform_device *platform;
0030     struct input_dev *input;
0031     struct led_classdev led;
0032 };
0033 
0034 /*
0035  * LED
0036  */
0037 
0038 static enum led_brightness topstar_led_get(struct led_classdev *led)
0039 {
0040     return led->brightness;
0041 }
0042 
0043 static int topstar_led_set(struct led_classdev *led,
0044         enum led_brightness state)
0045 {
0046     struct topstar_laptop *topstar = container_of(led,
0047             struct topstar_laptop, led);
0048 
0049     struct acpi_object_list params;
0050     union acpi_object in_obj;
0051     unsigned long long int ret;
0052     acpi_status status;
0053 
0054     params.count = 1;
0055     params.pointer = &in_obj;
0056     in_obj.type = ACPI_TYPE_INTEGER;
0057     in_obj.integer.value = 0x83;
0058 
0059     /*
0060      * Topstar ACPI returns 0x30001 when the LED is ON and 0x30000 when it
0061      * is OFF.
0062      */
0063     status = acpi_evaluate_integer(topstar->device->handle,
0064             "GETX", &params, &ret);
0065     if (ACPI_FAILURE(status))
0066         return -1;
0067 
0068     /*
0069      * FNCX(0x83) toggles the LED (more precisely, it is supposed to
0070      * act as an hardware switch and disconnect the WLAN adapter but
0071      * it seems to be faulty on some models like the Topstar U931
0072      * Notebook).
0073      */
0074     if ((ret == 0x30001 && state == LED_OFF)
0075             || (ret == 0x30000 && state != LED_OFF)) {
0076         status = acpi_execute_simple_method(topstar->device->handle,
0077                 "FNCX", 0x83);
0078         if (ACPI_FAILURE(status))
0079             return -1;
0080     }
0081 
0082     return 0;
0083 }
0084 
0085 static int topstar_led_init(struct topstar_laptop *topstar)
0086 {
0087     topstar->led = (struct led_classdev) {
0088         .default_trigger = "rfkill0",
0089         .brightness_get = topstar_led_get,
0090         .brightness_set_blocking = topstar_led_set,
0091         .name = TOPSTAR_LAPTOP_CLASS "::wlan",
0092     };
0093 
0094     return led_classdev_register(&topstar->platform->dev, &topstar->led);
0095 }
0096 
0097 static void topstar_led_exit(struct topstar_laptop *topstar)
0098 {
0099     led_classdev_unregister(&topstar->led);
0100 }
0101 
0102 /*
0103  * Input
0104  */
0105 
0106 static const struct key_entry topstar_keymap[] = {
0107     { KE_KEY, 0x80, { KEY_BRIGHTNESSUP } },
0108     { KE_KEY, 0x81, { KEY_BRIGHTNESSDOWN } },
0109     { KE_KEY, 0x83, { KEY_VOLUMEUP } },
0110     { KE_KEY, 0x84, { KEY_VOLUMEDOWN } },
0111     { KE_KEY, 0x85, { KEY_MUTE } },
0112     { KE_KEY, 0x86, { KEY_SWITCHVIDEOMODE } },
0113     { KE_KEY, 0x87, { KEY_F13 } }, /* touchpad enable/disable key */
0114     { KE_KEY, 0x88, { KEY_WLAN } },
0115     { KE_KEY, 0x8a, { KEY_WWW } },
0116     { KE_KEY, 0x8b, { KEY_MAIL } },
0117     { KE_KEY, 0x8c, { KEY_MEDIA } },
0118 
0119     /* Known non hotkey events don't handled or that we don't care yet */
0120     { KE_IGNORE, 0x82, }, /* backlight event */
0121     { KE_IGNORE, 0x8e, },
0122     { KE_IGNORE, 0x8f, },
0123     { KE_IGNORE, 0x90, },
0124 
0125     /*
0126      * 'G key' generate two event codes, convert to only
0127      * one event/key code for now, consider replacing by
0128      * a switch (3G switch - SW_3G?)
0129      */
0130     { KE_KEY, 0x96, { KEY_F14 } },
0131     { KE_KEY, 0x97, { KEY_F14 } },
0132 
0133     { KE_END, 0 }
0134 };
0135 
0136 static void topstar_input_notify(struct topstar_laptop *topstar, int event)
0137 {
0138     if (!sparse_keymap_report_event(topstar->input, event, 1, true))
0139         pr_info("unknown event = 0x%02x\n", event);
0140 }
0141 
0142 static int topstar_input_init(struct topstar_laptop *topstar)
0143 {
0144     struct input_dev *input;
0145     int err;
0146 
0147     input = input_allocate_device();
0148     if (!input)
0149         return -ENOMEM;
0150 
0151     input->name = "Topstar Laptop extra buttons";
0152     input->phys = TOPSTAR_LAPTOP_CLASS "/input0";
0153     input->id.bustype = BUS_HOST;
0154     input->dev.parent = &topstar->platform->dev;
0155 
0156     err = sparse_keymap_setup(input, topstar_keymap, NULL);
0157     if (err) {
0158         pr_err("Unable to setup input device keymap\n");
0159         goto err_free_dev;
0160     }
0161 
0162     err = input_register_device(input);
0163     if (err) {
0164         pr_err("Unable to register input device\n");
0165         goto err_free_dev;
0166     }
0167 
0168     topstar->input = input;
0169     return 0;
0170 
0171 err_free_dev:
0172     input_free_device(input);
0173     return err;
0174 }
0175 
0176 static void topstar_input_exit(struct topstar_laptop *topstar)
0177 {
0178     input_unregister_device(topstar->input);
0179 }
0180 
0181 /*
0182  * Platform
0183  */
0184 
0185 static struct platform_driver topstar_platform_driver = {
0186     .driver = {
0187         .name = TOPSTAR_LAPTOP_CLASS,
0188     },
0189 };
0190 
0191 static int topstar_platform_init(struct topstar_laptop *topstar)
0192 {
0193     int err;
0194 
0195     topstar->platform = platform_device_alloc(TOPSTAR_LAPTOP_CLASS, -1);
0196     if (!topstar->platform)
0197         return -ENOMEM;
0198 
0199     platform_set_drvdata(topstar->platform, topstar);
0200 
0201     err = platform_device_add(topstar->platform);
0202     if (err)
0203         goto err_device_put;
0204 
0205     return 0;
0206 
0207 err_device_put:
0208     platform_device_put(topstar->platform);
0209     return err;
0210 }
0211 
0212 static void topstar_platform_exit(struct topstar_laptop *topstar)
0213 {
0214     platform_device_unregister(topstar->platform);
0215 }
0216 
0217 /*
0218  * ACPI
0219  */
0220 
0221 static int topstar_acpi_fncx_switch(struct acpi_device *device, bool state)
0222 {
0223     acpi_status status;
0224     u64 arg = state ? 0x86 : 0x87;
0225 
0226     status = acpi_execute_simple_method(device->handle, "FNCX", arg);
0227     if (ACPI_FAILURE(status)) {
0228         pr_err("Unable to switch FNCX notifications\n");
0229         return -ENODEV;
0230     }
0231 
0232     return 0;
0233 }
0234 
0235 static void topstar_acpi_notify(struct acpi_device *device, u32 event)
0236 {
0237     struct topstar_laptop *topstar = acpi_driver_data(device);
0238     static bool dup_evnt[2];
0239     bool *dup;
0240 
0241     /* 0x83 and 0x84 key events comes duplicated... */
0242     if (event == 0x83 || event == 0x84) {
0243         dup = &dup_evnt[event - 0x83];
0244         if (*dup) {
0245             *dup = false;
0246             return;
0247         }
0248         *dup = true;
0249     }
0250 
0251     topstar_input_notify(topstar, event);
0252 }
0253 
0254 static int topstar_acpi_init(struct topstar_laptop *topstar)
0255 {
0256     return topstar_acpi_fncx_switch(topstar->device, true);
0257 }
0258 
0259 static void topstar_acpi_exit(struct topstar_laptop *topstar)
0260 {
0261     topstar_acpi_fncx_switch(topstar->device, false);
0262 }
0263 
0264 /*
0265  * Enable software-based WLAN LED control on systems with defective
0266  * hardware switch.
0267  */
0268 static bool led_workaround;
0269 
0270 static int dmi_led_workaround(const struct dmi_system_id *id)
0271 {
0272     led_workaround = true;
0273     return 0;
0274 }
0275 
0276 static const struct dmi_system_id topstar_dmi_ids[] = {
0277     {
0278         .callback = dmi_led_workaround,
0279         .ident = "Topstar U931/RVP7",
0280         .matches = {
0281             DMI_MATCH(DMI_BOARD_NAME, "U931"),
0282             DMI_MATCH(DMI_BOARD_VERSION, "RVP7"),
0283         },
0284     },
0285     {}
0286 };
0287 
0288 static int topstar_acpi_add(struct acpi_device *device)
0289 {
0290     struct topstar_laptop *topstar;
0291     int err;
0292 
0293     dmi_check_system(topstar_dmi_ids);
0294 
0295     topstar = kzalloc(sizeof(struct topstar_laptop), GFP_KERNEL);
0296     if (!topstar)
0297         return -ENOMEM;
0298 
0299     strcpy(acpi_device_name(device), "Topstar TPSACPI");
0300     strcpy(acpi_device_class(device), TOPSTAR_LAPTOP_CLASS);
0301     device->driver_data = topstar;
0302     topstar->device = device;
0303 
0304     err = topstar_acpi_init(topstar);
0305     if (err)
0306         goto err_free;
0307 
0308     err = topstar_platform_init(topstar);
0309     if (err)
0310         goto err_acpi_exit;
0311 
0312     err = topstar_input_init(topstar);
0313     if (err)
0314         goto err_platform_exit;
0315 
0316     if (led_workaround) {
0317         err = topstar_led_init(topstar);
0318         if (err)
0319             goto err_input_exit;
0320     }
0321 
0322     return 0;
0323 
0324 err_input_exit:
0325     topstar_input_exit(topstar);
0326 err_platform_exit:
0327     topstar_platform_exit(topstar);
0328 err_acpi_exit:
0329     topstar_acpi_exit(topstar);
0330 err_free:
0331     kfree(topstar);
0332     return err;
0333 }
0334 
0335 static int topstar_acpi_remove(struct acpi_device *device)
0336 {
0337     struct topstar_laptop *topstar = acpi_driver_data(device);
0338 
0339     if (led_workaround)
0340         topstar_led_exit(topstar);
0341 
0342     topstar_input_exit(topstar);
0343     topstar_platform_exit(topstar);
0344     topstar_acpi_exit(topstar);
0345 
0346     kfree(topstar);
0347     return 0;
0348 }
0349 
0350 static const struct acpi_device_id topstar_device_ids[] = {
0351     { "TPS0001", 0 },
0352     { "TPSACPI01", 0 },
0353     { "", 0 },
0354 };
0355 MODULE_DEVICE_TABLE(acpi, topstar_device_ids);
0356 
0357 static struct acpi_driver topstar_acpi_driver = {
0358     .name = "Topstar laptop ACPI driver",
0359     .class = TOPSTAR_LAPTOP_CLASS,
0360     .ids = topstar_device_ids,
0361     .ops = {
0362         .add = topstar_acpi_add,
0363         .remove = topstar_acpi_remove,
0364         .notify = topstar_acpi_notify,
0365     },
0366 };
0367 
0368 static int __init topstar_laptop_init(void)
0369 {
0370     int ret;
0371 
0372     ret = platform_driver_register(&topstar_platform_driver);
0373     if (ret < 0)
0374         return ret;
0375 
0376     ret = acpi_bus_register_driver(&topstar_acpi_driver);
0377     if (ret < 0)
0378         goto err_driver_unreg;
0379 
0380     pr_info("ACPI extras driver loaded\n");
0381     return 0;
0382 
0383 err_driver_unreg:
0384     platform_driver_unregister(&topstar_platform_driver);
0385     return ret;
0386 }
0387 
0388 static void __exit topstar_laptop_exit(void)
0389 {
0390     acpi_bus_unregister_driver(&topstar_acpi_driver);
0391     platform_driver_unregister(&topstar_platform_driver);
0392 }
0393 
0394 module_init(topstar_laptop_init);
0395 module_exit(topstar_laptop_exit);
0396 
0397 MODULE_AUTHOR("Herton Ronaldo Krzesinski");
0398 MODULE_AUTHOR("Guillaume Douézan-Grard");
0399 MODULE_DESCRIPTION("Topstar Laptop ACPI Extras driver");
0400 MODULE_LICENSE("GPL");