400 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Topstar Laptop ACPI Extras driver4 *5 * Copyright (c) 2009 Herton Ronaldo Krzesinski <herton@mandriva.com.br>6 * Copyright (c) 2018 Guillaume Douézan-Grard7 *8 * Implementation inspired by existing x86 platform drivers, in special9 * asus/eepc/fujitsu-laptop, thanks to their authors.10 */11 12#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt13 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 * LED36 */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 it61 * 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 to70 * act as an hardware switch and disconnect the WLAN adapter but71 * it seems to be faulty on some models like the Topstar U93172 * 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 * Input104 */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 only127 * one event/key code for now, consider replacing by128 * 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 * Platform183 */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, PLATFORM_DEVID_NONE);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 * ACPI219 */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 defective266 * 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 void 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}348 349static const struct acpi_device_id topstar_device_ids[] = {350 { "TPS0001", 0 },351 { "TPSACPI01", 0 },352 { "", 0 },353};354MODULE_DEVICE_TABLE(acpi, topstar_device_ids);355 356static struct acpi_driver topstar_acpi_driver = {357 .name = "Topstar laptop ACPI driver",358 .class = TOPSTAR_LAPTOP_CLASS,359 .ids = topstar_device_ids,360 .ops = {361 .add = topstar_acpi_add,362 .remove = topstar_acpi_remove,363 .notify = topstar_acpi_notify,364 },365};366 367static int __init topstar_laptop_init(void)368{369 int ret;370 371 ret = platform_driver_register(&topstar_platform_driver);372 if (ret < 0)373 return ret;374 375 ret = acpi_bus_register_driver(&topstar_acpi_driver);376 if (ret < 0)377 goto err_driver_unreg;378 379 pr_info("ACPI extras driver loaded\n");380 return 0;381 382err_driver_unreg:383 platform_driver_unregister(&topstar_platform_driver);384 return ret;385}386 387static void __exit topstar_laptop_exit(void)388{389 acpi_bus_unregister_driver(&topstar_acpi_driver);390 platform_driver_unregister(&topstar_platform_driver);391}392 393module_init(topstar_laptop_init);394module_exit(topstar_laptop_exit);395 396MODULE_AUTHOR("Herton Ronaldo Krzesinski");397MODULE_AUTHOR("Guillaume Douézan-Grard");398MODULE_DESCRIPTION("Topstar Laptop ACPI Extras driver");399MODULE_LICENSE("GPL");400