diff options
Diffstat (limited to 'drivers/platform/x86/topstar-laptop.c')
-rw-r--r-- | drivers/platform/x86/topstar-laptop.c | 363 |
1 files changed, 289 insertions, 74 deletions
diff --git a/drivers/platform/x86/topstar-laptop.c b/drivers/platform/x86/topstar-laptop.c index 1032c00b907b..f7761d98c0fd 100644 --- a/drivers/platform/x86/topstar-laptop.c +++ b/drivers/platform/x86/topstar-laptop.c @@ -1,14 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0 /* - * ACPI driver for Topstar notebooks (hotkeys support only) + * Topstar Laptop ACPI Extras driver * * Copyright (c) 2009 Herton Ronaldo Krzesinski <herton@mandriva.com.br> + * Copyright (c) 2018 Guillaume Douézan-Grard * * Implementation inspired by existing x86 platform drivers, in special - * asus/eepc/fujitsu-laptop, thanks to their authors - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. + * asus/eepc/fujitsu-laptop, thanks to their authors. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -18,15 +16,93 @@ #include <linux/init.h> #include <linux/slab.h> #include <linux/acpi.h> +#include <linux/dmi.h> #include <linux/input.h> #include <linux/input/sparse-keymap.h> +#include <linux/leds.h> +#include <linux/platform_device.h> -#define ACPI_TOPSTAR_CLASS "topstar" +#define TOPSTAR_LAPTOP_CLASS "topstar" -struct topstar_hkey { - struct input_dev *inputdev; +struct topstar_laptop { + struct acpi_device *device; + struct platform_device *platform; + struct input_dev *input; + struct led_classdev led; }; +/* + * LED + */ + +static enum led_brightness topstar_led_get(struct led_classdev *led) +{ + return led->brightness; +} + +static int topstar_led_set(struct led_classdev *led, + enum led_brightness state) +{ + struct topstar_laptop *topstar = container_of(led, + struct topstar_laptop, led); + + struct acpi_object_list params; + union acpi_object in_obj; + unsigned long long int ret; + acpi_status status; + + params.count = 1; + params.pointer = &in_obj; + in_obj.type = ACPI_TYPE_INTEGER; + in_obj.integer.value = 0x83; + + /* + * Topstar ACPI returns 0x30001 when the LED is ON and 0x30000 when it + * is OFF. + */ + status = acpi_evaluate_integer(topstar->device->handle, + "GETX", ¶ms, &ret); + if (ACPI_FAILURE(status)) + return -1; + + /* + * FNCX(0x83) toggles the LED (more precisely, it is supposed to + * act as an hardware switch and disconnect the WLAN adapter but + * it seems to be faulty on some models like the Topstar U931 + * Notebook). + */ + if ((ret == 0x30001 && state == LED_OFF) + || (ret == 0x30000 && state != LED_OFF)) { + status = acpi_execute_simple_method(topstar->device->handle, + "FNCX", 0x83); + if (ACPI_FAILURE(status)) + return -1; + } + + return 0; +} + +static int topstar_led_init(struct topstar_laptop *topstar) +{ + topstar->led = (struct led_classdev) { + .default_trigger = "rfkill0", + .brightness_get = topstar_led_get, + .brightness_set_blocking = topstar_led_set, + .name = TOPSTAR_LAPTOP_CLASS "::wlan", + }; + + return led_classdev_register(&topstar->platform->dev, &topstar->led); +} + +static void topstar_led_exit(struct topstar_laptop *topstar) +{ + led_classdev_unregister(&topstar->led); +} + +/* + * Input + */ + static const struct key_entry topstar_keymap[] = { { KE_KEY, 0x80, { KEY_BRIGHTNESSUP } }, { KE_KEY, 0x81, { KEY_BRIGHTNESSDOWN } }, @@ -57,107 +133,217 @@ static const struct key_entry topstar_keymap[] = { { KE_END, 0 } }; -static void acpi_topstar_notify(struct acpi_device *device, u32 event) +static void topstar_input_notify(struct topstar_laptop *topstar, int event) { - static bool dup_evnt[2]; - bool *dup; - struct topstar_hkey *hkey = acpi_driver_data(device); - - /* 0x83 and 0x84 key events comes duplicated... */ - if (event == 0x83 || event == 0x84) { - dup = &dup_evnt[event - 0x83]; - if (*dup) { - *dup = false; - return; - } - *dup = true; - } - - if (!sparse_keymap_report_event(hkey->inputdev, event, 1, true)) + if (!sparse_keymap_report_event(topstar->input, event, 1, true)) pr_info("unknown event = 0x%02x\n", event); } -static int acpi_topstar_fncx_switch(struct acpi_device *device, bool state) -{ - acpi_status status; - - status = acpi_execute_simple_method(device->handle, "FNCX", - state ? 0x86 : 0x87); - if (ACPI_FAILURE(status)) { - pr_err("Unable to switch FNCX notifications\n"); - return -ENODEV; - } - - return 0; -} - -static int acpi_topstar_init_hkey(struct topstar_hkey *hkey) +static int topstar_input_init(struct topstar_laptop *topstar) { struct input_dev *input; - int error; + int err; input = input_allocate_device(); if (!input) return -ENOMEM; input->name = "Topstar Laptop extra buttons"; - input->phys = "topstar/input0"; + input->phys = TOPSTAR_LAPTOP_CLASS "/input0"; input->id.bustype = BUS_HOST; + input->dev.parent = &topstar->platform->dev; - error = sparse_keymap_setup(input, topstar_keymap, NULL); - if (error) { + err = sparse_keymap_setup(input, topstar_keymap, NULL); + if (err) { pr_err("Unable to setup input device keymap\n"); goto err_free_dev; } - error = input_register_device(input); - if (error) { + err = input_register_device(input); + if (err) { pr_err("Unable to register input device\n"); goto err_free_dev; } - hkey->inputdev = input; + topstar->input = input; return 0; - err_free_dev: +err_free_dev: input_free_device(input); - return error; + return err; } -static int acpi_topstar_add(struct acpi_device *device) +static void topstar_input_exit(struct topstar_laptop *topstar) { - struct topstar_hkey *tps_hkey; + input_unregister_device(topstar->input); +} - tps_hkey = kzalloc(sizeof(struct topstar_hkey), GFP_KERNEL); - if (!tps_hkey) +/* + * Platform + */ + +static struct platform_driver topstar_platform_driver = { + .driver = { + .name = TOPSTAR_LAPTOP_CLASS, + }, +}; + +static int topstar_platform_init(struct topstar_laptop *topstar) +{ + int err; + + topstar->platform = platform_device_alloc(TOPSTAR_LAPTOP_CLASS, -1); + if (!topstar->platform) return -ENOMEM; - strcpy(acpi_device_name(device), "Topstar TPSACPI"); - strcpy(acpi_device_class(device), ACPI_TOPSTAR_CLASS); + platform_set_drvdata(topstar->platform, topstar); + + err = platform_device_add(topstar->platform); + if (err) + goto err_device_put; + + return 0; - if (acpi_topstar_fncx_switch(device, true)) - goto add_err; +err_device_put: + platform_device_put(topstar->platform); + return err; +} + +static void topstar_platform_exit(struct topstar_laptop *topstar) +{ + platform_device_unregister(topstar->platform); +} + +/* + * ACPI + */ + +static int topstar_acpi_fncx_switch(struct acpi_device *device, bool state) +{ + acpi_status status; + u64 arg = state ? 0x86 : 0x87; - if (acpi_topstar_init_hkey(tps_hkey)) - goto add_err; + status = acpi_execute_simple_method(device->handle, "FNCX", arg); + if (ACPI_FAILURE(status)) { + pr_err("Unable to switch FNCX notifications\n"); + return -ENODEV; + } - device->driver_data = tps_hkey; return 0; +} -add_err: - kfree(tps_hkey); - return -ENODEV; +static void topstar_acpi_notify(struct acpi_device *device, u32 event) +{ + struct topstar_laptop *topstar = acpi_driver_data(device); + static bool dup_evnt[2]; + bool *dup; + + /* 0x83 and 0x84 key events comes duplicated... */ + if (event == 0x83 || event == 0x84) { + dup = &dup_evnt[event - 0x83]; + if (*dup) { + *dup = false; + return; + } + *dup = true; + } + + topstar_input_notify(topstar, event); } -static int acpi_topstar_remove(struct acpi_device *device) +static int topstar_acpi_init(struct topstar_laptop *topstar) { - struct topstar_hkey *tps_hkey = acpi_driver_data(device); + return topstar_acpi_fncx_switch(topstar->device, true); +} - acpi_topstar_fncx_switch(device, false); +static void topstar_acpi_exit(struct topstar_laptop *topstar) +{ + topstar_acpi_fncx_switch(topstar->device, false); +} - input_unregister_device(tps_hkey->inputdev); - kfree(tps_hkey); +/* + * Enable software-based WLAN LED control on systems with defective + * hardware switch. + */ +static bool led_workaround; +static int dmi_led_workaround(const struct dmi_system_id *id) +{ + led_workaround = true; + return 0; +} + +static const struct dmi_system_id topstar_dmi_ids[] = { + { + .callback = dmi_led_workaround, + .ident = "Topstar U931/RVP7", + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "U931"), + DMI_MATCH(DMI_BOARD_VERSION, "RVP7"), + }, + }, + {} +}; + +static int topstar_acpi_add(struct acpi_device *device) +{ + struct topstar_laptop *topstar; + int err; + + dmi_check_system(topstar_dmi_ids); + + topstar = kzalloc(sizeof(struct topstar_laptop), GFP_KERNEL); + if (!topstar) + return -ENOMEM; + + strcpy(acpi_device_name(device), "Topstar TPSACPI"); + strcpy(acpi_device_class(device), TOPSTAR_LAPTOP_CLASS); + device->driver_data = topstar; + topstar->device = device; + + err = topstar_acpi_init(topstar); + if (err) + goto err_free; + + err = topstar_platform_init(topstar); + if (err) + goto err_acpi_exit; + + err = topstar_input_init(topstar); + if (err) + goto err_platform_exit; + + if (led_workaround) { + err = topstar_led_init(topstar); + if (err) + goto err_input_exit; + } + + return 0; + +err_input_exit: + topstar_input_exit(topstar); +err_platform_exit: + topstar_platform_exit(topstar); +err_acpi_exit: + topstar_acpi_exit(topstar); +err_free: + kfree(topstar); + return err; +} + +static int topstar_acpi_remove(struct acpi_device *device) +{ + struct topstar_laptop *topstar = acpi_driver_data(device); + + if (led_workaround) + topstar_led_exit(topstar); + + topstar_input_exit(topstar); + topstar_platform_exit(topstar); + topstar_acpi_exit(topstar); + + kfree(topstar); return 0; } @@ -168,18 +354,47 @@ static const struct acpi_device_id topstar_device_ids[] = { }; MODULE_DEVICE_TABLE(acpi, topstar_device_ids); -static struct acpi_driver acpi_topstar_driver = { +static struct acpi_driver topstar_acpi_driver = { .name = "Topstar laptop ACPI driver", - .class = ACPI_TOPSTAR_CLASS, + .class = TOPSTAR_LAPTOP_CLASS, .ids = topstar_device_ids, .ops = { - .add = acpi_topstar_add, - .remove = acpi_topstar_remove, - .notify = acpi_topstar_notify, + .add = topstar_acpi_add, + .remove = topstar_acpi_remove, + .notify = topstar_acpi_notify, }, }; -module_acpi_driver(acpi_topstar_driver); + +static int __init topstar_laptop_init(void) +{ + int ret; + + ret = platform_driver_register(&topstar_platform_driver); + if (ret < 0) + return ret; + + ret = acpi_bus_register_driver(&topstar_acpi_driver); + if (ret < 0) + goto err_driver_unreg; + + pr_info("ACPI extras driver loaded\n"); + return 0; + +err_driver_unreg: + platform_driver_unregister(&topstar_platform_driver); + return ret; +} + +static void __exit topstar_laptop_exit(void) +{ + acpi_bus_unregister_driver(&topstar_acpi_driver); + platform_driver_unregister(&topstar_platform_driver); +} + +module_init(topstar_laptop_init); +module_exit(topstar_laptop_exit); MODULE_AUTHOR("Herton Ronaldo Krzesinski"); +MODULE_AUTHOR("Guillaume Douézan-Grard"); MODULE_DESCRIPTION("Topstar Laptop ACPI Extras driver"); MODULE_LICENSE("GPL"); |