diff options
Diffstat (limited to 'drivers/platform/x86/topstar-laptop.c')
| -rw-r--r-- | drivers/platform/x86/topstar-laptop.c | 374 |
1 files changed, 290 insertions, 84 deletions
diff --git a/drivers/platform/x86/topstar-laptop.c b/drivers/platform/x86/topstar-laptop.c index 4ab618c63b45..53fc2b364552 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,36 +133,97 @@ 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); + if (!sparse_keymap_report_event(topstar->input, event, 1, true)) + pr_info("unknown event = 0x%02x\n", event); +} - /* 0x83 and 0x84 key events comes duplicated... */ - if (event == 0x83 || event == 0x84) { - dup = &dup_evnt[event - 0x83]; - if (*dup) { - *dup = false; - return; - } - *dup = true; +static int topstar_input_init(struct topstar_laptop *topstar) +{ + struct input_dev *input; + int err; + + input = input_allocate_device(); + if (!input) + return -ENOMEM; + + input->name = "Topstar Laptop extra buttons"; + input->phys = TOPSTAR_LAPTOP_CLASS "/input0"; + input->id.bustype = BUS_HOST; + input->dev.parent = &topstar->platform->dev; + + err = sparse_keymap_setup(input, topstar_keymap, NULL); + if (err) { + pr_err("Unable to setup input device keymap\n"); + goto err_free_dev; } - if (!sparse_keymap_report_event(hkey->inputdev, event, 1, true)) - pr_info("unknown event = 0x%02x\n", event); + err = input_register_device(input); + if (err) { + pr_err("Unable to register input device\n"); + goto err_free_dev; + } + + topstar->input = input; + return 0; + +err_free_dev: + input_free_device(input); + return err; +} + +static void topstar_input_exit(struct topstar_laptop *topstar) +{ + input_unregister_device(topstar->input); +} + +/* + * 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, PLATFORM_DEVID_NONE); + if (!topstar->platform) + return -ENOMEM; + + platform_set_drvdata(topstar->platform, topstar); + + err = platform_device_add(topstar->platform); + if (err) + goto err_device_put; + + return 0; + +err_device_put: + platform_device_put(topstar->platform); + return err; } -static int acpi_topstar_fncx_switch(struct acpi_device *device, bool state) +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; - union acpi_object fncx_params[1] = { - { .type = ACPI_TYPE_INTEGER } - }; - struct acpi_object_list fncx_arg_list = { 1, &fncx_params[0] }; + u64 arg = state ? 0x86 : 0x87; - fncx_params[0].integer.value = state ? 0x86 : 0x87; - status = acpi_evaluate_object(device->handle, "FNCX", &fncx_arg_list, NULL); + status = acpi_execute_simple_method(device->handle, "FNCX", arg); if (ACPI_FAILURE(status)) { pr_err("Unable to switch FNCX notifications\n"); return -ENODEV; @@ -95,99 +232,168 @@ static int acpi_topstar_fncx_switch(struct acpi_device *device, bool state) return 0; } -static int acpi_topstar_init_hkey(struct topstar_hkey *hkey) +static void topstar_acpi_notify(struct acpi_device *device, u32 event) { - struct input_dev *input; - int error; + struct topstar_laptop *topstar = acpi_driver_data(device); + static bool dup_evnt[2]; + bool *dup; - input = input_allocate_device(); - if (!input) { - pr_err("Unable to allocate input device\n"); - return -ENOMEM; + /* 0x83 and 0x84 key events comes duplicated... */ + if (event == 0x83 || event == 0x84) { + dup = &dup_evnt[event - 0x83]; + if (*dup) { + *dup = false; + return; + } + *dup = true; } - input->name = "Topstar Laptop extra buttons"; - input->phys = "topstar/input0"; - input->id.bustype = BUS_HOST; + topstar_input_notify(topstar, event); +} - error = sparse_keymap_setup(input, topstar_keymap, NULL); - if (error) { - pr_err("Unable to setup input device keymap\n"); - goto err_free_dev; - } +static int topstar_acpi_init(struct topstar_laptop *topstar) +{ + return topstar_acpi_fncx_switch(topstar->device, true); +} - error = input_register_device(input); - if (error) { - pr_err("Unable to register input device\n"); - goto err_free_keymap; - } +static void topstar_acpi_exit(struct topstar_laptop *topstar) +{ + topstar_acpi_fncx_switch(topstar->device, false); +} - hkey->inputdev = input; - return 0; +/* + * Enable software-based WLAN LED control on systems with defective + * hardware switch. + */ +static bool led_workaround; - err_free_keymap: - sparse_keymap_free(input); - err_free_dev: - input_free_device(input); - return error; +static int dmi_led_workaround(const struct dmi_system_id *id) +{ + led_workaround = true; + return 0; } -static int acpi_topstar_add(struct acpi_device *device) +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_hkey *tps_hkey; + struct topstar_laptop *topstar; + int err; - tps_hkey = kzalloc(sizeof(struct topstar_hkey), GFP_KERNEL); - if (!tps_hkey) + 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), ACPI_TOPSTAR_CLASS); + strscpy(acpi_device_name(device), "Topstar TPSACPI"); + strscpy(acpi_device_class(device), TOPSTAR_LAPTOP_CLASS); + device->driver_data = topstar; + topstar->device = device; + + err = topstar_acpi_init(topstar); + if (err) + goto err_free; - if (acpi_topstar_fncx_switch(device, true)) - goto add_err; + err = topstar_platform_init(topstar); + if (err) + goto err_acpi_exit; - if (acpi_topstar_init_hkey(tps_hkey)) - goto add_err; + 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; + } - device->driver_data = tps_hkey; return 0; -add_err: - kfree(tps_hkey); - return -ENODEV; +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 acpi_topstar_remove(struct acpi_device *device) +static void topstar_acpi_remove(struct acpi_device *device) { - struct topstar_hkey *tps_hkey = acpi_driver_data(device); + struct topstar_laptop *topstar = acpi_driver_data(device); - acpi_topstar_fncx_switch(device, false); + if (led_workaround) + topstar_led_exit(topstar); - sparse_keymap_free(tps_hkey->inputdev); - input_unregister_device(tps_hkey->inputdev); - kfree(tps_hkey); + topstar_input_exit(topstar); + topstar_platform_exit(topstar); + topstar_acpi_exit(topstar); - return 0; + kfree(topstar); } static const struct acpi_device_id topstar_device_ids[] = { + { "TPS0001", 0 }, { "TPSACPI01", 0 }, { "", 0 }, }; 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"); |
