brintos

brintos / linux-shallow public Read only

0
0
Text · 8.9 KiB · 62fd400 Raw
353 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Surface GPE/Lid driver to enable wakeup from suspend via the lid by4 * properly configuring the respective GPEs. Required for wakeup via lid on5 * newer Intel-based Microsoft Surface devices.6 *7 * Copyright (C) 2020-2022 Maximilian Luz <luzmaximilian@gmail.com>8 */9 10#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt11 12#include <linux/acpi.h>13#include <linux/dmi.h>14#include <linux/kernel.h>15#include <linux/module.h>16#include <linux/platform_device.h>17 18/*19 * Note: The GPE numbers for the lid devices found below have been obtained20 *       from ACPI/the DSDT table, specifically from the GPE handler for the21 *       lid.22 */23 24static const struct property_entry lid_device_props_l17[] = {25	PROPERTY_ENTRY_U32("gpe", 0x17),26	{},27};28 29static const struct property_entry lid_device_props_l4B[] = {30	PROPERTY_ENTRY_U32("gpe", 0x4B),31	{},32};33 34static const struct property_entry lid_device_props_l4D[] = {35	PROPERTY_ENTRY_U32("gpe", 0x4D),36	{},37};38 39static const struct property_entry lid_device_props_l4F[] = {40	PROPERTY_ENTRY_U32("gpe", 0x4F),41	{},42};43 44static const struct property_entry lid_device_props_l57[] = {45	PROPERTY_ENTRY_U32("gpe", 0x57),46	{},47};48 49/*50 * Note: When changing this, don't forget to check that the MODULE_ALIAS below51 *       still fits.52 */53static const struct dmi_system_id dmi_lid_device_table[] = {54	{55		.ident = "Surface Pro 4",56		.matches = {57			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),58			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"),59		},60		.driver_data = (void *)lid_device_props_l17,61	},62	{63		.ident = "Surface Pro 5",64		.matches = {65			/*66			 * We match for SKU here due to generic product name67			 * "Surface Pro".68			 */69			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),70			DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"),71		},72		.driver_data = (void *)lid_device_props_l4F,73	},74	{75		.ident = "Surface Pro 5 (LTE)",76		.matches = {77			/*78			 * We match for SKU here due to generic product name79			 * "Surface Pro"80			 */81			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),82			DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"),83		},84		.driver_data = (void *)lid_device_props_l4F,85	},86	{87		.ident = "Surface Pro 6",88		.matches = {89			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),90			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"),91		},92		.driver_data = (void *)lid_device_props_l4F,93	},94	{95		.ident = "Surface Pro 7",96		.matches = {97			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),98			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 7"),99		},100		.driver_data = (void *)lid_device_props_l4D,101	},102	{103		.ident = "Surface Pro 8",104		.matches = {105			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),106			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 8"),107		},108		.driver_data = (void *)lid_device_props_l4B,109	},110	{111		.ident = "Surface Book 1",112		.matches = {113			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),114			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"),115		},116		.driver_data = (void *)lid_device_props_l17,117	},118	{119		.ident = "Surface Book 2",120		.matches = {121			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),122			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"),123		},124		.driver_data = (void *)lid_device_props_l17,125	},126	{127		.ident = "Surface Book 3",128		.matches = {129			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),130			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 3"),131		},132		.driver_data = (void *)lid_device_props_l4D,133	},134	{135		.ident = "Surface Laptop 1",136		.matches = {137			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),138			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"),139		},140		.driver_data = (void *)lid_device_props_l57,141	},142	{143		.ident = "Surface Laptop 2",144		.matches = {145			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),146			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"),147		},148		.driver_data = (void *)lid_device_props_l57,149	},150	{151		.ident = "Surface Laptop 3 (Intel 13\")",152		.matches = {153			/*154			 * We match for SKU here due to different variants: The155			 * AMD (15") version does not rely on GPEs.156			 */157			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),158			DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_3_1867:1868"),159		},160		.driver_data = (void *)lid_device_props_l4D,161	},162	{163		.ident = "Surface Laptop 3 (Intel 15\")",164		.matches = {165			/*166			 * We match for SKU here due to different variants: The167			 * AMD (15") version does not rely on GPEs.168			 */169			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),170			DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_3_1872"),171		},172		.driver_data = (void *)lid_device_props_l4D,173	},174	{175		.ident = "Surface Laptop 4 (Intel 13\")",176		.matches = {177			/*178			 * We match for SKU here due to different variants: The179			 * AMD (15") version does not rely on GPEs.180			 */181			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),182			DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1950:1951"),183		},184		.driver_data = (void *)lid_device_props_l4B,185	},186	{187		.ident = "Surface Laptop Studio",188		.matches = {189			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),190			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop Studio"),191		},192		.driver_data = (void *)lid_device_props_l4B,193	},194	{ }195};196 197struct surface_lid_device {198	u32 gpe_number;199};200 201static int surface_lid_enable_wakeup(struct device *dev, bool enable)202{203	const struct surface_lid_device *lid = dev_get_drvdata(dev);204	int action = enable ? ACPI_GPE_ENABLE : ACPI_GPE_DISABLE;205	acpi_status status;206 207	status = acpi_set_gpe_wake_mask(NULL, lid->gpe_number, action);208	if (ACPI_FAILURE(status)) {209		dev_err(dev, "failed to set GPE wake mask: %s\n",210			acpi_format_exception(status));211		return -EINVAL;212	}213 214	return 0;215}216 217static int __maybe_unused surface_gpe_suspend(struct device *dev)218{219	return surface_lid_enable_wakeup(dev, true);220}221 222static int __maybe_unused surface_gpe_resume(struct device *dev)223{224	return surface_lid_enable_wakeup(dev, false);225}226 227static SIMPLE_DEV_PM_OPS(surface_gpe_pm, surface_gpe_suspend, surface_gpe_resume);228 229static int surface_gpe_probe(struct platform_device *pdev)230{231	struct surface_lid_device *lid;232	u32 gpe_number;233	acpi_status status;234	int ret;235 236	ret = device_property_read_u32(&pdev->dev, "gpe", &gpe_number);237	if (ret) {238		dev_err(&pdev->dev, "failed to read 'gpe' property: %d\n", ret);239		return ret;240	}241 242	lid = devm_kzalloc(&pdev->dev, sizeof(*lid), GFP_KERNEL);243	if (!lid)244		return -ENOMEM;245 246	lid->gpe_number = gpe_number;247	platform_set_drvdata(pdev, lid);248 249	status = acpi_mark_gpe_for_wake(NULL, gpe_number);250	if (ACPI_FAILURE(status)) {251		dev_err(&pdev->dev, "failed to mark GPE for wake: %s\n",252			acpi_format_exception(status));253		return -EINVAL;254	}255 256	status = acpi_enable_gpe(NULL, gpe_number);257	if (ACPI_FAILURE(status)) {258		dev_err(&pdev->dev, "failed to enable GPE: %s\n",259			acpi_format_exception(status));260		return -EINVAL;261	}262 263	ret = surface_lid_enable_wakeup(&pdev->dev, false);264	if (ret)265		acpi_disable_gpe(NULL, gpe_number);266 267	return ret;268}269 270static void surface_gpe_remove(struct platform_device *pdev)271{272	struct surface_lid_device *lid = dev_get_drvdata(&pdev->dev);273 274	/* restore default behavior without this module */275	surface_lid_enable_wakeup(&pdev->dev, false);276	acpi_disable_gpe(NULL, lid->gpe_number);277}278 279static struct platform_driver surface_gpe_driver = {280	.probe = surface_gpe_probe,281	.remove_new = surface_gpe_remove,282	.driver = {283		.name = "surface_gpe",284		.pm = &surface_gpe_pm,285		.probe_type = PROBE_PREFER_ASYNCHRONOUS,286	},287};288 289static struct platform_device *surface_gpe_device;290 291static int __init surface_gpe_init(void)292{293	const struct dmi_system_id *match;294	struct platform_device *pdev;295	struct fwnode_handle *fwnode;296	int status;297 298	match = dmi_first_match(dmi_lid_device_table);299	if (!match) {300		pr_info("no compatible Microsoft Surface device found, exiting\n");301		return -ENODEV;302	}303 304	status = platform_driver_register(&surface_gpe_driver);305	if (status)306		return status;307 308	fwnode = fwnode_create_software_node(match->driver_data, NULL);309	if (IS_ERR(fwnode)) {310		status = PTR_ERR(fwnode);311		goto err_node;312	}313 314	pdev = platform_device_alloc("surface_gpe", PLATFORM_DEVID_NONE);315	if (!pdev) {316		status = -ENOMEM;317		goto err_alloc;318	}319 320	pdev->dev.fwnode = fwnode;321 322	status = platform_device_add(pdev);323	if (status)324		goto err_add;325 326	surface_gpe_device = pdev;327	return 0;328 329err_add:330	platform_device_put(pdev);331err_alloc:332	fwnode_remove_software_node(fwnode);333err_node:334	platform_driver_unregister(&surface_gpe_driver);335	return status;336}337module_init(surface_gpe_init);338 339static void __exit surface_gpe_exit(void)340{341	struct fwnode_handle *fwnode = surface_gpe_device->dev.fwnode;342 343	platform_device_unregister(surface_gpe_device);344	platform_driver_unregister(&surface_gpe_driver);345	fwnode_remove_software_node(fwnode);346}347module_exit(surface_gpe_exit);348 349MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");350MODULE_DESCRIPTION("Surface GPE/Lid Driver");351MODULE_LICENSE("GPL");352MODULE_ALIAS("dmi:*:svnMicrosoftCorporation:pnSurface*:*");353