brintos

brintos / linux-shallow public Read only

0
0
Text · 8.7 KiB · 20df1eb Raw
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", &params, &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