diff options
Diffstat (limited to 'drivers/platform')
53 files changed, 3813 insertions, 355 deletions
| diff --git a/drivers/platform/chrome/chromeos_laptop.c b/drivers/platform/chrome/chromeos_laptop.c index 472a03daa869..4e14b4d6635d 100644 --- a/drivers/platform/chrome/chromeos_laptop.c +++ b/drivers/platform/chrome/chromeos_laptop.c @@ -52,12 +52,15 @@ struct i2c_peripheral {  	enum i2c_adapter_type type;  	u32 pci_devid; +	const struct property_entry *properties; +  	struct i2c_client *client;  };  struct acpi_peripheral {  	char hid[ACPI_ID_LEN]; -	const struct property_entry *properties; +	struct software_node swnode; +	struct i2c_client *client;  };  struct chromeos_laptop { @@ -68,7 +71,7 @@ struct chromeos_laptop {  	struct i2c_peripheral *i2c_peripherals;  	unsigned int num_i2c_peripherals; -	const struct acpi_peripheral *acpi_peripherals; +	struct acpi_peripheral *acpi_peripherals;  	unsigned int num_acpi_peripherals;  }; @@ -161,7 +164,7 @@ static void chromeos_laptop_check_adapter(struct i2c_adapter *adapter)  static bool chromeos_laptop_adjust_client(struct i2c_client *client)  { -	const struct acpi_peripheral *acpi_dev; +	struct acpi_peripheral *acpi_dev;  	struct acpi_device_id acpi_ids[2] = { };  	int i;  	int error; @@ -175,8 +178,7 @@ static bool chromeos_laptop_adjust_client(struct i2c_client *client)  		memcpy(acpi_ids[0].id, acpi_dev->hid, ACPI_ID_LEN);  		if (acpi_match_device(acpi_ids, &client->dev)) { -			error = device_add_properties(&client->dev, -						      acpi_dev->properties); +			error = device_add_software_node(&client->dev, &acpi_dev->swnode);  			if (error) {  				dev_err(&client->dev,  					"failed to add properties: %d\n", @@ -184,6 +186,8 @@ static bool chromeos_laptop_adjust_client(struct i2c_client *client)  				break;  			} +			acpi_dev->client = client; +  			return true;  		}  	} @@ -193,15 +197,28 @@ static bool chromeos_laptop_adjust_client(struct i2c_client *client)  static void chromeos_laptop_detach_i2c_client(struct i2c_client *client)  { +	struct acpi_peripheral *acpi_dev;  	struct i2c_peripheral *i2c_dev;  	int i; -	for (i = 0; i < cros_laptop->num_i2c_peripherals; i++) { -		i2c_dev = &cros_laptop->i2c_peripherals[i]; +	if (has_acpi_companion(&client->dev)) +		for (i = 0; i < cros_laptop->num_acpi_peripherals; i++) { +			acpi_dev = &cros_laptop->acpi_peripherals[i]; -		if (i2c_dev->client == client) -			i2c_dev->client = NULL; -	} +			if (acpi_dev->client == client) { +				acpi_dev->client = NULL; +				return; +			} +		} +	else +		for (i = 0; i < cros_laptop->num_i2c_peripherals; i++) { +			i2c_dev = &cros_laptop->i2c_peripherals[i]; + +			if (i2c_dev->client == client) { +				i2c_dev->client = NULL; +				return; +			} +		}  }  static int chromeos_laptop_i2c_notifier_call(struct notifier_block *nb, @@ -302,28 +319,26 @@ static struct i2c_peripheral chromebook_pixel_peripherals[] __initdata = {  		.board_info	= {  			I2C_BOARD_INFO("atmel_mxt_ts",  					ATMEL_TS_I2C_ADDR), -			.properties	= -				chromebook_atmel_touchscreen_props,  			.flags		= I2C_CLIENT_WAKE,  		},  		.dmi_name	= "touchscreen",  		.irqflags	= IRQF_TRIGGER_FALLING,  		.type		= I2C_ADAPTER_PANEL,  		.alt_addr	= ATMEL_TS_I2C_BL_ADDR, +		.properties	= chromebook_atmel_touchscreen_props,  	},  	/* Touchpad. */  	{  		.board_info	= {  			I2C_BOARD_INFO("atmel_mxt_tp",  					ATMEL_TP_I2C_ADDR), -			.properties	= -				chromebook_pixel_trackpad_props,  			.flags		= I2C_CLIENT_WAKE,  		},  		.dmi_name	= "trackpad",  		.irqflags	= IRQF_TRIGGER_FALLING,  		.type		= I2C_ADAPTER_VGADDC,  		.alt_addr	= ATMEL_TP_I2C_BL_ADDR, +		.properties	= chromebook_pixel_trackpad_props,  	},  	/* Light Sensor. */  	{ @@ -414,8 +429,6 @@ static struct i2c_peripheral acer_c720_peripherals[] __initdata = {  		.board_info	= {  			I2C_BOARD_INFO("atmel_mxt_ts",  					ATMEL_TS_I2C_ADDR), -			.properties	= -				chromebook_atmel_touchscreen_props,  			.flags		= I2C_CLIENT_WAKE,  		},  		.dmi_name	= "touchscreen", @@ -423,6 +436,7 @@ static struct i2c_peripheral acer_c720_peripherals[] __initdata = {  		.type		= I2C_ADAPTER_DESIGNWARE,  		.pci_devid	= PCI_DEVID(0, PCI_DEVFN(0x15, 0x2)),  		.alt_addr	= ATMEL_TS_I2C_BL_ADDR, +		.properties	= chromebook_atmel_touchscreen_props,  	},  	/* Touchpad. */  	{ @@ -498,12 +512,16 @@ static struct acpi_peripheral samus_peripherals[] __initdata = {  	/* Touchpad */  	{  		.hid		= "ATML0000", -		.properties	= samus_trackpad_props, +		.swnode		= { +			.properties = samus_trackpad_props, +		},  	},  	/* Touchsceen */  	{  		.hid		= "ATML0001", -		.properties	= chromebook_atmel_touchscreen_props, +		.swnode		= { +			.properties = chromebook_atmel_touchscreen_props, +		},  	},  };  DECLARE_ACPI_CROS_LAPTOP(samus); @@ -512,12 +530,16 @@ static struct acpi_peripheral generic_atmel_peripherals[] __initdata = {  	/* Touchpad */  	{  		.hid		= "ATML0000", -		.properties	= chromebook_pixel_trackpad_props, +		.swnode		= { +			.properties = chromebook_pixel_trackpad_props, +		},  	},  	/* Touchsceen */  	{  		.hid		= "ATML0001", -		.properties	= chromebook_atmel_touchscreen_props, +		.swnode		= { +			.properties = chromebook_atmel_touchscreen_props, +		},  	},  };  DECLARE_ACPI_CROS_LAPTOP(generic_atmel); @@ -743,12 +765,11 @@ chromeos_laptop_prepare_i2c_peripherals(struct chromeos_laptop *cros_laptop,  		if (error)  			goto err_out; -		/* We need to deep-copy properties */ -		if (info->properties) { -			info->properties = -				property_entries_dup(info->properties); -			if (IS_ERR(info->properties)) { -				error = PTR_ERR(info->properties); +		/* Create primary fwnode for the device - copies everything */ +		if (i2c_dev->properties) { +			info->fwnode = fwnode_create_software_node(i2c_dev->properties, NULL); +			if (IS_ERR(info->fwnode)) { +				error = PTR_ERR(info->fwnode);  				goto err_out;  			}  		} @@ -760,8 +781,8 @@ err_out:  	while (--i >= 0) {  		i2c_dev = &cros_laptop->i2c_peripherals[i];  		info = &i2c_dev->board_info; -		if (info->properties) -			property_entries_free(info->properties); +		if (!IS_ERR_OR_NULL(info->fwnode)) +			fwnode_remove_software_node(info->fwnode);  	}  	kfree(cros_laptop->i2c_peripherals);  	return error; @@ -801,11 +822,11 @@ chromeos_laptop_prepare_acpi_peripherals(struct chromeos_laptop *cros_laptop,  		*acpi_dev = *src_dev;  		/* We need to deep-copy properties */ -		if (src_dev->properties) { -			acpi_dev->properties = -				property_entries_dup(src_dev->properties); -			if (IS_ERR(acpi_dev->properties)) { -				error = PTR_ERR(acpi_dev->properties); +		if (src_dev->swnode.properties) { +			acpi_dev->swnode.properties = +				property_entries_dup(src_dev->swnode.properties); +			if (IS_ERR(acpi_dev->swnode.properties)) { +				error = PTR_ERR(acpi_dev->swnode.properties);  				goto err_out;  			}  		} @@ -821,8 +842,8 @@ chromeos_laptop_prepare_acpi_peripherals(struct chromeos_laptop *cros_laptop,  err_out:  	while (--i >= 0) {  		acpi_dev = &acpi_peripherals[i]; -		if (acpi_dev->properties) -			property_entries_free(acpi_dev->properties); +		if (!IS_ERR_OR_NULL(acpi_dev->swnode.properties)) +			property_entries_free(acpi_dev->swnode.properties);  	}  	kfree(acpi_peripherals); @@ -833,21 +854,20 @@ static void chromeos_laptop_destroy(const struct chromeos_laptop *cros_laptop)  {  	const struct acpi_peripheral *acpi_dev;  	struct i2c_peripheral *i2c_dev; -	struct i2c_board_info *info;  	int i;  	for (i = 0; i < cros_laptop->num_i2c_peripherals; i++) {  		i2c_dev = &cros_laptop->i2c_peripherals[i]; -		info = &i2c_dev->board_info; -  		i2c_unregister_device(i2c_dev->client); -		property_entries_free(info->properties);  	}  	for (i = 0; i < cros_laptop->num_acpi_peripherals; i++) {  		acpi_dev = &cros_laptop->acpi_peripherals[i]; -		property_entries_free(acpi_dev->properties); +		if (acpi_dev->client) +			device_remove_software_node(&acpi_dev->client->dev); + +		property_entries_free(acpi_dev->swnode.properties);  	}  	kfree(cros_laptop->i2c_peripherals); diff --git a/drivers/platform/mellanox/mlxbf-bootctl.c b/drivers/platform/mellanox/mlxbf-bootctl.c index 5d21c6adf1ab..1c7a288b59a5 100644 --- a/drivers/platform/mellanox/mlxbf-bootctl.c +++ b/drivers/platform/mellanox/mlxbf-bootctl.c @@ -208,7 +208,7 @@ static ssize_t secure_boot_fuse_state_show(struct device *dev,  	 * 0011 = version 1, 0111 = version 2, 1111 = version 3). Upper 4 bits  	 * are a thermometer code indicating key programming has completed for  	 * key n (same encodings as the start bits). This allows for detection -	 * of an interruption in the progamming process which has left the key +	 * of an interruption in the programming process which has left the key  	 * partially programmed (and thus invalid). The process is to burn the  	 * eFuse for the new key start bit, burn the key eFuses, then burn the  	 * eFuse for the new key complete bit. diff --git a/drivers/platform/mellanox/mlxreg-hotplug.c b/drivers/platform/mellanox/mlxreg-hotplug.c index b013445147dd..a9db2f32658f 100644 --- a/drivers/platform/mellanox/mlxreg-hotplug.c +++ b/drivers/platform/mellanox/mlxreg-hotplug.c @@ -683,13 +683,13 @@ static int mlxreg_hotplug_probe(struct platform_device *pdev)  	err = devm_request_irq(&pdev->dev, priv->irq,  			       mlxreg_hotplug_irq_handler, IRQF_TRIGGER_FALLING -			       | IRQF_SHARED, "mlxreg-hotplug", priv); +			       | IRQF_SHARED | IRQF_NO_AUTOEN, +			       "mlxreg-hotplug", priv);  	if (err) {  		dev_err(&pdev->dev, "Failed to request irq: %d\n", err);  		return err;  	} -	disable_irq(priv->irq);  	spin_lock_init(&priv->lock);  	INIT_DELAYED_WORK(&priv->dwork_irq, mlxreg_hotplug_work_handler);  	dev_set_drvdata(&pdev->dev, priv); diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig index 0847b2dc97bf..3105f651614f 100644 --- a/drivers/platform/surface/Kconfig +++ b/drivers/platform/surface/Kconfig @@ -77,6 +77,53 @@ config SURFACE_AGGREGATOR_CDEV  	  The provided interface is intended for debugging and development only,  	  and should not be used otherwise. +config SURFACE_AGGREGATOR_REGISTRY +	tristate "Surface System Aggregator Module Device Registry" +	depends on SURFACE_AGGREGATOR +	depends on SURFACE_AGGREGATOR_BUS +	help +	  Device-registry and device-hubs for Surface System Aggregator Module +	  (SSAM) devices. + +	  Provides a module and driver which act as a device-registry for SSAM +	  client devices that cannot be detected automatically, e.g. via ACPI. +	  Such devices are instead provided via this registry and attached via +	  device hubs, also provided in this module. + +	  Devices provided via this registry are: +	  - Platform profile (performance-/cooling-mode) device (5th- and later +	    generations). +	  - Battery/AC devices (7th-generation). +	  - HID input devices (7th-generation). + +	  Select M (recommended) or Y here if you want support for the above +	  mentioned devices on the corresponding Surface models. Without this +	  module, the respective devices will not be instantiated and thus any +	  functionality provided by them will be missing, even when drivers for +	  these devices are present. In other words, this module only provides +	  the respective client devices. Drivers for these devices still need to +	  be selected via the other options. + +config SURFACE_DTX +	tristate "Surface DTX (Detachment System) Driver" +	depends on SURFACE_AGGREGATOR +	depends on INPUT +	help +	  Driver for the Surface Book clipboard detachment system (DTX). + +	  On the Surface Book series devices, the display part containing the +	  CPU (called the clipboard) can be detached from the base (containing a +	  battery, the keyboard, and, optionally, a discrete GPU) by (if +	  necessary) unlocking and opening the latch connecting both parts. + +	  This driver provides a user-space interface that can influence the +	  behavior of this process, which includes the option to abort it in +	  case the base is still in use or speed it up in case it is not. + +	  Note that this module can be built without support for the Surface +	  Aggregator Bus (i.e. CONFIG_SURFACE_AGGREGATOR_BUS=n). In that case, +	  some devices, specifically the Surface Book 3, will not be supported. +  config SURFACE_GPE  	tristate "Surface GPE/Lid Support Driver"  	depends on DMI @@ -105,6 +152,28 @@ config SURFACE_HOTPLUG  	  Select M or Y here, if you want to (fully) support hot-plugging of  	  dGPU devices on the Surface Book 2 and/or 3 during D3cold. +config SURFACE_PLATFORM_PROFILE +	tristate "Surface Platform Profile Driver" +	depends on SURFACE_AGGREGATOR_REGISTRY +	select ACPI_PLATFORM_PROFILE +	help +	  Provides support for the ACPI platform profile on 5th- and later +	  generation Microsoft Surface devices. + +	  More specifically, this driver provides ACPI platform profile support +	  on Microsoft Surface devices with a Surface System Aggregator Module +	  (SSAM) connected via the Surface Serial Hub (SSH / SAM-over-SSH). In +	  other words, this driver provides platform profile support on the +	  Surface Pro 5, Surface Book 2, Surface Laptop, Surface Laptop Go and +	  later. On those devices, the platform profile can significantly +	  influence cooling behavior, e.g. setting it to 'quiet' (default) or +	  'low-power' can significantly limit performance of the discrete GPU on +	  Surface Books, while in turn leading to lower power consumption and/or +	  less fan noise. + +	  Select M or Y here, if you want to include ACPI platform profile +	  support on the above mentioned devices. +  config SURFACE_PRO3_BUTTON  	tristate "Power/home/volume buttons driver for Microsoft Surface Pro 3/4 tablet"  	depends on INPUT diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile index 990424c5f0c9..32889482de55 100644 --- a/drivers/platform/surface/Makefile +++ b/drivers/platform/surface/Makefile @@ -10,6 +10,9 @@ obj-$(CONFIG_SURFACE_3_POWER_OPREGION)	+= surface3_power.o  obj-$(CONFIG_SURFACE_ACPI_NOTIFY)	+= surface_acpi_notify.o  obj-$(CONFIG_SURFACE_AGGREGATOR)	+= aggregator/  obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV)	+= surface_aggregator_cdev.o +obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o +obj-$(CONFIG_SURFACE_DTX)		+= surface_dtx.o  obj-$(CONFIG_SURFACE_GPE)		+= surface_gpe.o  obj-$(CONFIG_SURFACE_HOTPLUG)		+= surface_hotplug.o +obj-$(CONFIG_SURFACE_PLATFORM_PROFILE)	+= surface_platform_profile.o  obj-$(CONFIG_SURFACE_PRO3_BUTTON)	+= surfacepro3_button.o diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c index 5bcb59ed579d..69e86cd599d3 100644 --- a/drivers/platform/surface/aggregator/controller.c +++ b/drivers/platform/surface/aggregator/controller.c @@ -1040,7 +1040,7 @@ static int ssam_dsm_load_u32(acpi_handle handle, u64 funcs, u64 func, u32 *ret)  	union acpi_object *obj;  	u64 val; -	if (!(funcs & BIT(func))) +	if (!(funcs & BIT_ULL(func)))  		return 0; /* Not supported, leave *ret at its default value */  	obj = acpi_evaluate_dsm_typed(handle, &SSAM_SSH_DSM_GUID, @@ -1750,35 +1750,35 @@ EXPORT_SYMBOL_GPL(ssam_request_sync_with_buffer);  /* -- Internal SAM requests. ------------------------------------------------ */ -static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_get_firmware_version, __le32, { +SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_get_firmware_version, __le32, {  	.target_category = SSAM_SSH_TC_SAM,  	.target_id       = 0x01,  	.command_id      = 0x13,  	.instance_id     = 0x00,  }); -static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_off, u8, { +SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_off, u8, {  	.target_category = SSAM_SSH_TC_SAM,  	.target_id       = 0x01,  	.command_id      = 0x15,  	.instance_id     = 0x00,  }); -static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_on, u8, { +SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_on, u8, {  	.target_category = SSAM_SSH_TC_SAM,  	.target_id       = 0x01,  	.command_id      = 0x16,  	.instance_id     = 0x00,  }); -static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_exit, u8, { +SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_exit, u8, {  	.target_category = SSAM_SSH_TC_SAM,  	.target_id       = 0x01,  	.command_id      = 0x33,  	.instance_id     = 0x00,  }); -static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_entry, u8, { +SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_entry, u8, {  	.target_category = SSAM_SSH_TC_SAM,  	.target_id       = 0x01,  	.command_id      = 0x34, @@ -2483,7 +2483,8 @@ int ssam_irq_setup(struct ssam_controller *ctrl)  	 * interrupt, and let the SAM resume callback during the controller  	 * resume process clear it.  	 */ -	const int irqf = IRQF_SHARED | IRQF_ONESHOT | IRQF_TRIGGER_RISING; +	const int irqf = IRQF_SHARED | IRQF_ONESHOT | +			 IRQF_TRIGGER_RISING | IRQF_NO_AUTOEN;  	gpiod = gpiod_get(dev, "ssam_wakeup-int", GPIOD_ASIS);  	if (IS_ERR(gpiod)) @@ -2501,7 +2502,6 @@ int ssam_irq_setup(struct ssam_controller *ctrl)  		return status;  	ctrl->irq.num = irq; -	disable_irq(ctrl->irq.num);  	return 0;  } diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c new file mode 100644 index 000000000000..685d37a7add1 --- /dev/null +++ b/drivers/platform/surface/surface_aggregator_registry.c @@ -0,0 +1,626 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Surface System Aggregator Module (SSAM) client device registry. + * + * Registry for non-platform/non-ACPI SSAM client devices, i.e. devices that + * cannot be auto-detected. Provides device-hubs and performs instantiation + * for these devices. + * + * Copyright (C) 2020-2021 Maximilian Luz <luzmaximilian@gmail.com> + */ + +#include <linux/acpi.h> +#include <linux/kernel.h> +#include <linux/limits.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/property.h> +#include <linux/types.h> +#include <linux/workqueue.h> + +#include <linux/surface_aggregator/controller.h> +#include <linux/surface_aggregator/device.h> + + +/* -- Device registry. ------------------------------------------------------ */ + +/* + * SSAM device names follow the SSAM module alias, meaning they are prefixed + * with 'ssam:', followed by domain, category, target ID, instance ID, and + * function, each encoded as two-digit hexadecimal, separated by ':'. In other + * words, it follows the scheme + * + *      ssam:dd:cc:tt:ii:ff + * + * Where, 'dd', 'cc', 'tt', 'ii', and 'ff' are the two-digit hexadecimal + * values mentioned above, respectively. + */ + +/* Root node. */ +static const struct software_node ssam_node_root = { +	.name = "ssam_platform_hub", +}; + +/* Base device hub (devices attached to Surface Book 3 base). */ +static const struct software_node ssam_node_hub_base = { +	.name = "ssam:00:00:02:00:00", +	.parent = &ssam_node_root, +}; + +/* AC adapter. */ +static const struct software_node ssam_node_bat_ac = { +	.name = "ssam:01:02:01:01:01", +	.parent = &ssam_node_root, +}; + +/* Primary battery. */ +static const struct software_node ssam_node_bat_main = { +	.name = "ssam:01:02:01:01:00", +	.parent = &ssam_node_root, +}; + +/* Secondary battery (Surface Book 3). */ +static const struct software_node ssam_node_bat_sb3base = { +	.name = "ssam:01:02:02:01:00", +	.parent = &ssam_node_hub_base, +}; + +/* Platform profile / performance-mode device. */ +static const struct software_node ssam_node_tmp_pprof = { +	.name = "ssam:01:03:01:00:01", +	.parent = &ssam_node_root, +}; + +/* DTX / detachment-system device (Surface Book 3). */ +static const struct software_node ssam_node_bas_dtx = { +	.name = "ssam:01:11:01:00:00", +	.parent = &ssam_node_root, +}; + +/* HID keyboard. */ +static const struct software_node ssam_node_hid_main_keyboard = { +	.name = "ssam:01:15:02:01:00", +	.parent = &ssam_node_root, +}; + +/* HID touchpad. */ +static const struct software_node ssam_node_hid_main_touchpad = { +	.name = "ssam:01:15:02:03:00", +	.parent = &ssam_node_root, +}; + +/* HID device instance 5 (unknown HID device). */ +static const struct software_node ssam_node_hid_main_iid5 = { +	.name = "ssam:01:15:02:05:00", +	.parent = &ssam_node_root, +}; + +/* HID keyboard (base hub). */ +static const struct software_node ssam_node_hid_base_keyboard = { +	.name = "ssam:01:15:02:01:00", +	.parent = &ssam_node_hub_base, +}; + +/* HID touchpad (base hub). */ +static const struct software_node ssam_node_hid_base_touchpad = { +	.name = "ssam:01:15:02:03:00", +	.parent = &ssam_node_hub_base, +}; + +/* HID device instance 5 (unknown HID device, base hub). */ +static const struct software_node ssam_node_hid_base_iid5 = { +	.name = "ssam:01:15:02:05:00", +	.parent = &ssam_node_hub_base, +}; + +/* HID device instance 6 (unknown HID device, base hub). */ +static const struct software_node ssam_node_hid_base_iid6 = { +	.name = "ssam:01:15:02:06:00", +	.parent = &ssam_node_hub_base, +}; + +/* Devices for Surface Book 2. */ +static const struct software_node *ssam_node_group_sb2[] = { +	&ssam_node_root, +	&ssam_node_tmp_pprof, +	NULL, +}; + +/* Devices for Surface Book 3. */ +static const struct software_node *ssam_node_group_sb3[] = { +	&ssam_node_root, +	&ssam_node_hub_base, +	&ssam_node_bat_ac, +	&ssam_node_bat_main, +	&ssam_node_bat_sb3base, +	&ssam_node_tmp_pprof, +	&ssam_node_bas_dtx, +	&ssam_node_hid_base_keyboard, +	&ssam_node_hid_base_touchpad, +	&ssam_node_hid_base_iid5, +	&ssam_node_hid_base_iid6, +	NULL, +}; + +/* Devices for Surface Laptop 1. */ +static const struct software_node *ssam_node_group_sl1[] = { +	&ssam_node_root, +	&ssam_node_tmp_pprof, +	NULL, +}; + +/* Devices for Surface Laptop 2. */ +static const struct software_node *ssam_node_group_sl2[] = { +	&ssam_node_root, +	&ssam_node_tmp_pprof, +	NULL, +}; + +/* Devices for Surface Laptop 3. */ +static const struct software_node *ssam_node_group_sl3[] = { +	&ssam_node_root, +	&ssam_node_bat_ac, +	&ssam_node_bat_main, +	&ssam_node_tmp_pprof, +	&ssam_node_hid_main_keyboard, +	&ssam_node_hid_main_touchpad, +	&ssam_node_hid_main_iid5, +	NULL, +}; + +/* Devices for Surface Laptop Go. */ +static const struct software_node *ssam_node_group_slg1[] = { +	&ssam_node_root, +	&ssam_node_bat_ac, +	&ssam_node_bat_main, +	&ssam_node_tmp_pprof, +	NULL, +}; + +/* Devices for Surface Pro 5. */ +static const struct software_node *ssam_node_group_sp5[] = { +	&ssam_node_root, +	&ssam_node_tmp_pprof, +	NULL, +}; + +/* Devices for Surface Pro 6. */ +static const struct software_node *ssam_node_group_sp6[] = { +	&ssam_node_root, +	&ssam_node_tmp_pprof, +	NULL, +}; + +/* Devices for Surface Pro 7 and Surface Pro 7+. */ +static const struct software_node *ssam_node_group_sp7[] = { +	&ssam_node_root, +	&ssam_node_bat_ac, +	&ssam_node_bat_main, +	&ssam_node_tmp_pprof, +	NULL, +}; + + +/* -- Device registry helper functions. ------------------------------------- */ + +static int ssam_uid_from_string(const char *str, struct ssam_device_uid *uid) +{ +	u8 d, tc, tid, iid, fn; +	int n; + +	n = sscanf(str, "ssam:%hhx:%hhx:%hhx:%hhx:%hhx", &d, &tc, &tid, &iid, &fn); +	if (n != 5) +		return -EINVAL; + +	uid->domain = d; +	uid->category = tc; +	uid->target = tid; +	uid->instance = iid; +	uid->function = fn; + +	return 0; +} + +static int ssam_hub_remove_devices_fn(struct device *dev, void *data) +{ +	if (!is_ssam_device(dev)) +		return 0; + +	ssam_device_remove(to_ssam_device(dev)); +	return 0; +} + +static void ssam_hub_remove_devices(struct device *parent) +{ +	device_for_each_child_reverse(parent, NULL, ssam_hub_remove_devices_fn); +} + +static int ssam_hub_add_device(struct device *parent, struct ssam_controller *ctrl, +			       struct fwnode_handle *node) +{ +	struct ssam_device_uid uid; +	struct ssam_device *sdev; +	int status; + +	status = ssam_uid_from_string(fwnode_get_name(node), &uid); +	if (status) +		return status; + +	sdev = ssam_device_alloc(ctrl, uid); +	if (!sdev) +		return -ENOMEM; + +	sdev->dev.parent = parent; +	sdev->dev.fwnode = node; + +	status = ssam_device_add(sdev); +	if (status) +		ssam_device_put(sdev); + +	return status; +} + +static int ssam_hub_add_devices(struct device *parent, struct ssam_controller *ctrl, +				struct fwnode_handle *node) +{ +	struct fwnode_handle *child; +	int status; + +	fwnode_for_each_child_node(node, child) { +		/* +		 * Try to add the device specified in the firmware node. If +		 * this fails with -EINVAL, the node does not specify any SSAM +		 * device, so ignore it and continue with the next one. +		 */ + +		status = ssam_hub_add_device(parent, ctrl, child); +		if (status && status != -EINVAL) +			goto err; +	} + +	return 0; +err: +	ssam_hub_remove_devices(parent); +	return status; +} + + +/* -- SSAM base-hub driver. ------------------------------------------------- */ + +/* + * Some devices (especially battery) may need a bit of time to be fully usable + * after being (re-)connected. This delay has been determined via + * experimentation. + */ +#define SSAM_BASE_UPDATE_CONNECT_DELAY		msecs_to_jiffies(2500) + +enum ssam_base_hub_state { +	SSAM_BASE_HUB_UNINITIALIZED, +	SSAM_BASE_HUB_CONNECTED, +	SSAM_BASE_HUB_DISCONNECTED, +}; + +struct ssam_base_hub { +	struct ssam_device *sdev; + +	enum ssam_base_hub_state state; +	struct delayed_work update_work; + +	struct ssam_event_notifier notif; +}; + +SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, { +	.target_category = SSAM_SSH_TC_BAS, +	.target_id       = 0x01, +	.command_id      = 0x0d, +	.instance_id     = 0x00, +}); + +#define SSAM_BAS_OPMODE_TABLET		0x00 +#define SSAM_EVENT_BAS_CID_CONNECTION	0x0c + +static int ssam_base_hub_query_state(struct ssam_base_hub *hub, enum ssam_base_hub_state *state) +{ +	u8 opmode; +	int status; + +	status = ssam_retry(ssam_bas_query_opmode, hub->sdev->ctrl, &opmode); +	if (status < 0) { +		dev_err(&hub->sdev->dev, "failed to query base state: %d\n", status); +		return status; +	} + +	if (opmode != SSAM_BAS_OPMODE_TABLET) +		*state = SSAM_BASE_HUB_CONNECTED; +	else +		*state = SSAM_BASE_HUB_DISCONNECTED; + +	return 0; +} + +static ssize_t ssam_base_hub_state_show(struct device *dev, struct device_attribute *attr, +					char *buf) +{ +	struct ssam_base_hub *hub = dev_get_drvdata(dev); +	bool connected = hub->state == SSAM_BASE_HUB_CONNECTED; + +	return sysfs_emit(buf, "%d\n", connected); +} + +static struct device_attribute ssam_base_hub_attr_state = +	__ATTR(state, 0444, ssam_base_hub_state_show, NULL); + +static struct attribute *ssam_base_hub_attrs[] = { +	&ssam_base_hub_attr_state.attr, +	NULL, +}; + +static const struct attribute_group ssam_base_hub_group = { +	.attrs = ssam_base_hub_attrs, +}; + +static void ssam_base_hub_update_workfn(struct work_struct *work) +{ +	struct ssam_base_hub *hub = container_of(work, struct ssam_base_hub, update_work.work); +	struct fwnode_handle *node = dev_fwnode(&hub->sdev->dev); +	enum ssam_base_hub_state state; +	int status = 0; + +	status = ssam_base_hub_query_state(hub, &state); +	if (status) +		return; + +	if (hub->state == state) +		return; +	hub->state = state; + +	if (hub->state == SSAM_BASE_HUB_CONNECTED) +		status = ssam_hub_add_devices(&hub->sdev->dev, hub->sdev->ctrl, node); +	else +		ssam_hub_remove_devices(&hub->sdev->dev); + +	if (status) +		dev_err(&hub->sdev->dev, "failed to update base-hub devices: %d\n", status); +} + +static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) +{ +	struct ssam_base_hub *hub = container_of(nf, struct ssam_base_hub, notif); +	unsigned long delay; + +	if (event->command_id != SSAM_EVENT_BAS_CID_CONNECTION) +		return 0; + +	if (event->length < 1) { +		dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length); +		return 0; +	} + +	/* +	 * Delay update when the base is being connected to give devices/EC +	 * some time to set up. +	 */ +	delay = event->data[0] ? SSAM_BASE_UPDATE_CONNECT_DELAY : 0; + +	schedule_delayed_work(&hub->update_work, delay); + +	/* +	 * Do not return SSAM_NOTIF_HANDLED: The event should be picked up and +	 * consumed by the detachment system driver. We're just a (more or less) +	 * silent observer. +	 */ +	return 0; +} + +static int __maybe_unused ssam_base_hub_resume(struct device *dev) +{ +	struct ssam_base_hub *hub = dev_get_drvdata(dev); + +	schedule_delayed_work(&hub->update_work, 0); +	return 0; +} +static SIMPLE_DEV_PM_OPS(ssam_base_hub_pm_ops, NULL, ssam_base_hub_resume); + +static int ssam_base_hub_probe(struct ssam_device *sdev) +{ +	struct ssam_base_hub *hub; +	int status; + +	hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL); +	if (!hub) +		return -ENOMEM; + +	hub->sdev = sdev; +	hub->state = SSAM_BASE_HUB_UNINITIALIZED; + +	hub->notif.base.priority = INT_MAX;  /* This notifier should run first. */ +	hub->notif.base.fn = ssam_base_hub_notif; +	hub->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; +	hub->notif.event.id.target_category = SSAM_SSH_TC_BAS, +	hub->notif.event.id.instance = 0, +	hub->notif.event.mask = SSAM_EVENT_MASK_NONE; +	hub->notif.event.flags = SSAM_EVENT_SEQUENCED; + +	INIT_DELAYED_WORK(&hub->update_work, ssam_base_hub_update_workfn); + +	ssam_device_set_drvdata(sdev, hub); + +	status = ssam_notifier_register(sdev->ctrl, &hub->notif); +	if (status) +		return status; + +	status = sysfs_create_group(&sdev->dev.kobj, &ssam_base_hub_group); +	if (status) +		goto err; + +	schedule_delayed_work(&hub->update_work, 0); +	return 0; + +err: +	ssam_notifier_unregister(sdev->ctrl, &hub->notif); +	cancel_delayed_work_sync(&hub->update_work); +	ssam_hub_remove_devices(&sdev->dev); +	return status; +} + +static void ssam_base_hub_remove(struct ssam_device *sdev) +{ +	struct ssam_base_hub *hub = ssam_device_get_drvdata(sdev); + +	sysfs_remove_group(&sdev->dev.kobj, &ssam_base_hub_group); + +	ssam_notifier_unregister(sdev->ctrl, &hub->notif); +	cancel_delayed_work_sync(&hub->update_work); +	ssam_hub_remove_devices(&sdev->dev); +} + +static const struct ssam_device_id ssam_base_hub_match[] = { +	{ SSAM_VDEV(HUB, 0x02, SSAM_ANY_IID, 0x00) }, +	{ }, +}; + +static struct ssam_device_driver ssam_base_hub_driver = { +	.probe = ssam_base_hub_probe, +	.remove = ssam_base_hub_remove, +	.match_table = ssam_base_hub_match, +	.driver = { +		.name = "surface_aggregator_base_hub", +		.probe_type = PROBE_PREFER_ASYNCHRONOUS, +		.pm = &ssam_base_hub_pm_ops, +	}, +}; + + +/* -- SSAM platform/meta-hub driver. ---------------------------------------- */ + +static const struct acpi_device_id ssam_platform_hub_match[] = { +	/* Surface Pro 4, 5, and 6 (OMBR < 0x10) */ +	{ "MSHW0081", (unsigned long)ssam_node_group_sp5 }, + +	/* Surface Pro 6 (OMBR >= 0x10) */ +	{ "MSHW0111", (unsigned long)ssam_node_group_sp6 }, + +	/* Surface Pro 7 */ +	{ "MSHW0116", (unsigned long)ssam_node_group_sp7 }, + +	/* Surface Pro 7+ */ +	{ "MSHW0119", (unsigned long)ssam_node_group_sp7 }, + +	/* Surface Book 2 */ +	{ "MSHW0107", (unsigned long)ssam_node_group_sb2 }, + +	/* Surface Book 3 */ +	{ "MSHW0117", (unsigned long)ssam_node_group_sb3 }, + +	/* Surface Laptop 1 */ +	{ "MSHW0086", (unsigned long)ssam_node_group_sl1 }, + +	/* Surface Laptop 2 */ +	{ "MSHW0112", (unsigned long)ssam_node_group_sl2 }, + +	/* Surface Laptop 3 (13", Intel) */ +	{ "MSHW0114", (unsigned long)ssam_node_group_sl3 }, + +	/* Surface Laptop 3 (15", AMD) */ +	{ "MSHW0110", (unsigned long)ssam_node_group_sl3 }, + +	/* Surface Laptop Go 1 */ +	{ "MSHW0118", (unsigned long)ssam_node_group_slg1 }, + +	{ }, +}; +MODULE_DEVICE_TABLE(acpi, ssam_platform_hub_match); + +static int ssam_platform_hub_probe(struct platform_device *pdev) +{ +	const struct software_node **nodes; +	struct ssam_controller *ctrl; +	struct fwnode_handle *root; +	int status; + +	nodes = (const struct software_node **)acpi_device_get_match_data(&pdev->dev); +	if (!nodes) +		return -ENODEV; + +	/* +	 * As we're adding the SSAM client devices as children under this device +	 * and not the SSAM controller, we need to add a device link to the +	 * controller to ensure that we remove all of our devices before the +	 * controller is removed. This also guarantees proper ordering for +	 * suspend/resume of the devices on this hub. +	 */ +	ctrl = ssam_client_bind(&pdev->dev); +	if (IS_ERR(ctrl)) +		return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl); + +	status = software_node_register_node_group(nodes); +	if (status) +		return status; + +	root = software_node_fwnode(&ssam_node_root); +	if (!root) { +		software_node_unregister_node_group(nodes); +		return -ENOENT; +	} + +	set_secondary_fwnode(&pdev->dev, root); + +	status = ssam_hub_add_devices(&pdev->dev, ctrl, root); +	if (status) { +		set_secondary_fwnode(&pdev->dev, NULL); +		software_node_unregister_node_group(nodes); +	} + +	platform_set_drvdata(pdev, nodes); +	return status; +} + +static int ssam_platform_hub_remove(struct platform_device *pdev) +{ +	const struct software_node **nodes = platform_get_drvdata(pdev); + +	ssam_hub_remove_devices(&pdev->dev); +	set_secondary_fwnode(&pdev->dev, NULL); +	software_node_unregister_node_group(nodes); +	return 0; +} + +static struct platform_driver ssam_platform_hub_driver = { +	.probe = ssam_platform_hub_probe, +	.remove = ssam_platform_hub_remove, +	.driver = { +		.name = "surface_aggregator_platform_hub", +		.acpi_match_table = ssam_platform_hub_match, +		.probe_type = PROBE_PREFER_ASYNCHRONOUS, +	}, +}; + + +/* -- Module initialization. ------------------------------------------------ */ + +static int __init ssam_device_hub_init(void) +{ +	int status; + +	status = platform_driver_register(&ssam_platform_hub_driver); +	if (status) +		return status; + +	status = ssam_device_driver_register(&ssam_base_hub_driver); +	if (status) +		platform_driver_unregister(&ssam_platform_hub_driver); + +	return status; +} +module_init(ssam_device_hub_init); + +static void __exit ssam_device_hub_exit(void) +{ +	ssam_device_driver_unregister(&ssam_base_hub_driver); +	platform_driver_unregister(&ssam_platform_hub_driver); +} +module_exit(ssam_device_hub_exit); + +MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>"); +MODULE_DESCRIPTION("Device-registry for Surface System Aggregator Module"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/surface/surface_dtx.c b/drivers/platform/surface/surface_dtx.c new file mode 100644 index 000000000000..63ce587e79e3 --- /dev/null +++ b/drivers/platform/surface/surface_dtx.c @@ -0,0 +1,1289 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Surface Book (gen. 2 and later) detachment system (DTX) driver. + * + * Provides a user-space interface to properly handle clipboard/tablet + * (containing screen and processor) detachment from the base of the device + * (containing the keyboard and optionally a discrete GPU). Allows to + * acknowledge (to speed things up), abort (e.g. in case the dGPU is still in + * use), or request detachment via user-space. + * + * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com> + */ + +#include <linux/fs.h> +#include <linux/input.h> +#include <linux/ioctl.h> +#include <linux/kernel.h> +#include <linux/kfifo.h> +#include <linux/kref.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/poll.h> +#include <linux/rwsem.h> +#include <linux/slab.h> +#include <linux/workqueue.h> + +#include <linux/surface_aggregator/controller.h> +#include <linux/surface_aggregator/device.h> +#include <linux/surface_aggregator/dtx.h> + + +/* -- SSAM interface. ------------------------------------------------------- */ + +enum sam_event_cid_bas { +	SAM_EVENT_CID_DTX_CONNECTION			= 0x0c, +	SAM_EVENT_CID_DTX_REQUEST			= 0x0e, +	SAM_EVENT_CID_DTX_CANCEL			= 0x0f, +	SAM_EVENT_CID_DTX_LATCH_STATUS			= 0x11, +}; + +enum ssam_bas_base_state { +	SSAM_BAS_BASE_STATE_DETACH_SUCCESS		= 0x00, +	SSAM_BAS_BASE_STATE_ATTACHED			= 0x01, +	SSAM_BAS_BASE_STATE_NOT_FEASIBLE		= 0x02, +}; + +enum ssam_bas_latch_status { +	SSAM_BAS_LATCH_STATUS_CLOSED			= 0x00, +	SSAM_BAS_LATCH_STATUS_OPENED			= 0x01, +	SSAM_BAS_LATCH_STATUS_FAILED_TO_OPEN		= 0x02, +	SSAM_BAS_LATCH_STATUS_FAILED_TO_REMAIN_OPEN	= 0x03, +	SSAM_BAS_LATCH_STATUS_FAILED_TO_CLOSE		= 0x04, +}; + +enum ssam_bas_cancel_reason { +	SSAM_BAS_CANCEL_REASON_NOT_FEASIBLE		= 0x00,  /* Low battery. */ +	SSAM_BAS_CANCEL_REASON_TIMEOUT			= 0x02, +	SSAM_BAS_CANCEL_REASON_FAILED_TO_OPEN		= 0x03, +	SSAM_BAS_CANCEL_REASON_FAILED_TO_REMAIN_OPEN	= 0x04, +	SSAM_BAS_CANCEL_REASON_FAILED_TO_CLOSE		= 0x05, +}; + +struct ssam_bas_base_info { +	u8 state; +	u8 base_id; +} __packed; + +static_assert(sizeof(struct ssam_bas_base_info) == 2); + +SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_lock, { +	.target_category = SSAM_SSH_TC_BAS, +	.target_id       = 0x01, +	.command_id      = 0x06, +	.instance_id     = 0x00, +}); + +SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_unlock, { +	.target_category = SSAM_SSH_TC_BAS, +	.target_id       = 0x01, +	.command_id      = 0x07, +	.instance_id     = 0x00, +}); + +SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_request, { +	.target_category = SSAM_SSH_TC_BAS, +	.target_id       = 0x01, +	.command_id      = 0x08, +	.instance_id     = 0x00, +}); + +SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_confirm, { +	.target_category = SSAM_SSH_TC_BAS, +	.target_id       = 0x01, +	.command_id      = 0x09, +	.instance_id     = 0x00, +}); + +SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_heartbeat, { +	.target_category = SSAM_SSH_TC_BAS, +	.target_id       = 0x01, +	.command_id      = 0x0a, +	.instance_id     = 0x00, +}); + +SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_cancel, { +	.target_category = SSAM_SSH_TC_BAS, +	.target_id       = 0x01, +	.command_id      = 0x0b, +	.instance_id     = 0x00, +}); + +SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_base, struct ssam_bas_base_info, { +	.target_category = SSAM_SSH_TC_BAS, +	.target_id       = 0x01, +	.command_id      = 0x0c, +	.instance_id     = 0x00, +}); + +SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_device_mode, u8, { +	.target_category = SSAM_SSH_TC_BAS, +	.target_id       = 0x01, +	.command_id      = 0x0d, +	.instance_id     = 0x00, +}); + +SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_latch_status, u8, { +	.target_category = SSAM_SSH_TC_BAS, +	.target_id       = 0x01, +	.command_id      = 0x11, +	.instance_id     = 0x00, +}); + + +/* -- Main structures. ------------------------------------------------------ */ + +enum sdtx_device_state { +	SDTX_DEVICE_SHUTDOWN_BIT    = BIT(0), +	SDTX_DEVICE_DIRTY_BASE_BIT  = BIT(1), +	SDTX_DEVICE_DIRTY_MODE_BIT  = BIT(2), +	SDTX_DEVICE_DIRTY_LATCH_BIT = BIT(3), +}; + +struct sdtx_device { +	struct kref kref; +	struct rw_semaphore lock;         /* Guards device and controller reference. */ + +	struct device *dev; +	struct ssam_controller *ctrl; +	unsigned long flags; + +	struct miscdevice mdev; +	wait_queue_head_t waitq; +	struct mutex write_lock;          /* Guards order of events/notifications. */ +	struct rw_semaphore client_lock;  /* Guards client list.                   */ +	struct list_head client_list; + +	struct delayed_work state_work; +	struct { +		struct ssam_bas_base_info base; +		u8 device_mode; +		u8 latch_status; +	} state; + +	struct delayed_work mode_work; +	struct input_dev *mode_switch; + +	struct ssam_event_notifier notif; +}; + +enum sdtx_client_state { +	SDTX_CLIENT_EVENTS_ENABLED_BIT = BIT(0), +}; + +struct sdtx_client { +	struct sdtx_device *ddev; +	struct list_head node; +	unsigned long flags; + +	struct fasync_struct *fasync; + +	struct mutex read_lock;           /* Guards FIFO buffer read access. */ +	DECLARE_KFIFO(buffer, u8, 512); +}; + +static void __sdtx_device_release(struct kref *kref) +{ +	struct sdtx_device *ddev = container_of(kref, struct sdtx_device, kref); + +	mutex_destroy(&ddev->write_lock); +	kfree(ddev); +} + +static struct sdtx_device *sdtx_device_get(struct sdtx_device *ddev) +{ +	if (ddev) +		kref_get(&ddev->kref); + +	return ddev; +} + +static void sdtx_device_put(struct sdtx_device *ddev) +{ +	if (ddev) +		kref_put(&ddev->kref, __sdtx_device_release); +} + + +/* -- Firmware value translations. ------------------------------------------ */ + +static u16 sdtx_translate_base_state(struct sdtx_device *ddev, u8 state) +{ +	switch (state) { +	case SSAM_BAS_BASE_STATE_ATTACHED: +		return SDTX_BASE_ATTACHED; + +	case SSAM_BAS_BASE_STATE_DETACH_SUCCESS: +		return SDTX_BASE_DETACHED; + +	case SSAM_BAS_BASE_STATE_NOT_FEASIBLE: +		return SDTX_DETACH_NOT_FEASIBLE; + +	default: +		dev_err(ddev->dev, "unknown base state: %#04x\n", state); +		return SDTX_UNKNOWN(state); +	} +} + +static u16 sdtx_translate_latch_status(struct sdtx_device *ddev, u8 status) +{ +	switch (status) { +	case SSAM_BAS_LATCH_STATUS_CLOSED: +		return SDTX_LATCH_CLOSED; + +	case SSAM_BAS_LATCH_STATUS_OPENED: +		return SDTX_LATCH_OPENED; + +	case SSAM_BAS_LATCH_STATUS_FAILED_TO_OPEN: +		return SDTX_ERR_FAILED_TO_OPEN; + +	case SSAM_BAS_LATCH_STATUS_FAILED_TO_REMAIN_OPEN: +		return SDTX_ERR_FAILED_TO_REMAIN_OPEN; + +	case SSAM_BAS_LATCH_STATUS_FAILED_TO_CLOSE: +		return SDTX_ERR_FAILED_TO_CLOSE; + +	default: +		dev_err(ddev->dev, "unknown latch status: %#04x\n", status); +		return SDTX_UNKNOWN(status); +	} +} + +static u16 sdtx_translate_cancel_reason(struct sdtx_device *ddev, u8 reason) +{ +	switch (reason) { +	case SSAM_BAS_CANCEL_REASON_NOT_FEASIBLE: +		return SDTX_DETACH_NOT_FEASIBLE; + +	case SSAM_BAS_CANCEL_REASON_TIMEOUT: +		return SDTX_DETACH_TIMEDOUT; + +	case SSAM_BAS_CANCEL_REASON_FAILED_TO_OPEN: +		return SDTX_ERR_FAILED_TO_OPEN; + +	case SSAM_BAS_CANCEL_REASON_FAILED_TO_REMAIN_OPEN: +		return SDTX_ERR_FAILED_TO_REMAIN_OPEN; + +	case SSAM_BAS_CANCEL_REASON_FAILED_TO_CLOSE: +		return SDTX_ERR_FAILED_TO_CLOSE; + +	default: +		dev_err(ddev->dev, "unknown cancel reason: %#04x\n", reason); +		return SDTX_UNKNOWN(reason); +	} +} + + +/* -- IOCTLs. --------------------------------------------------------------- */ + +static int sdtx_ioctl_get_base_info(struct sdtx_device *ddev, +				    struct sdtx_base_info __user *buf) +{ +	struct ssam_bas_base_info raw; +	struct sdtx_base_info info; +	int status; + +	lockdep_assert_held_read(&ddev->lock); + +	status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &raw); +	if (status < 0) +		return status; + +	info.state = sdtx_translate_base_state(ddev, raw.state); +	info.base_id = SDTX_BASE_TYPE_SSH(raw.base_id); + +	if (copy_to_user(buf, &info, sizeof(info))) +		return -EFAULT; + +	return 0; +} + +static int sdtx_ioctl_get_device_mode(struct sdtx_device *ddev, u16 __user *buf) +{ +	u8 mode; +	int status; + +	lockdep_assert_held_read(&ddev->lock); + +	status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &mode); +	if (status < 0) +		return status; + +	return put_user(mode, buf); +} + +static int sdtx_ioctl_get_latch_status(struct sdtx_device *ddev, u16 __user *buf) +{ +	u8 latch; +	int status; + +	lockdep_assert_held_read(&ddev->lock); + +	status = ssam_retry(ssam_bas_get_latch_status, ddev->ctrl, &latch); +	if (status < 0) +		return status; + +	return put_user(sdtx_translate_latch_status(ddev, latch), buf); +} + +static long __surface_dtx_ioctl(struct sdtx_client *client, unsigned int cmd, unsigned long arg) +{ +	struct sdtx_device *ddev = client->ddev; + +	lockdep_assert_held_read(&ddev->lock); + +	switch (cmd) { +	case SDTX_IOCTL_EVENTS_ENABLE: +		set_bit(SDTX_CLIENT_EVENTS_ENABLED_BIT, &client->flags); +		return 0; + +	case SDTX_IOCTL_EVENTS_DISABLE: +		clear_bit(SDTX_CLIENT_EVENTS_ENABLED_BIT, &client->flags); +		return 0; + +	case SDTX_IOCTL_LATCH_LOCK: +		return ssam_retry(ssam_bas_latch_lock, ddev->ctrl); + +	case SDTX_IOCTL_LATCH_UNLOCK: +		return ssam_retry(ssam_bas_latch_unlock, ddev->ctrl); + +	case SDTX_IOCTL_LATCH_REQUEST: +		return ssam_retry(ssam_bas_latch_request, ddev->ctrl); + +	case SDTX_IOCTL_LATCH_CONFIRM: +		return ssam_retry(ssam_bas_latch_confirm, ddev->ctrl); + +	case SDTX_IOCTL_LATCH_HEARTBEAT: +		return ssam_retry(ssam_bas_latch_heartbeat, ddev->ctrl); + +	case SDTX_IOCTL_LATCH_CANCEL: +		return ssam_retry(ssam_bas_latch_cancel, ddev->ctrl); + +	case SDTX_IOCTL_GET_BASE_INFO: +		return sdtx_ioctl_get_base_info(ddev, (struct sdtx_base_info __user *)arg); + +	case SDTX_IOCTL_GET_DEVICE_MODE: +		return sdtx_ioctl_get_device_mode(ddev, (u16 __user *)arg); + +	case SDTX_IOCTL_GET_LATCH_STATUS: +		return sdtx_ioctl_get_latch_status(ddev, (u16 __user *)arg); + +	default: +		return -EINVAL; +	} +} + +static long surface_dtx_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ +	struct sdtx_client *client = file->private_data; +	long status; + +	if (down_read_killable(&client->ddev->lock)) +		return -ERESTARTSYS; + +	if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &client->ddev->flags)) { +		up_read(&client->ddev->lock); +		return -ENODEV; +	} + +	status = __surface_dtx_ioctl(client, cmd, arg); + +	up_read(&client->ddev->lock); +	return status; +} + + +/* -- File operations. ------------------------------------------------------ */ + +static int surface_dtx_open(struct inode *inode, struct file *file) +{ +	struct sdtx_device *ddev = container_of(file->private_data, struct sdtx_device, mdev); +	struct sdtx_client *client; + +	/* Initialize client. */ +	client = kzalloc(sizeof(*client), GFP_KERNEL); +	if (!client) +		return -ENOMEM; + +	client->ddev = sdtx_device_get(ddev); + +	INIT_LIST_HEAD(&client->node); + +	mutex_init(&client->read_lock); +	INIT_KFIFO(client->buffer); + +	file->private_data = client; + +	/* Attach client. */ +	down_write(&ddev->client_lock); + +	/* +	 * Do not add a new client if the device has been shut down. Note that +	 * it's enough to hold the client_lock here as, during shutdown, we +	 * only acquire that lock and remove clients after marking the device +	 * as shut down. +	 */ +	if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags)) { +		up_write(&ddev->client_lock); +		sdtx_device_put(client->ddev); +		kfree(client); +		return -ENODEV; +	} + +	list_add_tail(&client->node, &ddev->client_list); +	up_write(&ddev->client_lock); + +	stream_open(inode, file); +	return 0; +} + +static int surface_dtx_release(struct inode *inode, struct file *file) +{ +	struct sdtx_client *client = file->private_data; + +	/* Detach client. */ +	down_write(&client->ddev->client_lock); +	list_del(&client->node); +	up_write(&client->ddev->client_lock); + +	/* Free client. */ +	sdtx_device_put(client->ddev); +	mutex_destroy(&client->read_lock); +	kfree(client); + +	return 0; +} + +static ssize_t surface_dtx_read(struct file *file, char __user *buf, size_t count, loff_t *offs) +{ +	struct sdtx_client *client = file->private_data; +	struct sdtx_device *ddev = client->ddev; +	unsigned int copied; +	int status = 0; + +	if (down_read_killable(&ddev->lock)) +		return -ERESTARTSYS; + +	/* Make sure we're not shut down. */ +	if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags)) { +		up_read(&ddev->lock); +		return -ENODEV; +	} + +	do { +		/* Check availability, wait if necessary. */ +		if (kfifo_is_empty(&client->buffer)) { +			up_read(&ddev->lock); + +			if (file->f_flags & O_NONBLOCK) +				return -EAGAIN; + +			status = wait_event_interruptible(ddev->waitq, +							  !kfifo_is_empty(&client->buffer) || +							  test_bit(SDTX_DEVICE_SHUTDOWN_BIT, +								   &ddev->flags)); +			if (status < 0) +				return status; + +			if (down_read_killable(&ddev->lock)) +				return -ERESTARTSYS; + +			/* Need to check that we're not shut down again. */ +			if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags)) { +				up_read(&ddev->lock); +				return -ENODEV; +			} +		} + +		/* Try to read from FIFO. */ +		if (mutex_lock_interruptible(&client->read_lock)) { +			up_read(&ddev->lock); +			return -ERESTARTSYS; +		} + +		status = kfifo_to_user(&client->buffer, buf, count, &copied); +		mutex_unlock(&client->read_lock); + +		if (status < 0) { +			up_read(&ddev->lock); +			return status; +		} + +		/* We might not have gotten anything, check this here. */ +		if (copied == 0 && (file->f_flags & O_NONBLOCK)) { +			up_read(&ddev->lock); +			return -EAGAIN; +		} +	} while (copied == 0); + +	up_read(&ddev->lock); +	return copied; +} + +static __poll_t surface_dtx_poll(struct file *file, struct poll_table_struct *pt) +{ +	struct sdtx_client *client = file->private_data; +	__poll_t events = 0; + +	if (down_read_killable(&client->ddev->lock)) +		return -ERESTARTSYS; + +	if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &client->ddev->flags)) { +		up_read(&client->ddev->lock); +		return EPOLLHUP | EPOLLERR; +	} + +	poll_wait(file, &client->ddev->waitq, pt); + +	if (!kfifo_is_empty(&client->buffer)) +		events |= EPOLLIN | EPOLLRDNORM; + +	up_read(&client->ddev->lock); +	return events; +} + +static int surface_dtx_fasync(int fd, struct file *file, int on) +{ +	struct sdtx_client *client = file->private_data; + +	return fasync_helper(fd, file, on, &client->fasync); +} + +static const struct file_operations surface_dtx_fops = { +	.owner          = THIS_MODULE, +	.open           = surface_dtx_open, +	.release        = surface_dtx_release, +	.read           = surface_dtx_read, +	.poll           = surface_dtx_poll, +	.fasync         = surface_dtx_fasync, +	.unlocked_ioctl = surface_dtx_ioctl, +	.compat_ioctl   = surface_dtx_ioctl, +	.llseek         = no_llseek, +}; + + +/* -- Event handling/forwarding. -------------------------------------------- */ + +/* + * The device operation mode is not immediately updated on the EC when the + * base has been connected, i.e. querying the device mode inside the + * connection event callback yields an outdated value. Thus, we can only + * determine the new tablet-mode switch and device mode values after some + * time. + * + * These delays have been chosen by experimenting. We first delay on connect + * events, then check and validate the device mode against the base state and + * if invalid delay again by the "recheck" delay. + */ +#define SDTX_DEVICE_MODE_DELAY_CONNECT	msecs_to_jiffies(100) +#define SDTX_DEVICE_MODE_DELAY_RECHECK	msecs_to_jiffies(100) + +struct sdtx_status_event { +	struct sdtx_event e; +	__u16 v; +} __packed; + +struct sdtx_base_info_event { +	struct sdtx_event e; +	struct sdtx_base_info v; +} __packed; + +union sdtx_generic_event { +	struct sdtx_event common; +	struct sdtx_status_event status; +	struct sdtx_base_info_event base; +}; + +static void sdtx_update_device_mode(struct sdtx_device *ddev, unsigned long delay); + +/* Must be executed with ddev->write_lock held. */ +static void sdtx_push_event(struct sdtx_device *ddev, struct sdtx_event *evt) +{ +	const size_t len = sizeof(struct sdtx_event) + evt->length; +	struct sdtx_client *client; + +	lockdep_assert_held(&ddev->write_lock); + +	down_read(&ddev->client_lock); +	list_for_each_entry(client, &ddev->client_list, node) { +		if (!test_bit(SDTX_CLIENT_EVENTS_ENABLED_BIT, &client->flags)) +			continue; + +		if (likely(kfifo_avail(&client->buffer) >= len)) +			kfifo_in(&client->buffer, (const u8 *)evt, len); +		else +			dev_warn(ddev->dev, "event buffer overrun\n"); + +		kill_fasync(&client->fasync, SIGIO, POLL_IN); +	} +	up_read(&ddev->client_lock); + +	wake_up_interruptible(&ddev->waitq); +} + +static u32 sdtx_notifier(struct ssam_event_notifier *nf, const struct ssam_event *in) +{ +	struct sdtx_device *ddev = container_of(nf, struct sdtx_device, notif); +	union sdtx_generic_event event; +	size_t len; + +	/* Validate event payload length. */ +	switch (in->command_id) { +	case SAM_EVENT_CID_DTX_CONNECTION: +		len = 2 * sizeof(u8); +		break; + +	case SAM_EVENT_CID_DTX_REQUEST: +		len = 0; +		break; + +	case SAM_EVENT_CID_DTX_CANCEL: +		len = sizeof(u8); +		break; + +	case SAM_EVENT_CID_DTX_LATCH_STATUS: +		len = sizeof(u8); +		break; + +	default: +		return 0; +	} + +	if (in->length != len) { +		dev_err(ddev->dev, +			"unexpected payload size for event %#04x: got %u, expected %zu\n", +			in->command_id, in->length, len); +		return 0; +	} + +	mutex_lock(&ddev->write_lock); + +	/* Translate event. */ +	switch (in->command_id) { +	case SAM_EVENT_CID_DTX_CONNECTION: +		clear_bit(SDTX_DEVICE_DIRTY_BASE_BIT, &ddev->flags); + +		/* If state has not changed: do not send new event. */ +		if (ddev->state.base.state == in->data[0] && +		    ddev->state.base.base_id == in->data[1]) +			goto out; + +		ddev->state.base.state = in->data[0]; +		ddev->state.base.base_id = in->data[1]; + +		event.base.e.length = sizeof(struct sdtx_base_info); +		event.base.e.code = SDTX_EVENT_BASE_CONNECTION; +		event.base.v.state = sdtx_translate_base_state(ddev, in->data[0]); +		event.base.v.base_id = SDTX_BASE_TYPE_SSH(in->data[1]); +		break; + +	case SAM_EVENT_CID_DTX_REQUEST: +		event.common.code = SDTX_EVENT_REQUEST; +		event.common.length = 0; +		break; + +	case SAM_EVENT_CID_DTX_CANCEL: +		event.status.e.length = sizeof(u16); +		event.status.e.code = SDTX_EVENT_CANCEL; +		event.status.v = sdtx_translate_cancel_reason(ddev, in->data[0]); +		break; + +	case SAM_EVENT_CID_DTX_LATCH_STATUS: +		clear_bit(SDTX_DEVICE_DIRTY_LATCH_BIT, &ddev->flags); + +		/* If state has not changed: do not send new event. */ +		if (ddev->state.latch_status == in->data[0]) +			goto out; + +		ddev->state.latch_status = in->data[0]; + +		event.status.e.length = sizeof(u16); +		event.status.e.code = SDTX_EVENT_LATCH_STATUS; +		event.status.v = sdtx_translate_latch_status(ddev, in->data[0]); +		break; +	} + +	sdtx_push_event(ddev, &event.common); + +	/* Update device mode on base connection change. */ +	if (in->command_id == SAM_EVENT_CID_DTX_CONNECTION) { +		unsigned long delay; + +		delay = in->data[0] ? SDTX_DEVICE_MODE_DELAY_CONNECT : 0; +		sdtx_update_device_mode(ddev, delay); +	} + +out: +	mutex_unlock(&ddev->write_lock); +	return SSAM_NOTIF_HANDLED; +} + + +/* -- State update functions. ----------------------------------------------- */ + +static bool sdtx_device_mode_invalid(u8 mode, u8 base_state) +{ +	return ((base_state == SSAM_BAS_BASE_STATE_ATTACHED) && +		(mode == SDTX_DEVICE_MODE_TABLET)) || +	       ((base_state == SSAM_BAS_BASE_STATE_DETACH_SUCCESS) && +		(mode != SDTX_DEVICE_MODE_TABLET)); +} + +static void sdtx_device_mode_workfn(struct work_struct *work) +{ +	struct sdtx_device *ddev = container_of(work, struct sdtx_device, mode_work.work); +	struct sdtx_status_event event; +	struct ssam_bas_base_info base; +	int status, tablet; +	u8 mode; + +	/* Get operation mode. */ +	status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &mode); +	if (status) { +		dev_err(ddev->dev, "failed to get device mode: %d\n", status); +		return; +	} + +	/* Get base info. */ +	status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &base); +	if (status) { +		dev_err(ddev->dev, "failed to get base info: %d\n", status); +		return; +	} + +	/* +	 * In some cases (specifically when attaching the base), the device +	 * mode isn't updated right away. Thus we check if the device mode +	 * makes sense for the given base state and try again later if it +	 * doesn't. +	 */ +	if (sdtx_device_mode_invalid(mode, base.state)) { +		dev_dbg(ddev->dev, "device mode is invalid, trying again\n"); +		sdtx_update_device_mode(ddev, SDTX_DEVICE_MODE_DELAY_RECHECK); +		return; +	} + +	mutex_lock(&ddev->write_lock); +	clear_bit(SDTX_DEVICE_DIRTY_MODE_BIT, &ddev->flags); + +	/* Avoid sending duplicate device-mode events. */ +	if (ddev->state.device_mode == mode) { +		mutex_unlock(&ddev->write_lock); +		return; +	} + +	ddev->state.device_mode = mode; + +	event.e.length = sizeof(u16); +	event.e.code = SDTX_EVENT_DEVICE_MODE; +	event.v = mode; + +	sdtx_push_event(ddev, &event.e); + +	/* Send SW_TABLET_MODE event. */ +	tablet = mode != SDTX_DEVICE_MODE_LAPTOP; +	input_report_switch(ddev->mode_switch, SW_TABLET_MODE, tablet); +	input_sync(ddev->mode_switch); + +	mutex_unlock(&ddev->write_lock); +} + +static void sdtx_update_device_mode(struct sdtx_device *ddev, unsigned long delay) +{ +	schedule_delayed_work(&ddev->mode_work, delay); +} + +/* Must be executed with ddev->write_lock held. */ +static void __sdtx_device_state_update_base(struct sdtx_device *ddev, +					    struct ssam_bas_base_info info) +{ +	struct sdtx_base_info_event event; + +	lockdep_assert_held(&ddev->write_lock); + +	/* Prevent duplicate events. */ +	if (ddev->state.base.state == info.state && +	    ddev->state.base.base_id == info.base_id) +		return; + +	ddev->state.base = info; + +	event.e.length = sizeof(struct sdtx_base_info); +	event.e.code = SDTX_EVENT_BASE_CONNECTION; +	event.v.state = sdtx_translate_base_state(ddev, info.state); +	event.v.base_id = SDTX_BASE_TYPE_SSH(info.base_id); + +	sdtx_push_event(ddev, &event.e); +} + +/* Must be executed with ddev->write_lock held. */ +static void __sdtx_device_state_update_mode(struct sdtx_device *ddev, u8 mode) +{ +	struct sdtx_status_event event; +	int tablet; + +	/* +	 * Note: This function must be called after updating the base state +	 * via __sdtx_device_state_update_base(), as we rely on the updated +	 * base state value in the validity check below. +	 */ + +	lockdep_assert_held(&ddev->write_lock); + +	if (sdtx_device_mode_invalid(mode, ddev->state.base.state)) { +		dev_dbg(ddev->dev, "device mode is invalid, trying again\n"); +		sdtx_update_device_mode(ddev, SDTX_DEVICE_MODE_DELAY_RECHECK); +		return; +	} + +	/* Prevent duplicate events. */ +	if (ddev->state.device_mode == mode) +		return; + +	ddev->state.device_mode = mode; + +	/* Send event. */ +	event.e.length = sizeof(u16); +	event.e.code = SDTX_EVENT_DEVICE_MODE; +	event.v = mode; + +	sdtx_push_event(ddev, &event.e); + +	/* Send SW_TABLET_MODE event. */ +	tablet = mode != SDTX_DEVICE_MODE_LAPTOP; +	input_report_switch(ddev->mode_switch, SW_TABLET_MODE, tablet); +	input_sync(ddev->mode_switch); +} + +/* Must be executed with ddev->write_lock held. */ +static void __sdtx_device_state_update_latch(struct sdtx_device *ddev, u8 status) +{ +	struct sdtx_status_event event; + +	lockdep_assert_held(&ddev->write_lock); + +	/* Prevent duplicate events. */ +	if (ddev->state.latch_status == status) +		return; + +	ddev->state.latch_status = status; + +	event.e.length = sizeof(struct sdtx_base_info); +	event.e.code = SDTX_EVENT_BASE_CONNECTION; +	event.v = sdtx_translate_latch_status(ddev, status); + +	sdtx_push_event(ddev, &event.e); +} + +static void sdtx_device_state_workfn(struct work_struct *work) +{ +	struct sdtx_device *ddev = container_of(work, struct sdtx_device, state_work.work); +	struct ssam_bas_base_info base; +	u8 mode, latch; +	int status; + +	/* Mark everything as dirty. */ +	set_bit(SDTX_DEVICE_DIRTY_BASE_BIT, &ddev->flags); +	set_bit(SDTX_DEVICE_DIRTY_MODE_BIT, &ddev->flags); +	set_bit(SDTX_DEVICE_DIRTY_LATCH_BIT, &ddev->flags); + +	/* +	 * Ensure that the state gets marked as dirty before continuing to +	 * query it. Necessary to ensure that clear_bit() calls in +	 * sdtx_notifier() and sdtx_device_mode_workfn() actually clear these +	 * bits if an event is received while updating the state here. +	 */ +	smp_mb__after_atomic(); + +	status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &base); +	if (status) { +		dev_err(ddev->dev, "failed to get base state: %d\n", status); +		return; +	} + +	status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &mode); +	if (status) { +		dev_err(ddev->dev, "failed to get device mode: %d\n", status); +		return; +	} + +	status = ssam_retry(ssam_bas_get_latch_status, ddev->ctrl, &latch); +	if (status) { +		dev_err(ddev->dev, "failed to get latch status: %d\n", status); +		return; +	} + +	mutex_lock(&ddev->write_lock); + +	/* +	 * If the respective dirty-bit has been cleared, an event has been +	 * received, updating this state. The queried state may thus be out of +	 * date. At this point, we can safely assume that the state provided +	 * by the event is either up to date, or we're about to receive +	 * another event updating it. +	 */ + +	if (test_and_clear_bit(SDTX_DEVICE_DIRTY_BASE_BIT, &ddev->flags)) +		__sdtx_device_state_update_base(ddev, base); + +	if (test_and_clear_bit(SDTX_DEVICE_DIRTY_MODE_BIT, &ddev->flags)) +		__sdtx_device_state_update_mode(ddev, mode); + +	if (test_and_clear_bit(SDTX_DEVICE_DIRTY_LATCH_BIT, &ddev->flags)) +		__sdtx_device_state_update_latch(ddev, latch); + +	mutex_unlock(&ddev->write_lock); +} + +static void sdtx_update_device_state(struct sdtx_device *ddev, unsigned long delay) +{ +	schedule_delayed_work(&ddev->state_work, delay); +} + + +/* -- Common device initialization. ----------------------------------------- */ + +static int sdtx_device_init(struct sdtx_device *ddev, struct device *dev, +			    struct ssam_controller *ctrl) +{ +	int status, tablet_mode; + +	/* Basic initialization. */ +	kref_init(&ddev->kref); +	init_rwsem(&ddev->lock); +	ddev->dev = dev; +	ddev->ctrl = ctrl; + +	ddev->mdev.minor = MISC_DYNAMIC_MINOR; +	ddev->mdev.name = "surface_dtx"; +	ddev->mdev.nodename = "surface/dtx"; +	ddev->mdev.fops = &surface_dtx_fops; + +	ddev->notif.base.priority = 1; +	ddev->notif.base.fn = sdtx_notifier; +	ddev->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; +	ddev->notif.event.id.target_category = SSAM_SSH_TC_BAS; +	ddev->notif.event.id.instance = 0; +	ddev->notif.event.mask = SSAM_EVENT_MASK_NONE; +	ddev->notif.event.flags = SSAM_EVENT_SEQUENCED; + +	init_waitqueue_head(&ddev->waitq); +	mutex_init(&ddev->write_lock); +	init_rwsem(&ddev->client_lock); +	INIT_LIST_HEAD(&ddev->client_list); + +	INIT_DELAYED_WORK(&ddev->mode_work, sdtx_device_mode_workfn); +	INIT_DELAYED_WORK(&ddev->state_work, sdtx_device_state_workfn); + +	/* +	 * Get current device state. We want to guarantee that events are only +	 * sent when state actually changes. Thus we cannot use special +	 * "uninitialized" values, as that would cause problems when manually +	 * querying the state in surface_dtx_pm_complete(). I.e. we would not +	 * be able to detect state changes there if no change event has been +	 * received between driver initialization and first device suspension. +	 * +	 * Note that we also need to do this before registering the event +	 * notifier, as that may access the state values. +	 */ +	status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &ddev->state.base); +	if (status) +		return status; + +	status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &ddev->state.device_mode); +	if (status) +		return status; + +	status = ssam_retry(ssam_bas_get_latch_status, ddev->ctrl, &ddev->state.latch_status); +	if (status) +		return status; + +	/* Set up tablet mode switch. */ +	ddev->mode_switch = input_allocate_device(); +	if (!ddev->mode_switch) +		return -ENOMEM; + +	ddev->mode_switch->name = "Microsoft Surface DTX Device Mode Switch"; +	ddev->mode_switch->phys = "ssam/01:11:01:00:00/input0"; +	ddev->mode_switch->id.bustype = BUS_HOST; +	ddev->mode_switch->dev.parent = ddev->dev; + +	tablet_mode = (ddev->state.device_mode != SDTX_DEVICE_MODE_LAPTOP); +	input_set_capability(ddev->mode_switch, EV_SW, SW_TABLET_MODE); +	input_report_switch(ddev->mode_switch, SW_TABLET_MODE, tablet_mode); + +	status = input_register_device(ddev->mode_switch); +	if (status) { +		input_free_device(ddev->mode_switch); +		return status; +	} + +	/* Set up event notifier. */ +	status = ssam_notifier_register(ddev->ctrl, &ddev->notif); +	if (status) +		goto err_notif; + +	/* Register miscdevice. */ +	status = misc_register(&ddev->mdev); +	if (status) +		goto err_mdev; + +	/* +	 * Update device state in case it has changed between getting the +	 * initial mode and registering the event notifier. +	 */ +	sdtx_update_device_state(ddev, 0); +	return 0; + +err_notif: +	ssam_notifier_unregister(ddev->ctrl, &ddev->notif); +	cancel_delayed_work_sync(&ddev->mode_work); +err_mdev: +	input_unregister_device(ddev->mode_switch); +	return status; +} + +static struct sdtx_device *sdtx_device_create(struct device *dev, struct ssam_controller *ctrl) +{ +	struct sdtx_device *ddev; +	int status; + +	ddev = kzalloc(sizeof(*ddev), GFP_KERNEL); +	if (!ddev) +		return ERR_PTR(-ENOMEM); + +	status = sdtx_device_init(ddev, dev, ctrl); +	if (status) { +		sdtx_device_put(ddev); +		return ERR_PTR(status); +	} + +	return ddev; +} + +static void sdtx_device_destroy(struct sdtx_device *ddev) +{ +	struct sdtx_client *client; + +	/* +	 * Mark device as shut-down. Prevent new clients from being added and +	 * new operations from being executed. +	 */ +	set_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags); + +	/* Disable notifiers, prevent new events from arriving. */ +	ssam_notifier_unregister(ddev->ctrl, &ddev->notif); + +	/* Stop mode_work, prevent access to mode_switch. */ +	cancel_delayed_work_sync(&ddev->mode_work); + +	/* Stop state_work. */ +	cancel_delayed_work_sync(&ddev->state_work); + +	/* With mode_work canceled, we can unregister the mode_switch. */ +	input_unregister_device(ddev->mode_switch); + +	/* Wake up async clients. */ +	down_write(&ddev->client_lock); +	list_for_each_entry(client, &ddev->client_list, node) { +		kill_fasync(&client->fasync, SIGIO, POLL_HUP); +	} +	up_write(&ddev->client_lock); + +	/* Wake up blocking clients. */ +	wake_up_interruptible(&ddev->waitq); + +	/* +	 * Wait for clients to finish their current operation. After this, the +	 * controller and device references are guaranteed to be no longer in +	 * use. +	 */ +	down_write(&ddev->lock); +	ddev->dev = NULL; +	ddev->ctrl = NULL; +	up_write(&ddev->lock); + +	/* Finally remove the misc-device. */ +	misc_deregister(&ddev->mdev); + +	/* +	 * We're now guaranteed that sdtx_device_open() won't be called any +	 * more, so we can now drop out reference. +	 */ +	sdtx_device_put(ddev); +} + + +/* -- PM ops. --------------------------------------------------------------- */ + +#ifdef CONFIG_PM_SLEEP + +static void surface_dtx_pm_complete(struct device *dev) +{ +	struct sdtx_device *ddev = dev_get_drvdata(dev); + +	/* +	 * Normally, the EC will store events while suspended (i.e. in +	 * display-off state) and release them when resumed (i.e. transitioned +	 * to display-on state). During hibernation, however, the EC will be +	 * shut down and does not store events. Furthermore, events might be +	 * dropped during prolonged suspension (it is currently unknown how +	 * big this event buffer is and how it behaves on overruns). +	 * +	 * To prevent any problems, we update the device state here. We do +	 * this delayed to ensure that any events sent by the EC directly +	 * after resuming will be handled first. The delay below has been +	 * chosen (experimentally), so that there should be ample time for +	 * these events to be handled, before we check and, if necessary, +	 * update the state. +	 */ +	sdtx_update_device_state(ddev, msecs_to_jiffies(1000)); +} + +static const struct dev_pm_ops surface_dtx_pm_ops = { +	.complete = surface_dtx_pm_complete, +}; + +#else /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops surface_dtx_pm_ops = {}; + +#endif /* CONFIG_PM_SLEEP */ + + +/* -- Platform driver. ------------------------------------------------------ */ + +static int surface_dtx_platform_probe(struct platform_device *pdev) +{ +	struct ssam_controller *ctrl; +	struct sdtx_device *ddev; + +	/* Link to EC. */ +	ctrl = ssam_client_bind(&pdev->dev); +	if (IS_ERR(ctrl)) +		return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl); + +	ddev = sdtx_device_create(&pdev->dev, ctrl); +	if (IS_ERR(ddev)) +		return PTR_ERR(ddev); + +	platform_set_drvdata(pdev, ddev); +	return 0; +} + +static int surface_dtx_platform_remove(struct platform_device *pdev) +{ +	sdtx_device_destroy(platform_get_drvdata(pdev)); +	return 0; +} + +static const struct acpi_device_id surface_dtx_acpi_match[] = { +	{ "MSHW0133", 0 }, +	{ }, +}; +MODULE_DEVICE_TABLE(acpi, surface_dtx_acpi_match); + +static struct platform_driver surface_dtx_platform_driver = { +	.probe = surface_dtx_platform_probe, +	.remove = surface_dtx_platform_remove, +	.driver = { +		.name = "surface_dtx_pltf", +		.acpi_match_table = surface_dtx_acpi_match, +		.pm = &surface_dtx_pm_ops, +		.probe_type = PROBE_PREFER_ASYNCHRONOUS, +	}, +}; + + +/* -- SSAM device driver. --------------------------------------------------- */ + +#ifdef CONFIG_SURFACE_AGGREGATOR_BUS + +static int surface_dtx_ssam_probe(struct ssam_device *sdev) +{ +	struct sdtx_device *ddev; + +	ddev = sdtx_device_create(&sdev->dev, sdev->ctrl); +	if (IS_ERR(ddev)) +		return PTR_ERR(ddev); + +	ssam_device_set_drvdata(sdev, ddev); +	return 0; +} + +static void surface_dtx_ssam_remove(struct ssam_device *sdev) +{ +	sdtx_device_destroy(ssam_device_get_drvdata(sdev)); +} + +static const struct ssam_device_id surface_dtx_ssam_match[] = { +	{ SSAM_SDEV(BAS, 0x01, 0x00, 0x00) }, +	{ }, +}; +MODULE_DEVICE_TABLE(ssam, surface_dtx_ssam_match); + +static struct ssam_device_driver surface_dtx_ssam_driver = { +	.probe = surface_dtx_ssam_probe, +	.remove = surface_dtx_ssam_remove, +	.match_table = surface_dtx_ssam_match, +	.driver = { +		.name = "surface_dtx", +		.pm = &surface_dtx_pm_ops, +		.probe_type = PROBE_PREFER_ASYNCHRONOUS, +	}, +}; + +static int ssam_dtx_driver_register(void) +{ +	return ssam_device_driver_register(&surface_dtx_ssam_driver); +} + +static void ssam_dtx_driver_unregister(void) +{ +	ssam_device_driver_unregister(&surface_dtx_ssam_driver); +} + +#else /* CONFIG_SURFACE_AGGREGATOR_BUS */ + +static int ssam_dtx_driver_register(void) +{ +	return 0; +} + +static void ssam_dtx_driver_unregister(void) +{ +} + +#endif /* CONFIG_SURFACE_AGGREGATOR_BUS */ + + +/* -- Module setup. --------------------------------------------------------- */ + +static int __init surface_dtx_init(void) +{ +	int status; + +	status = ssam_dtx_driver_register(); +	if (status) +		return status; + +	status = platform_driver_register(&surface_dtx_platform_driver); +	if (status) +		ssam_dtx_driver_unregister(); + +	return status; +} +module_init(surface_dtx_init); + +static void __exit surface_dtx_exit(void) +{ +	platform_driver_unregister(&surface_dtx_platform_driver); +	ssam_dtx_driver_unregister(); +} +module_exit(surface_dtx_exit); + +MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>"); +MODULE_DESCRIPTION("Detachment-system driver for Surface System Aggregator Module"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/surface/surface_platform_profile.c b/drivers/platform/surface/surface_platform_profile.c new file mode 100644 index 000000000000..6373d3b5eb7f --- /dev/null +++ b/drivers/platform/surface/surface_platform_profile.c @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Surface Platform Profile / Performance Mode driver for Surface System + * Aggregator Module (thermal subsystem). + * + * Copyright (C) 2021 Maximilian Luz <luzmaximilian@gmail.com> + */ + +#include <asm/unaligned.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_profile.h> +#include <linux/types.h> + +#include <linux/surface_aggregator/device.h> + +enum ssam_tmp_profile { +	SSAM_TMP_PROFILE_NORMAL             = 1, +	SSAM_TMP_PROFILE_BATTERY_SAVER      = 2, +	SSAM_TMP_PROFILE_BETTER_PERFORMANCE = 3, +	SSAM_TMP_PROFILE_BEST_PERFORMANCE   = 4, +}; + +struct ssam_tmp_profile_info { +	__le32 profile; +	__le16 unknown1; +	__le16 unknown2; +} __packed; + +struct ssam_tmp_profile_device { +	struct ssam_device *sdev; +	struct platform_profile_handler handler; +}; + +SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_tmp_profile_get, struct ssam_tmp_profile_info, { +	.target_category = SSAM_SSH_TC_TMP, +	.command_id      = 0x02, +}); + +SSAM_DEFINE_SYNC_REQUEST_CL_W(__ssam_tmp_profile_set, __le32, { +	.target_category = SSAM_SSH_TC_TMP, +	.command_id      = 0x03, +}); + +static int ssam_tmp_profile_get(struct ssam_device *sdev, enum ssam_tmp_profile *p) +{ +	struct ssam_tmp_profile_info info; +	int status; + +	status = ssam_retry(__ssam_tmp_profile_get, sdev, &info); +	if (status < 0) +		return status; + +	*p = le32_to_cpu(info.profile); +	return 0; +} + +static int ssam_tmp_profile_set(struct ssam_device *sdev, enum ssam_tmp_profile p) +{ +	__le32 profile_le = cpu_to_le32(p); + +	return ssam_retry(__ssam_tmp_profile_set, sdev, &profile_le); +} + +static int convert_ssam_to_profile(struct ssam_device *sdev, enum ssam_tmp_profile p) +{ +	switch (p) { +	case SSAM_TMP_PROFILE_NORMAL: +		return PLATFORM_PROFILE_BALANCED; + +	case SSAM_TMP_PROFILE_BATTERY_SAVER: +		return PLATFORM_PROFILE_LOW_POWER; + +	case SSAM_TMP_PROFILE_BETTER_PERFORMANCE: +		return PLATFORM_PROFILE_BALANCED_PERFORMANCE; + +	case SSAM_TMP_PROFILE_BEST_PERFORMANCE: +		return PLATFORM_PROFILE_PERFORMANCE; + +	default: +		dev_err(&sdev->dev, "invalid performance profile: %d", p); +		return -EINVAL; +	} +} + +static int convert_profile_to_ssam(struct ssam_device *sdev, enum platform_profile_option p) +{ +	switch (p) { +	case PLATFORM_PROFILE_LOW_POWER: +		return SSAM_TMP_PROFILE_BATTERY_SAVER; + +	case PLATFORM_PROFILE_BALANCED: +		return SSAM_TMP_PROFILE_NORMAL; + +	case PLATFORM_PROFILE_BALANCED_PERFORMANCE: +		return SSAM_TMP_PROFILE_BETTER_PERFORMANCE; + +	case PLATFORM_PROFILE_PERFORMANCE: +		return SSAM_TMP_PROFILE_BEST_PERFORMANCE; + +	default: +		/* This should have already been caught by platform_profile_store(). */ +		WARN(true, "unsupported platform profile"); +		return -EOPNOTSUPP; +	} +} + +static int ssam_platform_profile_get(struct platform_profile_handler *pprof, +				     enum platform_profile_option *profile) +{ +	struct ssam_tmp_profile_device *tpd; +	enum ssam_tmp_profile tp; +	int status; + +	tpd = container_of(pprof, struct ssam_tmp_profile_device, handler); + +	status = ssam_tmp_profile_get(tpd->sdev, &tp); +	if (status) +		return status; + +	status = convert_ssam_to_profile(tpd->sdev, tp); +	if (status < 0) +		return status; + +	*profile = status; +	return 0; +} + +static int ssam_platform_profile_set(struct platform_profile_handler *pprof, +				     enum platform_profile_option profile) +{ +	struct ssam_tmp_profile_device *tpd; +	int tp; + +	tpd = container_of(pprof, struct ssam_tmp_profile_device, handler); + +	tp = convert_profile_to_ssam(tpd->sdev, profile); +	if (tp < 0) +		return tp; + +	return ssam_tmp_profile_set(tpd->sdev, tp); +} + +static int surface_platform_profile_probe(struct ssam_device *sdev) +{ +	struct ssam_tmp_profile_device *tpd; + +	tpd = devm_kzalloc(&sdev->dev, sizeof(*tpd), GFP_KERNEL); +	if (!tpd) +		return -ENOMEM; + +	tpd->sdev = sdev; + +	tpd->handler.profile_get = ssam_platform_profile_get; +	tpd->handler.profile_set = ssam_platform_profile_set; + +	set_bit(PLATFORM_PROFILE_LOW_POWER, tpd->handler.choices); +	set_bit(PLATFORM_PROFILE_BALANCED, tpd->handler.choices); +	set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, tpd->handler.choices); +	set_bit(PLATFORM_PROFILE_PERFORMANCE, tpd->handler.choices); + +	platform_profile_register(&tpd->handler); +	return 0; +} + +static void surface_platform_profile_remove(struct ssam_device *sdev) +{ +	platform_profile_remove(); +} + +static const struct ssam_device_id ssam_platform_profile_match[] = { +	{ SSAM_SDEV(TMP, 0x01, 0x00, 0x01) }, +	{ }, +}; +MODULE_DEVICE_TABLE(ssam, ssam_platform_profile_match); + +static struct ssam_device_driver surface_platform_profile = { +	.probe = surface_platform_profile_probe, +	.remove = surface_platform_profile_remove, +	.match_table = ssam_platform_profile_match, +	.driver = { +		.name = "surface_platform_profile", +		.probe_type = PROBE_PREFER_ASYNCHRONOUS, +	}, +}; +module_ssam_device_driver(surface_platform_profile); + +MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>"); +MODULE_DESCRIPTION("Platform Profile Support for Surface System Aggregator Module"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/surface/surfacepro3_button.c b/drivers/platform/surface/surfacepro3_button.c index d8afed5db94c..242fb690dcaf 100644 --- a/drivers/platform/surface/surfacepro3_button.c +++ b/drivers/platform/surface/surfacepro3_button.c @@ -40,8 +40,6 @@ static const guid_t MSHW0040_DSM_UUID =  #define SURFACE_BUTTON_NOTIFY_PRESS_VOLUME_DOWN		0xc2  #define SURFACE_BUTTON_NOTIFY_RELEASE_VOLUME_DOWN	0xc3 -ACPI_MODULE_NAME("surface pro 3 button"); -  MODULE_AUTHOR("Chen Yu");  MODULE_DESCRIPTION("Surface Pro3 Button Driver");  MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index ad4e630e73e2..2714f7c3843e 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -123,6 +123,17 @@ config XIAOMI_WMI  	  To compile this driver as a module, choose M here: the module will  	  be called xiaomi-wmi. +config GIGABYTE_WMI +	tristate "Gigabyte WMI temperature driver" +	depends on ACPI_WMI +	depends on HWMON +	help +	  Say Y here if you want to support WMI-based temperature reporting on +	  Gigabyte mainboards. + +	  To compile this driver as a module, choose M here: the module will +	  be called gigabyte-wmi. +  config ACERHDF  	tristate "Acer Aspire One temperature and fan driver"  	depends on ACPI && THERMAL @@ -193,6 +204,17 @@ config AMD_PMC  	  If you choose to compile this driver as a module the module will be  	  called amd-pmc. +config ADV_SWBUTTON +	tristate "Advantech ACPI Software Button Driver" +	depends on ACPI && INPUT +	help +	  Say Y here to enable support for Advantech software defined +	  button feature. More information can be found at +	  <http://www.advantech.com.tw/products/> + +	  To compile this driver as a module, choose M here. The module will +	  be called adv_swbutton. +  config APPLE_GMUX  	tristate "Apple Gmux Driver"  	depends on ACPI && PCI @@ -410,6 +432,7 @@ config HP_WMI  	depends on INPUT  	depends on RFKILL || RFKILL = n  	select INPUT_SPARSEKMAP +	select ACPI_PLATFORM_PROFILE  	help  	 Say Y here if you want to support WMI-based hotkeys on HP laptops and  	 to read data from WMI such as docking or ambient light sensor state. @@ -1171,23 +1194,29 @@ config INTEL_MRFLD_PWRBTN  config INTEL_PMC_CORE  	tristate "Intel PMC Core driver"  	depends on PCI +	depends on ACPI  	help  	  The Intel Platform Controller Hub for Intel Core SoCs provides access -	  to Power Management Controller registers via a PCI interface. This +	  to Power Management Controller registers via various interfaces. This  	  driver can utilize debugging capabilities and supported features as -	  exposed by the Power Management Controller. +	  exposed by the Power Management Controller. It also may perform some +	  tasks in the PMC in order to enable transition into the SLPS0 state. +	  It should be selected on all Intel platforms supported by the driver.  	  Supported features:  		- SLP_S0_RESIDENCY counter  		- PCH IP Power Gating status -		- LTR Ignore +		- LTR Ignore / LTR Show  		- MPHY/PLL gating status (Sunrisepoint PCH only) +		- SLPS0 Debug registers (Cannonlake/Icelake PCH) +		- Low Power Mode registers (Tigerlake and beyond) +		- PMC quirks as needed to enable SLPS0/S0ix  config INTEL_PMT_CLASS  	tristate  	help  	  The Intel Platform Monitoring Technology (PMT) class driver provides -	  the basic sysfs interface and file hierarchy uses by PMT devices. +	  the basic sysfs interface and file hierarchy used by PMT devices.  	  For more information, see:  	  <file:Documentation/ABI/testing/sysfs-class-intel_pmt> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 60d554073749..dcc8cdb95b4d 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_INTEL_WMI_THUNDERBOLT)	+= intel-wmi-thunderbolt.o  obj-$(CONFIG_MXM_WMI)			+= mxm-wmi.o  obj-$(CONFIG_PEAQ_WMI)			+= peaq-wmi.o  obj-$(CONFIG_XIAOMI_WMI)		+= xiaomi-wmi.o +obj-$(CONFIG_GIGABYTE_WMI)		+= gigabyte-wmi.o  # Acer  obj-$(CONFIG_ACERHDF)		+= acerhdf.o @@ -24,6 +25,9 @@ obj-$(CONFIG_ACER_WMI)		+= acer-wmi.o  # AMD  obj-$(CONFIG_AMD_PMC)		+= amd-pmc.o +# Advantech +obj-$(CONFIG_ADV_SWBUTTON)	+= adv_swbutton.o +  # Apple  obj-$(CONFIG_APPLE_GMUX)	+= apple-gmux.o diff --git a/drivers/platform/x86/adv_swbutton.c b/drivers/platform/x86/adv_swbutton.c new file mode 100644 index 000000000000..38693b735c87 --- /dev/null +++ b/drivers/platform/x86/adv_swbutton.c @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + *  adv_swbutton.c - Software Button Interface Driver. + * + *  (C) Copyright 2020 Advantech Corporation, Inc + * + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/input.h> +#include <linux/acpi.h> +#include <linux/platform_device.h> + +#define ACPI_BUTTON_HID_SWBTN               "AHC0310" + +#define ACPI_BUTTON_NOTIFY_SWBTN_RELEASE    0x86 +#define ACPI_BUTTON_NOTIFY_SWBTN_PRESSED    0x85 + +struct adv_swbutton { +	struct input_dev *input; +	char phys[32]; +}; + +/*------------------------------------------------------------------------- + *                               Driver Interface + *-------------------------------------------------------------------------- + */ +static void adv_swbutton_notify(acpi_handle handle, u32 event, void *context) +{ +	struct platform_device *device = context; +	struct adv_swbutton *button = dev_get_drvdata(&device->dev); + +	switch (event) { +	case ACPI_BUTTON_NOTIFY_SWBTN_RELEASE: +		input_report_key(button->input, KEY_PROG1, 0); +		input_sync(button->input); +		break; +	case ACPI_BUTTON_NOTIFY_SWBTN_PRESSED: +		input_report_key(button->input, KEY_PROG1, 1); +		input_sync(button->input); +		break; +	default: +		dev_dbg(&device->dev, "Unsupported event [0x%x]\n", event); +	} +} + +static int adv_swbutton_probe(struct platform_device *device) +{ +	struct adv_swbutton *button; +	struct input_dev *input; +	acpi_handle handle = ACPI_HANDLE(&device->dev); +	acpi_status status; +	int error; + +	button = devm_kzalloc(&device->dev, sizeof(*button), GFP_KERNEL); +	if (!button) +		return -ENOMEM; + +	dev_set_drvdata(&device->dev, button); + +	input = devm_input_allocate_device(&device->dev); +	if (!input) +		return -ENOMEM; + +	button->input = input; +	snprintf(button->phys, sizeof(button->phys), "%s/button/input0", ACPI_BUTTON_HID_SWBTN); + +	input->name = "Advantech Software Button"; +	input->phys = button->phys; +	input->id.bustype = BUS_HOST; +	input->dev.parent = &device->dev; +	set_bit(EV_REP, input->evbit); +	input_set_capability(input, EV_KEY, KEY_PROG1); + +	error = input_register_device(input); +	if (error) +		return error; + +	device_init_wakeup(&device->dev, true); + +	status = acpi_install_notify_handler(handle, +					     ACPI_DEVICE_NOTIFY, +					     adv_swbutton_notify, +					     device); +	if (ACPI_FAILURE(status)) { +		dev_err(&device->dev, "Error installing notify handler\n"); +		return -EIO; +	} + +	return 0; +} + +static int adv_swbutton_remove(struct platform_device *device) +{ +	acpi_handle handle = ACPI_HANDLE(&device->dev); + +	acpi_remove_notify_handler(handle, ACPI_DEVICE_NOTIFY, +				   adv_swbutton_notify); + +	return 0; +} + +static const struct acpi_device_id button_device_ids[] = { +	{ACPI_BUTTON_HID_SWBTN, 0}, +	{"", 0}, +}; +MODULE_DEVICE_TABLE(acpi, button_device_ids); + +static struct platform_driver adv_swbutton_driver = { +	.driver = { +		.name = "adv_swbutton", +		.acpi_match_table = button_device_ids, +	}, +	.probe = adv_swbutton_probe, +	.remove = adv_swbutton_remove, +}; +module_platform_driver(adv_swbutton_driver); + +MODULE_AUTHOR("Andrea Ho"); +MODULE_DESCRIPTION("Advantech ACPI SW Button Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/asus-laptop.c b/drivers/platform/x86/asus-laptop.c index bfea656e910c..4d2d32bfbe2a 100644 --- a/drivers/platform/x86/asus-laptop.c +++ b/drivers/platform/x86/asus-laptop.c @@ -1569,7 +1569,7 @@ static umode_t asus_sysfs_is_visible(struct kobject *kobj,  				    struct attribute *attr,  				    int idx)  { -	struct device *dev = container_of(kobj, struct device, kobj); +	struct device *dev = kobj_to_dev(kobj);  	struct asus_laptop *asus = dev_get_drvdata(dev);  	acpi_handle handle = asus->handle;  	bool supported; diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index 9ca15f724343..ebaeb7bb80f5 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -47,6 +47,9 @@ MODULE_AUTHOR("Corentin Chary <corentin.chary@gmail.com>, "  MODULE_DESCRIPTION("Asus Generic WMI Driver");  MODULE_LICENSE("GPL"); +static bool fnlock_default = true; +module_param(fnlock_default, bool, 0444); +  #define to_asus_wmi_driver(pdrv)					\  	(container_of((pdrv), struct asus_wmi_driver, platform_driver)) @@ -2673,7 +2676,7 @@ static int asus_wmi_add(struct platform_device *pdev)  		err = asus_wmi_set_devstate(ASUS_WMI_DEVID_BACKLIGHT, 2, NULL);  	if (asus_wmi_has_fnlock_key(asus)) { -		asus->fnlock_locked = true; +		asus->fnlock_locked = fnlock_default;  		asus_wmi_fnlock_update(asus);  	} diff --git a/drivers/platform/x86/classmate-laptop.c b/drivers/platform/x86/classmate-laptop.c index 3e03e8d3a07f..9309ab5792cb 100644 --- a/drivers/platform/x86/classmate-laptop.c +++ b/drivers/platform/x86/classmate-laptop.c @@ -956,7 +956,7 @@ static int cmpc_ipml_add(struct acpi_device *acpi)  	/*  	 * If RFKILL is disabled, rfkill_alloc will return ERR_PTR(-ENODEV).  	 * This is OK, however, since all other uses of the device will not -	 * derefence it. +	 * dereference it.  	 */  	if (ipml->rf) {  		retval = rfkill_register(ipml->rf); diff --git a/drivers/platform/x86/dell/alienware-wmi.c b/drivers/platform/x86/dell/alienware-wmi.c index 5bb2859c8285..f21248255529 100644 --- a/drivers/platform/x86/dell/alienware-wmi.c +++ b/drivers/platform/x86/dell/alienware-wmi.c @@ -2,7 +2,7 @@  /*   * Alienware AlienFX control   * - * Copyright (C) 2014 Dell Inc <mario_limonciello@dell.com> + * Copyright (C) 2014 Dell Inc <Dell.Client.Kernel@dell.com>   */  #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -26,7 +26,7 @@  #define WMAX_METHOD_DEEP_SLEEP_CONTROL	0x0B  #define WMAX_METHOD_DEEP_SLEEP_STATUS	0x0C -MODULE_AUTHOR("Mario Limonciello <mario_limonciello@dell.com>"); +MODULE_AUTHOR("Mario Limonciello <mario.limonciello@outlook.com>");  MODULE_DESCRIPTION("Alienware special feature control");  MODULE_LICENSE("GPL");  MODULE_ALIAS("wmi:" LEGACY_CONTROL_GUID); diff --git a/drivers/platform/x86/dell/dell-smbios-base.c b/drivers/platform/x86/dell/dell-smbios-base.c index 3a1dbf199441..fc086b66f70b 100644 --- a/drivers/platform/x86/dell/dell-smbios-base.c +++ b/drivers/platform/x86/dell/dell-smbios-base.c @@ -647,6 +647,6 @@ module_exit(dell_smbios_exit);  MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>");  MODULE_AUTHOR("Gabriele Mazzotta <gabriele.mzt@gmail.com>");  MODULE_AUTHOR("Pali Rohár <pali@kernel.org>"); -MODULE_AUTHOR("Mario Limonciello <mario.limonciello@dell.com>"); +MODULE_AUTHOR("Mario Limonciello <mario.limonciello@outlook.com>");  MODULE_DESCRIPTION("Common functions for kernel modules using Dell SMBIOS");  MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/dell/dell-smbios-wmi.c b/drivers/platform/x86/dell/dell-smbios-wmi.c index 27a298b7c541..a1753485159c 100644 --- a/drivers/platform/x86/dell/dell-smbios-wmi.c +++ b/drivers/platform/x86/dell/dell-smbios-wmi.c @@ -205,7 +205,7 @@ fail_register:  	return ret;  } -static int dell_smbios_wmi_remove(struct wmi_device *wdev) +static void dell_smbios_wmi_remove(struct wmi_device *wdev)  {  	struct wmi_smbios_priv *priv = dev_get_drvdata(&wdev->dev);  	int count; @@ -218,7 +218,6 @@ static int dell_smbios_wmi_remove(struct wmi_device *wdev)  	count = get_order(priv->req_buf_size);  	free_pages((unsigned long)priv->buf, count);  	mutex_unlock(&call_mutex); -	return 0;  }  static const struct wmi_device_id dell_smbios_wmi_id_table[] = { diff --git a/drivers/platform/x86/dell/dell-wmi-descriptor.c b/drivers/platform/x86/dell/dell-wmi-descriptor.c index a068900ae8a1..c2a180202719 100644 --- a/drivers/platform/x86/dell/dell-wmi-descriptor.c +++ b/drivers/platform/x86/dell/dell-wmi-descriptor.c @@ -174,14 +174,13 @@ out:  	return ret;  } -static int dell_wmi_descriptor_remove(struct wmi_device *wdev) +static void dell_wmi_descriptor_remove(struct wmi_device *wdev)  {  	struct descriptor_priv *priv = dev_get_drvdata(&wdev->dev);  	mutex_lock(&list_mutex);  	list_del(&priv->list);  	mutex_unlock(&list_mutex); -	return 0;  }  static const struct wmi_device_id dell_wmi_descriptor_id_table[] = { @@ -201,6 +200,6 @@ static struct wmi_driver dell_wmi_descriptor_driver = {  module_wmi_driver(dell_wmi_descriptor_driver);  MODULE_DEVICE_TABLE(wmi, dell_wmi_descriptor_id_table); -MODULE_AUTHOR("Mario Limonciello <mario.limonciello@dell.com>"); +MODULE_AUTHOR("Mario Limonciello <mario.limonciello@outlook.com>");  MODULE_DESCRIPTION("Dell WMI descriptor driver");  MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.c b/drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.c index f95d8ddace5a..c2dd2de6bc20 100644 --- a/drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.c +++ b/drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.c @@ -152,12 +152,11 @@ static int bios_attr_set_interface_probe(struct wmi_device *wdev, const void *co  	return 0;  } -static int bios_attr_set_interface_remove(struct wmi_device *wdev) +static void bios_attr_set_interface_remove(struct wmi_device *wdev)  {  	mutex_lock(&wmi_priv.mutex);  	wmi_priv.bios_attr_wdev = NULL;  	mutex_unlock(&wmi_priv.mutex); -	return 0;  }  static const struct wmi_device_id bios_attr_set_interface_id_table[] = { diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/enum-attributes.c b/drivers/platform/x86/dell/dell-wmi-sysman/enum-attributes.c index 80f4b7785c6c..091e48c217ed 100644 --- a/drivers/platform/x86/dell/dell-wmi-sysman/enum-attributes.c +++ b/drivers/platform/x86/dell/dell-wmi-sysman/enum-attributes.c @@ -185,5 +185,8 @@ void exit_enum_attributes(void)  			sysfs_remove_group(wmi_priv.enumeration_data[instance_id].attr_name_kobj,  								&enumeration_attr_group);  	} +	wmi_priv.enumeration_instances_count = 0; +  	kfree(wmi_priv.enumeration_data); +	wmi_priv.enumeration_data = NULL;  } diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/int-attributes.c b/drivers/platform/x86/dell/dell-wmi-sysman/int-attributes.c index 75aedbb733be..8a49ba6e44f9 100644 --- a/drivers/platform/x86/dell/dell-wmi-sysman/int-attributes.c +++ b/drivers/platform/x86/dell/dell-wmi-sysman/int-attributes.c @@ -175,5 +175,8 @@ void exit_int_attributes(void)  			sysfs_remove_group(wmi_priv.integer_data[instance_id].attr_name_kobj,  								&integer_attr_group);  	} +	wmi_priv.integer_instances_count = 0; +  	kfree(wmi_priv.integer_data); +	wmi_priv.integer_data = NULL;  } diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/passobj-attributes.c b/drivers/platform/x86/dell/dell-wmi-sysman/passobj-attributes.c index 3abcd95477c0..834b3e82ad9f 100644 --- a/drivers/platform/x86/dell/dell-wmi-sysman/passobj-attributes.c +++ b/drivers/platform/x86/dell/dell-wmi-sysman/passobj-attributes.c @@ -183,5 +183,8 @@ void exit_po_attributes(void)  			sysfs_remove_group(wmi_priv.po_data[instance_id].attr_name_kobj,  								&po_attr_group);  	} +	wmi_priv.po_instances_count = 0; +  	kfree(wmi_priv.po_data); +	wmi_priv.po_data = NULL;  } diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/passwordattr-interface.c b/drivers/platform/x86/dell/dell-wmi-sysman/passwordattr-interface.c index 5780b4d94759..339a082d6c18 100644 --- a/drivers/platform/x86/dell/dell-wmi-sysman/passwordattr-interface.c +++ b/drivers/platform/x86/dell/dell-wmi-sysman/passwordattr-interface.c @@ -119,12 +119,11 @@ static int bios_attr_pass_interface_probe(struct wmi_device *wdev, const void *c  	return 0;  } -static int bios_attr_pass_interface_remove(struct wmi_device *wdev) +static void bios_attr_pass_interface_remove(struct wmi_device *wdev)  {  	mutex_lock(&wmi_priv.mutex);  	wmi_priv.password_attr_wdev = NULL;  	mutex_unlock(&wmi_priv.mutex); -	return 0;  }  static const struct wmi_device_id bios_attr_pass_interface_id_table[] = { diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/string-attributes.c b/drivers/platform/x86/dell/dell-wmi-sysman/string-attributes.c index ac75dce88a4c..552537852459 100644 --- a/drivers/platform/x86/dell/dell-wmi-sysman/string-attributes.c +++ b/drivers/platform/x86/dell/dell-wmi-sysman/string-attributes.c @@ -155,5 +155,8 @@ void exit_str_attributes(void)  			sysfs_remove_group(wmi_priv.str_data[instance_id].attr_name_kobj,  								&str_attr_group);  	} +	wmi_priv.str_instances_count = 0; +  	kfree(wmi_priv.str_data); +	wmi_priv.str_data = NULL;  } diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c b/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c index cb81010ba1a2..c8d276d78e92 100644 --- a/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c +++ b/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c @@ -210,25 +210,17 @@ static struct kobj_attribute pending_reboot = __ATTR_RO(pending_reboot);   */  static int create_attributes_level_sysfs_files(void)  { -	int ret = sysfs_create_file(&wmi_priv.main_dir_kset->kobj, &reset_bios.attr); +	int ret; -	if (ret) { -		pr_debug("could not create reset_bios file\n"); +	ret = sysfs_create_file(&wmi_priv.main_dir_kset->kobj, &reset_bios.attr); +	if (ret)  		return ret; -	}  	ret = sysfs_create_file(&wmi_priv.main_dir_kset->kobj, &pending_reboot.attr); -	if (ret) { -		pr_debug("could not create changing_pending_reboot file\n"); -		sysfs_remove_file(&wmi_priv.main_dir_kset->kobj, &reset_bios.attr); -	} -	return ret; -} +	if (ret) +		return ret; -static void release_reset_bios_data(void) -{ -	sysfs_remove_file(&wmi_priv.main_dir_kset->kobj, &reset_bios.attr); -	sysfs_remove_file(&wmi_priv.main_dir_kset->kobj, &pending_reboot.attr); +	return 0;  }  static ssize_t wmi_sysman_attr_show(struct kobject *kobj, struct attribute *attr, @@ -373,8 +365,6 @@ static void destroy_attribute_objs(struct kset *kset)   */  static void release_attributes_data(void)  { -	release_reset_bios_data(); -  	mutex_lock(&wmi_priv.mutex);  	exit_enum_attributes();  	exit_int_attributes(); @@ -386,11 +376,13 @@ static void release_attributes_data(void)  		wmi_priv.authentication_dir_kset = NULL;  	}  	if (wmi_priv.main_dir_kset) { +		sysfs_remove_file(&wmi_priv.main_dir_kset->kobj, &reset_bios.attr); +		sysfs_remove_file(&wmi_priv.main_dir_kset->kobj, &pending_reboot.attr);  		destroy_attribute_objs(wmi_priv.main_dir_kset);  		kset_unregister(wmi_priv.main_dir_kset); +		wmi_priv.main_dir_kset = NULL;  	}  	mutex_unlock(&wmi_priv.mutex); -  }  /** @@ -407,6 +399,7 @@ static int init_bios_attributes(int attr_type, const char *guid)  	union acpi_object *obj = NULL;  	union acpi_object *elements;  	struct kset *tmp_set; +	int min_elements;  	/* instance_id needs to be reset for each type GUID  	 * also, instance IDs are unique within GUID but not across @@ -417,14 +410,38 @@ static int init_bios_attributes(int attr_type, const char *guid)  	retval = alloc_attributes_data(attr_type);  	if (retval)  		return retval; + +	switch (attr_type) { +	case ENUM:	min_elements = 8;	break; +	case INT:	min_elements = 9;	break; +	case STR:	min_elements = 8;	break; +	case PO:	min_elements = 4;	break; +	default: +		pr_err("Error: Unknown attr_type: %d\n", attr_type); +		return -EINVAL; +	} +  	/* need to use specific instance_id and guid combination to get right data */  	obj = get_wmiobj_pointer(instance_id, guid); -	if (!obj || obj->type != ACPI_TYPE_PACKAGE) +	if (!obj)  		return -ENODEV; -	elements = obj->package.elements;  	mutex_lock(&wmi_priv.mutex); -	while (elements) { +	while (obj) { +		if (obj->type != ACPI_TYPE_PACKAGE) { +			pr_err("Error: Expected ACPI-package type, got: %d\n", obj->type); +			retval = -EIO; +			goto err_attr_init; +		} + +		if (obj->package.count < min_elements) { +			pr_err("Error: ACPI-package does not have enough elements: %d < %d\n", +			       obj->package.count, min_elements); +			goto nextobj; +		} + +		elements = obj->package.elements; +  		/* sanity checking */  		if (elements[ATTR_NAME].type != ACPI_TYPE_STRING) {  			pr_debug("incorrect element type\n"); @@ -489,7 +506,6 @@ nextobj:  		kfree(obj);  		instance_id++;  		obj = get_wmiobj_pointer(instance_id, guid); -		elements = obj ? obj->package.elements : NULL;  	}  	mutex_unlock(&wmi_priv.mutex); @@ -497,7 +513,6 @@ nextobj:  err_attr_init:  	mutex_unlock(&wmi_priv.mutex); -	release_attributes_data();  	kfree(obj);  	return retval;  } @@ -513,102 +528,91 @@ static int __init sysman_init(void)  	}  	ret = init_bios_attr_set_interface(); -	if (ret || !wmi_priv.bios_attr_wdev) { -		pr_debug("failed to initialize set interface\n"); -		goto fail_set_interface; -	} +	if (ret) +		return ret;  	ret = init_bios_attr_pass_interface(); -	if (ret || !wmi_priv.password_attr_wdev) { -		pr_debug("failed to initialize pass interface\n"); -		goto fail_pass_interface; +	if (ret) +		goto err_exit_bios_attr_set_interface; + +	if (!wmi_priv.bios_attr_wdev || !wmi_priv.password_attr_wdev) { +		pr_debug("failed to find set or pass interface\n"); +		ret = -ENODEV; +		goto err_exit_bios_attr_pass_interface;  	}  	ret = class_register(&firmware_attributes_class);  	if (ret) -		goto fail_class; +		goto err_exit_bios_attr_pass_interface;  	wmi_priv.class_dev = device_create(&firmware_attributes_class, NULL, MKDEV(0, 0),  				  NULL, "%s", DRIVER_NAME);  	if (IS_ERR(wmi_priv.class_dev)) {  		ret = PTR_ERR(wmi_priv.class_dev); -		goto fail_classdev; +		goto err_unregister_class;  	}  	wmi_priv.main_dir_kset = kset_create_and_add("attributes", NULL,  						     &wmi_priv.class_dev->kobj);  	if (!wmi_priv.main_dir_kset) {  		ret = -ENOMEM; -		goto fail_main_kset; +		goto err_destroy_classdev;  	}  	wmi_priv.authentication_dir_kset = kset_create_and_add("authentication", NULL,  								&wmi_priv.class_dev->kobj);  	if (!wmi_priv.authentication_dir_kset) {  		ret = -ENOMEM; -		goto fail_authentication_kset; +		goto err_release_attributes_data;  	}  	ret = create_attributes_level_sysfs_files();  	if (ret) {  		pr_debug("could not create reset BIOS attribute\n"); -		goto fail_reset_bios; +		goto err_release_attributes_data;  	}  	ret = init_bios_attributes(ENUM, DELL_WMI_BIOS_ENUMERATION_ATTRIBUTE_GUID);  	if (ret) {  		pr_debug("failed to populate enumeration type attributes\n"); -		goto fail_create_group; +		goto err_release_attributes_data;  	}  	ret = init_bios_attributes(INT, DELL_WMI_BIOS_INTEGER_ATTRIBUTE_GUID);  	if (ret) {  		pr_debug("failed to populate integer type attributes\n"); -		goto fail_create_group; +		goto err_release_attributes_data;  	}  	ret = init_bios_attributes(STR, DELL_WMI_BIOS_STRING_ATTRIBUTE_GUID);  	if (ret) {  		pr_debug("failed to populate string type attributes\n"); -		goto fail_create_group; +		goto err_release_attributes_data;  	}  	ret = init_bios_attributes(PO, DELL_WMI_BIOS_PASSOBJ_ATTRIBUTE_GUID);  	if (ret) {  		pr_debug("failed to populate pass object type attributes\n"); -		goto fail_create_group; +		goto err_release_attributes_data;  	}  	return 0; -fail_create_group: +err_release_attributes_data:  	release_attributes_data(); -fail_reset_bios: -	if (wmi_priv.authentication_dir_kset) { -		kset_unregister(wmi_priv.authentication_dir_kset); -		wmi_priv.authentication_dir_kset = NULL; -	} - -fail_authentication_kset: -	if (wmi_priv.main_dir_kset) { -		kset_unregister(wmi_priv.main_dir_kset); -		wmi_priv.main_dir_kset = NULL; -	} - -fail_main_kset: +err_destroy_classdev:  	device_destroy(&firmware_attributes_class, MKDEV(0, 0)); -fail_classdev: +err_unregister_class:  	class_unregister(&firmware_attributes_class); -fail_class: +err_exit_bios_attr_pass_interface:  	exit_bios_attr_pass_interface(); -fail_pass_interface: +err_exit_bios_attr_set_interface:  	exit_bios_attr_set_interface(); -fail_set_interface:  	return ret;  } @@ -624,7 +628,7 @@ static void __exit sysman_exit(void)  module_init(sysman_init);  module_exit(sysman_exit); -MODULE_AUTHOR("Mario Limonciello <mario.limonciello@dell.com>"); +MODULE_AUTHOR("Mario Limonciello <mario.limonciello@outlook.com>");  MODULE_AUTHOR("Prasanth Ksr <prasanth.ksr@dell.com>");  MODULE_AUTHOR("Divya Bharathi <divya.bharathi@dell.com>");  MODULE_DESCRIPTION("Dell platform setting control interface"); diff --git a/drivers/platform/x86/dell/dell-wmi.c b/drivers/platform/x86/dell/dell-wmi.c index bbdb3e860892..5e1b7f897df5 100644 --- a/drivers/platform/x86/dell/dell-wmi.c +++ b/drivers/platform/x86/dell/dell-wmi.c @@ -714,10 +714,9 @@ static int dell_wmi_probe(struct wmi_device *wdev, const void *context)  	return dell_wmi_input_setup(wdev);  } -static int dell_wmi_remove(struct wmi_device *wdev) +static void dell_wmi_remove(struct wmi_device *wdev)  {  	dell_wmi_input_destroy(wdev); -	return 0;  }  static const struct wmi_device_id dell_wmi_id_table[] = {  	{ .guid_string = DELL_EVENT_GUID }, diff --git a/drivers/platform/x86/gigabyte-wmi.c b/drivers/platform/x86/gigabyte-wmi.c new file mode 100644 index 000000000000..13d57434e60f --- /dev/null +++ b/drivers/platform/x86/gigabyte-wmi.c @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + *  Copyright (C) 2021 Thomas Weißschuh <thomas@weissschuh.net> + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/acpi.h> +#include <linux/dmi.h> +#include <linux/hwmon.h> +#include <linux/module.h> +#include <linux/wmi.h> + +#define GIGABYTE_WMI_GUID	"DEADBEEF-2001-0000-00A0-C90629100000" +#define NUM_TEMPERATURE_SENSORS	6 + +static bool force_load; +module_param(force_load, bool, 0444); +MODULE_PARM_DESC(force_load, "Force loading on unknown platform"); + +static u8 usable_sensors_mask; + +enum gigabyte_wmi_commandtype { +	GIGABYTE_WMI_BUILD_DATE_QUERY       =   0x1, +	GIGABYTE_WMI_MAINBOARD_TYPE_QUERY   =   0x2, +	GIGABYTE_WMI_FIRMWARE_VERSION_QUERY =   0x4, +	GIGABYTE_WMI_MAINBOARD_NAME_QUERY   =   0x5, +	GIGABYTE_WMI_TEMPERATURE_QUERY      = 0x125, +}; + +struct gigabyte_wmi_args { +	u32 arg1; +}; + +static int gigabyte_wmi_perform_query(struct wmi_device *wdev, +				      enum gigabyte_wmi_commandtype command, +				      struct gigabyte_wmi_args *args, struct acpi_buffer *out) +{ +	const struct acpi_buffer in = { +		.length = sizeof(*args), +		.pointer = args, +	}; + +	acpi_status ret = wmidev_evaluate_method(wdev, 0x0, command, &in, out); + +	if (ACPI_FAILURE(ret)) +		return -EIO; + +	return 0; +} + +static int gigabyte_wmi_query_integer(struct wmi_device *wdev, +				      enum gigabyte_wmi_commandtype command, +				      struct gigabyte_wmi_args *args, u64 *res) +{ +	union acpi_object *obj; +	struct acpi_buffer result = { ACPI_ALLOCATE_BUFFER, NULL }; +	int ret; + +	ret = gigabyte_wmi_perform_query(wdev, command, args, &result); +	if (ret) +		return ret; +	obj = result.pointer; +	if (obj && obj->type == ACPI_TYPE_INTEGER) +		*res = obj->integer.value; +	else +		ret = -EIO; +	kfree(result.pointer); +	return ret; +} + +static int gigabyte_wmi_temperature(struct wmi_device *wdev, u8 sensor, long *res) +{ +	struct gigabyte_wmi_args args = { +		.arg1 = sensor, +	}; +	u64 temp; +	acpi_status ret; + +	ret = gigabyte_wmi_query_integer(wdev, GIGABYTE_WMI_TEMPERATURE_QUERY, &args, &temp); +	if (ret == 0) { +		if (temp == 0) +			return -ENODEV; +		*res = (s8)temp * 1000; // value is a signed 8-bit integer +	} +	return ret; +} + +static int gigabyte_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, +				   u32 attr, int channel, long *val) +{ +	struct wmi_device *wdev = dev_get_drvdata(dev); + +	return gigabyte_wmi_temperature(wdev, channel, val); +} + +static umode_t gigabyte_wmi_hwmon_is_visible(const void *data, enum hwmon_sensor_types type, +					     u32 attr, int channel) +{ +	return usable_sensors_mask & BIT(channel) ? 0444  : 0; +} + +static const struct hwmon_channel_info *gigabyte_wmi_hwmon_info[] = { +	HWMON_CHANNEL_INFO(temp, +			   HWMON_T_INPUT, +			   HWMON_T_INPUT, +			   HWMON_T_INPUT, +			   HWMON_T_INPUT, +			   HWMON_T_INPUT, +			   HWMON_T_INPUT), +	NULL +}; + +static const struct hwmon_ops gigabyte_wmi_hwmon_ops = { +	.read = gigabyte_wmi_hwmon_read, +	.is_visible = gigabyte_wmi_hwmon_is_visible, +}; + +static const struct hwmon_chip_info gigabyte_wmi_hwmon_chip_info = { +	.ops = &gigabyte_wmi_hwmon_ops, +	.info = gigabyte_wmi_hwmon_info, +}; + +static u8 gigabyte_wmi_detect_sensor_usability(struct wmi_device *wdev) +{ +	int i; +	long temp; +	u8 r = 0; + +	for (i = 0; i < NUM_TEMPERATURE_SENSORS; i++) { +		if (!gigabyte_wmi_temperature(wdev, i, &temp)) +			r |= BIT(i); +	} +	return r; +} + +static const struct dmi_system_id gigabyte_wmi_known_working_platforms[] = { +	{ .matches = { +		DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Gigabyte Technology Co., Ltd."), +		DMI_EXACT_MATCH(DMI_BOARD_NAME, "B550 GAMING X V2"), +	}}, +	{ .matches = { +		DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Gigabyte Technology Co., Ltd."), +		DMI_EXACT_MATCH(DMI_BOARD_NAME, "B550M AORUS PRO-P"), +	}}, +	{ .matches = { +		DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Gigabyte Technology Co., Ltd."), +		DMI_EXACT_MATCH(DMI_BOARD_NAME, "B550M DS3H"), +	}}, +	{ .matches = { +		DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Gigabyte Technology Co., Ltd."), +		DMI_EXACT_MATCH(DMI_BOARD_NAME, "Z390 I AORUS PRO WIFI-CF"), +	}}, +	{ .matches = { +		DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Gigabyte Technology Co., Ltd."), +		DMI_EXACT_MATCH(DMI_BOARD_NAME, "X570 AORUS ELITE"), +	}}, +	{ .matches = { +		DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Gigabyte Technology Co., Ltd."), +		DMI_EXACT_MATCH(DMI_BOARD_NAME, "X570 I AORUS PRO WIFI"), +	}}, +	{ } +}; + +static int gigabyte_wmi_probe(struct wmi_device *wdev, const void *context) +{ +	struct device *hwmon_dev; + +	if (!dmi_check_system(gigabyte_wmi_known_working_platforms)) { +		if (!force_load) +			return -ENODEV; +		dev_warn(&wdev->dev, "Forcing load on unknown platform"); +	} + +	usable_sensors_mask = gigabyte_wmi_detect_sensor_usability(wdev); +	if (!usable_sensors_mask) { +		dev_info(&wdev->dev, "No temperature sensors usable"); +		return -ENODEV; +	} + +	hwmon_dev = devm_hwmon_device_register_with_info(&wdev->dev, "gigabyte_wmi", wdev, +							 &gigabyte_wmi_hwmon_chip_info, NULL); + +	return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct wmi_device_id gigabyte_wmi_id_table[] = { +	{ GIGABYTE_WMI_GUID, NULL }, +	{ } +}; + +static struct wmi_driver gigabyte_wmi_driver = { +	.driver = { +		.name = "gigabyte-wmi", +	}, +	.id_table = gigabyte_wmi_id_table, +	.probe = gigabyte_wmi_probe, +}; +module_wmi_driver(gigabyte_wmi_driver); + +MODULE_DEVICE_TABLE(wmi, gigabyte_wmi_id_table); +MODULE_AUTHOR("Thomas Weißschuh <thomas@weissschuh.net>"); +MODULE_DESCRIPTION("Gigabyte WMI temperature driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/gpd-pocket-fan.c b/drivers/platform/x86/gpd-pocket-fan.c index 5b516e4c2bfb..7a20f68ae206 100644 --- a/drivers/platform/x86/gpd-pocket-fan.c +++ b/drivers/platform/x86/gpd-pocket-fan.c @@ -6,6 +6,7 @@   */  #include <linux/acpi.h> +#include <linux/devm-helpers.h>  #include <linux/gpio/consumer.h>  #include <linux/module.h>  #include <linux/moduleparam.h> @@ -124,7 +125,7 @@ static void gpd_pocket_fan_force_update(struct gpd_pocket_fan_data *fan)  static int gpd_pocket_fan_probe(struct platform_device *pdev)  {  	struct gpd_pocket_fan_data *fan; -	int i; +	int i, ret;  	for (i = 0; i < ARRAY_SIZE(temp_limits); i++) {  		if (temp_limits[i] < 20000 || temp_limits[i] > 90000) { @@ -152,7 +153,10 @@ static int gpd_pocket_fan_probe(struct platform_device *pdev)  		return -ENOMEM;  	fan->dev = &pdev->dev; -	INIT_DELAYED_WORK(&fan->work, gpd_pocket_fan_worker); +	ret = devm_delayed_work_autocancel(&pdev->dev, &fan->work, +					   gpd_pocket_fan_worker); +	if (ret) +		return ret;  	/* Note this returns a "weak" reference which we don't need to free */  	fan->dts0 = thermal_zone_get_zone_by_name("soc_dts0"); @@ -177,14 +181,6 @@ static int gpd_pocket_fan_probe(struct platform_device *pdev)  	return 0;  } -static int gpd_pocket_fan_remove(struct platform_device *pdev) -{ -	struct gpd_pocket_fan_data *fan = platform_get_drvdata(pdev); - -	cancel_delayed_work_sync(&fan->work); -	return 0; -} -  #ifdef CONFIG_PM_SLEEP  static int gpd_pocket_fan_suspend(struct device *dev)  { @@ -215,7 +211,6 @@ MODULE_DEVICE_TABLE(acpi, gpd_pocket_fan_acpi_match);  static struct platform_driver gpd_pocket_fan_driver = {  	.probe	= gpd_pocket_fan_probe, -	.remove	= gpd_pocket_fan_remove,  	.driver	= {  		.name			= "gpd_pocket_fan",  		.acpi_match_table	= gpd_pocket_fan_acpi_match, diff --git a/drivers/platform/x86/hp-wmi.c b/drivers/platform/x86/hp-wmi.c index e94e59283ecb..027a1467d009 100644 --- a/drivers/platform/x86/hp-wmi.c +++ b/drivers/platform/x86/hp-wmi.c @@ -21,6 +21,7 @@  #include <linux/input.h>  #include <linux/input/sparse-keymap.h>  #include <linux/platform_device.h> +#include <linux/platform_profile.h>  #include <linux/acpi.h>  #include <linux/rfkill.h>  #include <linux/string.h> @@ -85,7 +86,7 @@ enum hp_wmi_commandtype {  	HPWMI_FEATURE2_QUERY		= 0x0d,  	HPWMI_WIRELESS2_QUERY		= 0x1b,  	HPWMI_POSTCODEERROR_QUERY	= 0x2a, -	HPWMI_THERMAL_POLICY_QUERY	= 0x4c, +	HPWMI_THERMAL_PROFILE_QUERY	= 0x4c,  };  enum hp_wmi_command { @@ -119,6 +120,12 @@ enum hp_wireless2_bits {  	HPWMI_POWER_FW_OR_HW	= HPWMI_POWER_BIOS | HPWMI_POWER_HARD,  }; +enum hp_thermal_profile { +	HP_THERMAL_PROFILE_PERFORMANCE	= 0x00, +	HP_THERMAL_PROFILE_DEFAULT		= 0x01, +	HP_THERMAL_PROFILE_COOL			= 0x02 +}; +  #define IS_HWBLOCKED(x) ((x & HPWMI_POWER_FW_OR_HW) != HPWMI_POWER_FW_OR_HW)  #define IS_SWBLOCKED(x) !(x & HPWMI_POWER_SOFT) @@ -159,6 +166,8 @@ static const struct key_entry hp_wmi_keymap[] = {  static struct input_dev *hp_wmi_input_dev;  static struct platform_device *hp_wmi_platform_dev; +static struct platform_profile_handler platform_profile_handler; +static bool platform_profile_support;  static struct rfkill *wifi_rfkill;  static struct rfkill *bluetooth_rfkill; @@ -869,23 +878,98 @@ fail:  	return err;  } -static int thermal_policy_setup(struct platform_device *device) +static int thermal_profile_get(void) +{ +	return hp_wmi_read_int(HPWMI_THERMAL_PROFILE_QUERY); +} + +static int thermal_profile_set(int thermal_profile) +{ +	return hp_wmi_perform_query(HPWMI_THERMAL_PROFILE_QUERY, HPWMI_WRITE, &thermal_profile, +							   sizeof(thermal_profile), 0); +} + +static int platform_profile_get(struct platform_profile_handler *pprof, +				enum platform_profile_option *profile) +{ +	int tp; + +	tp = thermal_profile_get(); +	if (tp < 0) +		return tp; + +	switch (tp) { +	case HP_THERMAL_PROFILE_PERFORMANCE: +		*profile =  PLATFORM_PROFILE_PERFORMANCE; +		break; +	case HP_THERMAL_PROFILE_DEFAULT: +		*profile =  PLATFORM_PROFILE_BALANCED; +		break; +	case HP_THERMAL_PROFILE_COOL: +		*profile =  PLATFORM_PROFILE_COOL; +		break; +	default: +		return -EINVAL; +	} + +	return 0; +} + +static int platform_profile_set(struct platform_profile_handler *pprof, +				enum platform_profile_option profile)  {  	int err, tp; -	tp = hp_wmi_read_int(HPWMI_THERMAL_POLICY_QUERY); +	switch (profile) { +	case PLATFORM_PROFILE_PERFORMANCE: +		tp =  HP_THERMAL_PROFILE_PERFORMANCE; +		break; +	case PLATFORM_PROFILE_BALANCED: +		tp =  HP_THERMAL_PROFILE_DEFAULT; +		break; +	case PLATFORM_PROFILE_COOL: +		tp =  HP_THERMAL_PROFILE_COOL; +		break; +	default: +		return -EOPNOTSUPP; +	} + +	err = thermal_profile_set(tp); +	if (err) +		return err; + +	return 0; +} + +static int thermal_profile_setup(void) +{ +	int err, tp; + +	tp = thermal_profile_get();  	if (tp < 0)  		return tp;  	/* -	 * call thermal policy write command to ensure that the firmware correctly +	 * call thermal profile write command to ensure that the firmware correctly  	 * sets the OEM variables for the DPTF  	 */ -	err = hp_wmi_perform_query(HPWMI_THERMAL_POLICY_QUERY, HPWMI_WRITE, &tp, -							   sizeof(tp), 0); +	err = thermal_profile_set(tp);  	if (err)  		return err; +	platform_profile_handler.profile_get = platform_profile_get, +	platform_profile_handler.profile_set = platform_profile_set, + +	set_bit(PLATFORM_PROFILE_COOL, platform_profile_handler.choices); +	set_bit(PLATFORM_PROFILE_BALANCED, platform_profile_handler.choices); +	set_bit(PLATFORM_PROFILE_PERFORMANCE, platform_profile_handler.choices); + +	err = platform_profile_register(&platform_profile_handler); +	if (err) +		return err; + +	platform_profile_support = true; +  	return 0;  } @@ -900,7 +984,7 @@ static int __init hp_wmi_bios_setup(struct platform_device *device)  	if (hp_wmi_rfkill_setup(device))  		hp_wmi_rfkill2_setup(device); -	thermal_policy_setup(device); +	thermal_profile_setup();  	return 0;  } @@ -927,6 +1011,9 @@ static int __exit hp_wmi_bios_remove(struct platform_device *device)  		rfkill_destroy(wwan_rfkill);  	} +	if (platform_profile_support) +		platform_profile_remove(); +  	return 0;  } diff --git a/drivers/platform/x86/intel-hid.c b/drivers/platform/x86/intel-hid.c index 2f5b8d09143e..078648a9201b 100644 --- a/drivers/platform/x86/intel-hid.c +++ b/drivers/platform/x86/intel-hid.c @@ -90,6 +90,13 @@ static const struct dmi_system_id button_array_table[] = {  			DMI_MATCH(DMI_PRODUCT_NAME, "HP Spectre x2 Detachable"),  		},  	}, +	{ +		.ident = "Lenovo ThinkPad X1 Tablet Gen 2", +		.matches = { +			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), +			DMI_MATCH(DMI_PRODUCT_FAMILY, "ThinkPad X1 Tablet Gen 2"), +		}, +	},  	{ }  }; @@ -476,11 +483,16 @@ static void notify_handler(acpi_handle handle, u32 event, void *context)  			goto wakeup;  		/* -		 * Switch events will wake the device and report the new switch -		 * position to the input subsystem. +		 * Some devices send (duplicate) tablet-mode events when moved +		 * around even though the mode has not changed; and they do this +		 * even when suspended. +		 * Update the switch state in case it changed and then return +		 * without waking up to avoid spurious wakeups.  		 */ -		if (priv->switches && (event == 0xcc || event == 0xcd)) -			goto wakeup; +		if (event == 0xcc || event == 0xcd) { +			report_tablet_mode_event(priv->switches, event); +			return; +		}  		/* Wake up on 5-button array events only. */  		if (event == 0xc0 || !priv->array) @@ -494,9 +506,6 @@ static void notify_handler(acpi_handle handle, u32 event, void *context)  wakeup:  		pm_wakeup_hard_event(&device->dev); -		if (report_tablet_mode_event(priv->switches, event)) -			return; -  		return;  	} diff --git a/drivers/platform/x86/intel-vbtn.c b/drivers/platform/x86/intel-vbtn.c index 8a8017f9ca91..888a764efad1 100644 --- a/drivers/platform/x86/intel-vbtn.c +++ b/drivers/platform/x86/intel-vbtn.c @@ -48,16 +48,21 @@ static const struct key_entry intel_vbtn_keymap[] = {  };  static const struct key_entry intel_vbtn_switchmap[] = { -	{ KE_SW,     0xCA, { .sw = { SW_DOCK, 1 } } },		/* Docked */ -	{ KE_SW,     0xCB, { .sw = { SW_DOCK, 0 } } },		/* Undocked */ +	/* +	 * SW_DOCK should only be reported for docking stations, but DSDTs using the +	 * intel-vbtn code, always seem to use this for 2-in-1s / convertibles and set +	 * SW_DOCK=1 when in laptop-mode (in tandem with setting SW_TABLET_MODE=0). +	 * This causes userspace to think the laptop is docked to a port-replicator +	 * and to disable suspend-on-lid-close, which is undesirable. +	 * Map the dock events to KEY_IGNORE to avoid this broken SW_DOCK reporting. +	 */ +	{ KE_IGNORE, 0xCA, { .sw = { SW_DOCK, 1 } } },		/* Docked */ +	{ KE_IGNORE, 0xCB, { .sw = { SW_DOCK, 0 } } },		/* Undocked */  	{ KE_SW,     0xCC, { .sw = { SW_TABLET_MODE, 1 } } },	/* Tablet */  	{ KE_SW,     0xCD, { .sw = { SW_TABLET_MODE, 0 } } },	/* Laptop */  	{ KE_END }  }; -#define KEYMAP_LEN \ -	(ARRAY_SIZE(intel_vbtn_keymap) + ARRAY_SIZE(intel_vbtn_switchmap) + 1) -  struct intel_vbtn_priv {  	struct input_dev *buttons_dev;  	struct input_dev *switches_dev; diff --git a/drivers/platform/x86/intel-wmi-sbl-fw-update.c b/drivers/platform/x86/intel-wmi-sbl-fw-update.c index ea87fa0786e8..3c86e0108a24 100644 --- a/drivers/platform/x86/intel-wmi-sbl-fw-update.c +++ b/drivers/platform/x86/intel-wmi-sbl-fw-update.c @@ -117,10 +117,9 @@ static int intel_wmi_sbl_fw_update_probe(struct wmi_device *wdev,  	return 0;  } -static int intel_wmi_sbl_fw_update_remove(struct wmi_device *wdev) +static void intel_wmi_sbl_fw_update_remove(struct wmi_device *wdev)  {  	dev_info(&wdev->dev, "Slim Bootloader signaling driver removed\n"); -	return 0;  }  static const struct wmi_device_id intel_wmi_sbl_id_table[] = { diff --git a/drivers/platform/x86/intel-wmi-thunderbolt.c b/drivers/platform/x86/intel-wmi-thunderbolt.c index 974c22a7ff61..4ae87060d18b 100644 --- a/drivers/platform/x86/intel-wmi-thunderbolt.c +++ b/drivers/platform/x86/intel-wmi-thunderbolt.c @@ -66,11 +66,10 @@ static int intel_wmi_thunderbolt_probe(struct wmi_device *wdev,  	return ret;  } -static int intel_wmi_thunderbolt_remove(struct wmi_device *wdev) +static void intel_wmi_thunderbolt_remove(struct wmi_device *wdev)  {  	sysfs_remove_group(&wdev->dev.kobj, &tbt_attribute_group);  	kobject_uevent(&wdev->dev.kobj, KOBJ_CHANGE); -	return 0;  }  static const struct wmi_device_id intel_wmi_thunderbolt_id_table[] = { diff --git a/drivers/platform/x86/intel_cht_int33fe_microb.c b/drivers/platform/x86/intel_cht_int33fe_microb.c index 20b11e0d9a75..673f41cd14b5 100644 --- a/drivers/platform/x86/intel_cht_int33fe_microb.c +++ b/drivers/platform/x86/intel_cht_int33fe_microb.c @@ -35,6 +35,10 @@ static const struct property_entry bq27xxx_props[] = {  	{ }  }; +static const struct software_node bq27xxx_node = { +	.properties = bq27xxx_props, +}; +  int cht_int33fe_microb_probe(struct cht_int33fe_data *data)  {  	struct device *dev = data->dev; @@ -43,7 +47,7 @@ int cht_int33fe_microb_probe(struct cht_int33fe_data *data)  	memset(&board_info, 0, sizeof(board_info));  	strscpy(board_info.type, "bq27542", ARRAY_SIZE(board_info.type));  	board_info.dev_name = "bq27542"; -	board_info.properties = bq27xxx_props; +	board_info.swnode = &bq27xxx_node;  	data->battery_fg = i2c_acpi_new_device(dev, 1, &board_info);  	return PTR_ERR_OR_ZERO(data->battery_fg); diff --git a/drivers/platform/x86/intel_cht_int33fe_typec.c b/drivers/platform/x86/intel_cht_int33fe_typec.c index 48638d1c56e5..b61bad9cc8d2 100644 --- a/drivers/platform/x86/intel_cht_int33fe_typec.c +++ b/drivers/platform/x86/intel_cht_int33fe_typec.c @@ -124,12 +124,31 @@ static const struct software_node usb_connector_node = {  	.properties = usb_connector_properties,  }; +static const struct software_node altmodes_node = { +	.name = "altmodes", +	.parent = &usb_connector_node, +}; + +static const struct property_entry dp_altmode_properties[] = { +	PROPERTY_ENTRY_U32("svid", 0xff01), +	PROPERTY_ENTRY_U32("vdo", 0x0c0086), +	{ } +}; + +static const struct software_node dp_altmode_node = { +	.name = "displayport-altmode", +	.parent = &altmodes_node, +	.properties = dp_altmode_properties, +}; +  static const struct software_node *node_group[] = {  	&fusb302_node,  	&max17047_node,  	&pi3usb30532_node,  	&displayport_node,  	&usb_connector_node, +	&altmodes_node, +	&dp_altmode_node,  	NULL  }; diff --git a/drivers/platform/x86/intel_chtdc_ti_pwrbtn.c b/drivers/platform/x86/intel_chtdc_ti_pwrbtn.c index 0df2e82dd249..9606a994af22 100644 --- a/drivers/platform/x86/intel_chtdc_ti_pwrbtn.c +++ b/drivers/platform/x86/intel_chtdc_ti_pwrbtn.c @@ -58,7 +58,7 @@ static int chtdc_ti_pwrbtn_probe(struct platform_device *pdev)  	err = devm_request_threaded_irq(dev, irq, NULL,  					chtdc_ti_pwrbtn_interrupt, -					0, KBUILD_MODNAME, input); +					IRQF_ONESHOT, KBUILD_MODNAME, input);  	if (err)  		return err; diff --git a/drivers/platform/x86/intel_pmc_core.c b/drivers/platform/x86/intel_pmc_core.c index ee2f757515b0..b0e486a6bdfb 100644 --- a/drivers/platform/x86/intel_pmc_core.c +++ b/drivers/platform/x86/intel_pmc_core.c @@ -23,7 +23,9 @@  #include <linux/slab.h>  #include <linux/suspend.h>  #include <linux/uaccess.h> +#include <linux/uuid.h> +#include <acpi/acpi_bus.h>  #include <asm/cpu_device_id.h>  #include <asm/intel-family.h>  #include <asm/msr.h> @@ -31,7 +33,8 @@  #include "intel_pmc_core.h" -static struct pmc_dev pmc; +#define ACPI_S0IX_DSM_UUID		"57a6512e-3979-4e9d-9708-ff13b2508972" +#define ACPI_GET_LOW_MODE_REGISTERS	1  /* PKGC MSRs are common across Intel Core SoCs */  static const struct pmc_bit_map msr_map[] = { @@ -380,6 +383,8 @@ static const struct pmc_bit_map cnp_ltr_show_map[] = {  	 * a list of core SoCs using this.  	 */  	{"WIGIG",		ICL_PMC_LTR_WIGIG}, +	{"THC0",                TGL_PMC_LTR_THC0}, +	{"THC1",                TGL_PMC_LTR_THC1},  	/* Below two cannot be used for LTR_IGNORE */  	{"CURRENT_PLATFORM",	CNP_PMC_LTR_CUR_PLT},  	{"AGGREGATED_SYSTEM",	CNP_PMC_LTR_CUR_ASLT}, @@ -401,6 +406,7 @@ static const struct pmc_reg_map cnp_reg_map = {  	.pm_cfg_offset = CNP_PMC_PM_CFG_OFFSET,  	.pm_read_disable_bit = CNP_PMC_READ_DISABLE_BIT,  	.ltr_ignore_max = CNP_NUM_IP_IGN_ALLOWED, +	.etr3_offset = ETR3_OFFSET,  };  static const struct pmc_reg_map icl_reg_map = { @@ -418,6 +424,7 @@ static const struct pmc_reg_map icl_reg_map = {  	.pm_cfg_offset = CNP_PMC_PM_CFG_OFFSET,  	.pm_read_disable_bit = CNP_PMC_READ_DISABLE_BIT,  	.ltr_ignore_max = ICL_NUM_IP_IGN_ALLOWED, +	.etr3_offset = ETR3_OFFSET,  };  static const struct pmc_bit_map tgl_clocksource_status_map[] = { @@ -579,14 +586,65 @@ static const struct pmc_reg_map tgl_reg_map = {  	.pm_cfg_offset = CNP_PMC_PM_CFG_OFFSET,  	.pm_read_disable_bit = CNP_PMC_READ_DISABLE_BIT,  	.ltr_ignore_max = TGL_NUM_IP_IGN_ALLOWED, -	.lpm_modes = tgl_lpm_modes, +	.lpm_num_maps = TGL_LPM_NUM_MAPS, +	.lpm_res_counter_step_x2 = TGL_PMC_LPM_RES_COUNTER_STEP_X2, +	.lpm_sts_latch_en_offset = TGL_LPM_STS_LATCH_EN_OFFSET,  	.lpm_en_offset = TGL_LPM_EN_OFFSET, +	.lpm_priority_offset = TGL_LPM_PRI_OFFSET,  	.lpm_residency_offset = TGL_LPM_RESIDENCY_OFFSET,  	.lpm_sts = tgl_lpm_maps,  	.lpm_status_offset = TGL_LPM_STATUS_OFFSET,  	.lpm_live_status_offset = TGL_LPM_LIVE_STATUS_OFFSET, +	.etr3_offset = ETR3_OFFSET,  }; +static void pmc_core_get_tgl_lpm_reqs(struct platform_device *pdev) +{ +	struct pmc_dev *pmcdev = platform_get_drvdata(pdev); +	const int num_maps = pmcdev->map->lpm_num_maps; +	u32 lpm_size = LPM_MAX_NUM_MODES * num_maps * 4; +	union acpi_object *out_obj; +	struct acpi_device *adev; +	guid_t s0ix_dsm_guid; +	u32 *lpm_req_regs, *addr; + +	adev = ACPI_COMPANION(&pdev->dev); +	if (!adev) +		return; + +	guid_parse(ACPI_S0IX_DSM_UUID, &s0ix_dsm_guid); + +	out_obj = acpi_evaluate_dsm(adev->handle, &s0ix_dsm_guid, 0, +				    ACPI_GET_LOW_MODE_REGISTERS, NULL); +	if (out_obj && out_obj->type == ACPI_TYPE_BUFFER) { +		u32 size = out_obj->buffer.length; + +		if (size != lpm_size) { +			acpi_handle_debug(adev->handle, +				"_DSM returned unexpected buffer size, have %u, expect %u\n", +				size, lpm_size); +			goto free_acpi_obj; +		} +	} else { +		acpi_handle_debug(adev->handle, +				  "_DSM function 0 evaluation failed\n"); +		goto free_acpi_obj; +	} + +	addr = (u32 *)out_obj->buffer.pointer; + +	lpm_req_regs = devm_kzalloc(&pdev->dev, lpm_size * sizeof(u32), +				     GFP_KERNEL); +	if (!lpm_req_regs) +		goto free_acpi_obj; + +	memcpy(lpm_req_regs, addr, lpm_size); +	pmcdev->lpm_req_regs = lpm_req_regs; + +free_acpi_obj: +	ACPI_FREE(out_obj); +} +  static inline u32 pmc_core_reg_read(struct pmc_dev *pmcdev, int reg_offset)  {  	return readl(pmcdev->regbase + reg_offset); @@ -603,6 +661,115 @@ static inline u64 pmc_core_adjust_slp_s0_step(struct pmc_dev *pmcdev, u32 value)  	return (u64)value * pmcdev->map->slp_s0_res_counter_step;  } +static int set_etr3(struct pmc_dev *pmcdev) +{ +	const struct pmc_reg_map *map = pmcdev->map; +	u32 reg; +	int err; + +	if (!map->etr3_offset) +		return -EOPNOTSUPP; + +	mutex_lock(&pmcdev->lock); + +	/* check if CF9 is locked */ +	reg = pmc_core_reg_read(pmcdev, map->etr3_offset); +	if (reg & ETR3_CF9LOCK) { +		err = -EACCES; +		goto out_unlock; +	} + +	/* write CF9 global reset bit */ +	reg |= ETR3_CF9GR; +	pmc_core_reg_write(pmcdev, map->etr3_offset, reg); + +	reg = pmc_core_reg_read(pmcdev, map->etr3_offset); +	if (!(reg & ETR3_CF9GR)) { +		err = -EIO; +		goto out_unlock; +	} + +	err = 0; + +out_unlock: +	mutex_unlock(&pmcdev->lock); +	return err; +} +static umode_t etr3_is_visible(struct kobject *kobj, +				struct attribute *attr, +				int idx) +{ +	struct device *dev = container_of(kobj, struct device, kobj); +	struct pmc_dev *pmcdev = dev_get_drvdata(dev); +	const struct pmc_reg_map *map = pmcdev->map; +	u32 reg; + +	mutex_lock(&pmcdev->lock); +	reg = pmc_core_reg_read(pmcdev, map->etr3_offset); +	mutex_unlock(&pmcdev->lock); + +	return reg & ETR3_CF9LOCK ? attr->mode & (SYSFS_PREALLOC | 0444) : attr->mode; +} + +static ssize_t etr3_show(struct device *dev, +				 struct device_attribute *attr, char *buf) +{ +	struct pmc_dev *pmcdev = dev_get_drvdata(dev); +	const struct pmc_reg_map *map = pmcdev->map; +	u32 reg; + +	if (!map->etr3_offset) +		return -EOPNOTSUPP; + +	mutex_lock(&pmcdev->lock); + +	reg = pmc_core_reg_read(pmcdev, map->etr3_offset); +	reg &= ETR3_CF9GR | ETR3_CF9LOCK; + +	mutex_unlock(&pmcdev->lock); + +	return sysfs_emit(buf, "0x%08x", reg); +} + +static ssize_t etr3_store(struct device *dev, +				  struct device_attribute *attr, +				  const char *buf, size_t len) +{ +	struct pmc_dev *pmcdev = dev_get_drvdata(dev); +	int err; +	u32 reg; + +	err = kstrtouint(buf, 16, ®); +	if (err) +		return err; + +	/* allow only CF9 writes */ +	if (reg != ETR3_CF9GR) +		return -EINVAL; + +	err = set_etr3(pmcdev); +	if (err) +		return err; + +	return len; +} +static DEVICE_ATTR_RW(etr3); + +static struct attribute *pmc_attrs[] = { +	&dev_attr_etr3.attr, +	NULL +}; + +static const struct attribute_group pmc_attr_group = { +	.attrs = pmc_attrs, +	.is_visible = etr3_is_visible, +}; + +static const struct attribute_group *pmc_dev_groups[] = { +	&pmc_attr_group, +	NULL +}; +  static int pmc_core_dev_state_get(void *data, u64 *val)  {  	struct pmc_dev *pmcdev = data; @@ -617,9 +784,8 @@ static int pmc_core_dev_state_get(void *data, u64 *val)  DEFINE_DEBUGFS_ATTRIBUTE(pmc_core_dev_state, pmc_core_dev_state_get, NULL, "%llu\n"); -static int pmc_core_check_read_lock_bit(void) +static int pmc_core_check_read_lock_bit(struct pmc_dev *pmcdev)  { -	struct pmc_dev *pmcdev = &pmc;  	u32 value;  	value = pmc_core_reg_read(pmcdev, pmcdev->map->pm_cfg_offset); @@ -744,28 +910,26 @@ static int pmc_core_ppfear_show(struct seq_file *s, void *unused)  DEFINE_SHOW_ATTRIBUTE(pmc_core_ppfear);  /* This function should return link status, 0 means ready */ -static int pmc_core_mtpmc_link_status(void) +static int pmc_core_mtpmc_link_status(struct pmc_dev *pmcdev)  { -	struct pmc_dev *pmcdev = &pmc;  	u32 value;  	value = pmc_core_reg_read(pmcdev, SPT_PMC_PM_STS_OFFSET);  	return value & BIT(SPT_PMC_MSG_FULL_STS_BIT);  } -static int pmc_core_send_msg(u32 *addr_xram) +static int pmc_core_send_msg(struct pmc_dev *pmcdev, u32 *addr_xram)  { -	struct pmc_dev *pmcdev = &pmc;  	u32 dest;  	int timeout;  	for (timeout = NUM_RETRIES; timeout > 0; timeout--) { -		if (pmc_core_mtpmc_link_status() == 0) +		if (pmc_core_mtpmc_link_status(pmcdev) == 0)  			break;  		msleep(5);  	} -	if (timeout <= 0 && pmc_core_mtpmc_link_status()) +	if (timeout <= 0 && pmc_core_mtpmc_link_status(pmcdev))  		return -EBUSY;  	dest = (*addr_xram & MTPMC_MASK) | (1U << 1); @@ -791,7 +955,7 @@ static int pmc_core_mphy_pg_show(struct seq_file *s, void *unused)  	mutex_lock(&pmcdev->lock); -	if (pmc_core_send_msg(&mphy_core_reg_low) != 0) { +	if (pmc_core_send_msg(pmcdev, &mphy_core_reg_low) != 0) {  		err = -EBUSY;  		goto out_unlock;  	} @@ -799,7 +963,7 @@ static int pmc_core_mphy_pg_show(struct seq_file *s, void *unused)  	msleep(10);  	val_low = pmc_core_reg_read(pmcdev, SPT_PMC_MFPMC_OFFSET); -	if (pmc_core_send_msg(&mphy_core_reg_high) != 0) { +	if (pmc_core_send_msg(pmcdev, &mphy_core_reg_high) != 0) {  		err = -EBUSY;  		goto out_unlock;  	} @@ -842,7 +1006,7 @@ static int pmc_core_pll_show(struct seq_file *s, void *unused)  	mphy_common_reg  = (SPT_PMC_MPHY_COM_STS_0 << 16);  	mutex_lock(&pmcdev->lock); -	if (pmc_core_send_msg(&mphy_common_reg) != 0) { +	if (pmc_core_send_msg(pmcdev, &mphy_common_reg) != 0) {  		err = -EBUSY;  		goto out_unlock;  	} @@ -863,34 +1027,46 @@ out_unlock:  }  DEFINE_SHOW_ATTRIBUTE(pmc_core_pll); -static ssize_t pmc_core_ltr_ignore_write(struct file *file, -					 const char __user *userbuf, -					 size_t count, loff_t *ppos) +static int pmc_core_send_ltr_ignore(struct pmc_dev *pmcdev, u32 value)  { -	struct pmc_dev *pmcdev = &pmc;  	const struct pmc_reg_map *map = pmcdev->map; -	u32 val, buf_size, fd; -	int err; - -	buf_size = count < 64 ? count : 64; - -	err = kstrtou32_from_user(userbuf, buf_size, 10, &val); -	if (err) -		return err; +	u32 reg; +	int err = 0;  	mutex_lock(&pmcdev->lock); -	if (val > map->ltr_ignore_max) { +	if (value > map->ltr_ignore_max) {  		err = -EINVAL;  		goto out_unlock;  	} -	fd = pmc_core_reg_read(pmcdev, map->ltr_ignore_offset); -	fd |= (1U << val); -	pmc_core_reg_write(pmcdev, map->ltr_ignore_offset, fd); +	reg = pmc_core_reg_read(pmcdev, map->ltr_ignore_offset); +	reg |= BIT(value); +	pmc_core_reg_write(pmcdev, map->ltr_ignore_offset, reg);  out_unlock:  	mutex_unlock(&pmcdev->lock); + +	return err; +} + +static ssize_t pmc_core_ltr_ignore_write(struct file *file, +					 const char __user *userbuf, +					 size_t count, loff_t *ppos) +{ +	struct seq_file *s = file->private_data; +	struct pmc_dev *pmcdev = s->private; +	u32 buf_size, value; +	int err; + +	buf_size = min_t(u32, count, 64); + +	err = kstrtou32_from_user(userbuf, buf_size, 10, &value); +	if (err) +		return err; + +	err = pmc_core_send_ltr_ignore(pmcdev, value); +  	return err == 0 ? count : err;  } @@ -1018,21 +1194,26 @@ static int pmc_core_ltr_show(struct seq_file *s, void *unused)  }  DEFINE_SHOW_ATTRIBUTE(pmc_core_ltr); +static inline u64 adjust_lpm_residency(struct pmc_dev *pmcdev, u32 offset, +				       const int lpm_adj_x2) +{ +	u64 lpm_res = pmc_core_reg_read(pmcdev, offset); + +	return GET_X2_COUNTER((u64)lpm_adj_x2 * lpm_res); +} +  static int pmc_core_substate_res_show(struct seq_file *s, void *unused)  {  	struct pmc_dev *pmcdev = s->private; -	const char **lpm_modes = pmcdev->map->lpm_modes; +	const int lpm_adj_x2 = pmcdev->map->lpm_res_counter_step_x2;  	u32 offset = pmcdev->map->lpm_residency_offset; -	u32 lpm_en; -	int index; +	int i, mode; -	lpm_en = pmc_core_reg_read(pmcdev, pmcdev->map->lpm_en_offset); -	seq_printf(s, "status substate residency\n"); -	for (index = 0; lpm_modes[index]; index++) { -		seq_printf(s, "%7s %7s %-15u\n", -			   BIT(index) & lpm_en ? "Enabled" : " ", -			   lpm_modes[index], pmc_core_reg_read(pmcdev, offset)); -		offset += 4; +	seq_printf(s, "%-10s %-15s\n", "Substate", "Residency"); + +	pmc_for_each_mode(i, mode, pmcdev) { +		seq_printf(s, "%-10s %-15llu\n", pmc_lpm_modes[mode], +			   adjust_lpm_residency(pmcdev, offset + (4 * mode), lpm_adj_x2));  	}  	return 0; @@ -1063,6 +1244,190 @@ static int pmc_core_substate_l_sts_regs_show(struct seq_file *s, void *unused)  }  DEFINE_SHOW_ATTRIBUTE(pmc_core_substate_l_sts_regs); +static void pmc_core_substate_req_header_show(struct seq_file *s) +{ +	struct pmc_dev *pmcdev = s->private; +	int i, mode; + +	seq_printf(s, "%30s |", "Element"); +	pmc_for_each_mode(i, mode, pmcdev) +		seq_printf(s, " %9s |", pmc_lpm_modes[mode]); + +	seq_printf(s, " %9s |\n", "Status"); +} + +static int pmc_core_substate_req_regs_show(struct seq_file *s, void *unused) +{ +	struct pmc_dev *pmcdev = s->private; +	const struct pmc_bit_map **maps = pmcdev->map->lpm_sts; +	const struct pmc_bit_map *map; +	const int num_maps = pmcdev->map->lpm_num_maps; +	u32 sts_offset = pmcdev->map->lpm_status_offset; +	u32 *lpm_req_regs = pmcdev->lpm_req_regs; +	int mp; + +	/* Display the header */ +	pmc_core_substate_req_header_show(s); + +	/* Loop over maps */ +	for (mp = 0; mp < num_maps; mp++) { +		u32 req_mask = 0; +		u32 lpm_status; +		int mode, idx, i, len = 32; + +		/* +		 * Capture the requirements and create a mask so that we only +		 * show an element if it's required for at least one of the +		 * enabled low power modes +		 */ +		pmc_for_each_mode(idx, mode, pmcdev) +			req_mask |= lpm_req_regs[mp + (mode * num_maps)]; + +		/* Get the last latched status for this map */ +		lpm_status = pmc_core_reg_read(pmcdev, sts_offset + (mp * 4)); + +		/*  Loop over elements in this map */ +		map = maps[mp]; +		for (i = 0; map[i].name && i < len; i++) { +			u32 bit_mask = map[i].bit_mask; + +			if (!(bit_mask & req_mask)) +				/* +				 * Not required for any enabled states +				 * so don't display +				 */ +				continue; + +			/* Display the element name in the first column */ +			seq_printf(s, "%30s |", map[i].name); + +			/* Loop over the enabled states and display if required */ +			pmc_for_each_mode(idx, mode, pmcdev) { +				if (lpm_req_regs[mp + (mode * num_maps)] & bit_mask) +					seq_printf(s, " %9s |", +						   "Required"); +				else +					seq_printf(s, " %9s |", " "); +			} + +			/* In Status column, show the last captured state of this agent */ +			if (lpm_status & bit_mask) +				seq_printf(s, " %9s |", "Yes"); +			else +				seq_printf(s, " %9s |", " "); + +			seq_puts(s, "\n"); +		} +	} + +	return 0; +} +DEFINE_SHOW_ATTRIBUTE(pmc_core_substate_req_regs); + +static int pmc_core_lpm_latch_mode_show(struct seq_file *s, void *unused) +{ +	struct pmc_dev *pmcdev = s->private; +	bool c10; +	u32 reg; +	int idx, mode; + +	reg = pmc_core_reg_read(pmcdev, pmcdev->map->lpm_sts_latch_en_offset); +	if (reg & LPM_STS_LATCH_MODE) { +		seq_puts(s, "c10"); +		c10 = false; +	} else { +		seq_puts(s, "[c10]"); +		c10 = true; +	} + +	pmc_for_each_mode(idx, mode, pmcdev) { +		if ((BIT(mode) & reg) && !c10) +			seq_printf(s, " [%s]", pmc_lpm_modes[mode]); +		else +			seq_printf(s, " %s", pmc_lpm_modes[mode]); +	} + +	seq_puts(s, " clear\n"); + +	return 0; +} + +static ssize_t pmc_core_lpm_latch_mode_write(struct file *file, +					     const char __user *userbuf, +					     size_t count, loff_t *ppos) +{ +	struct seq_file *s = file->private_data; +	struct pmc_dev *pmcdev = s->private; +	bool clear = false, c10 = false; +	unsigned char buf[8]; +	int idx, m, mode; +	u32 reg; + +	if (count > sizeof(buf) - 1) +		return -EINVAL; +	if (copy_from_user(buf, userbuf, count)) +		return -EFAULT; +	buf[count] = '\0'; + +	/* +	 * Allowed strings are: +	 *	Any enabled substate, e.g. 'S0i2.0' +	 *	'c10' +	 *	'clear' +	 */ +	mode = sysfs_match_string(pmc_lpm_modes, buf); + +	/* Check string matches enabled mode */ +	pmc_for_each_mode(idx, m, pmcdev) +		if (mode == m) +			break; + +	if (mode != m || mode < 0) { +		if (sysfs_streq(buf, "clear")) +			clear = true; +		else if (sysfs_streq(buf, "c10")) +			c10 = true; +		else +			return -EINVAL; +	} + +	if (clear) { +		mutex_lock(&pmcdev->lock); + +		reg = pmc_core_reg_read(pmcdev, pmcdev->map->etr3_offset); +		reg |= ETR3_CLEAR_LPM_EVENTS; +		pmc_core_reg_write(pmcdev, pmcdev->map->etr3_offset, reg); + +		mutex_unlock(&pmcdev->lock); + +		return count; +	} + +	if (c10) { +		mutex_lock(&pmcdev->lock); + +		reg = pmc_core_reg_read(pmcdev, pmcdev->map->lpm_sts_latch_en_offset); +		reg &= ~LPM_STS_LATCH_MODE; +		pmc_core_reg_write(pmcdev, pmcdev->map->lpm_sts_latch_en_offset, reg); + +		mutex_unlock(&pmcdev->lock); + +		return count; +	} + +	/* +	 * For LPM mode latching we set the latch enable bit and selected mode +	 * and clear everything else. +	 */ +	reg = LPM_STS_LATCH_MODE | BIT(mode); +	mutex_lock(&pmcdev->lock); +	pmc_core_reg_write(pmcdev, pmcdev->map->lpm_sts_latch_en_offset, reg); +	mutex_unlock(&pmcdev->lock); + +	return count; +} +DEFINE_PMC_CORE_ATTR_WRITE(pmc_core_lpm_latch_mode); +  static int pmc_core_pkgc_show(struct seq_file *s, void *unused)  {  	struct pmc_dev *pmcdev = s->private; @@ -1084,6 +1449,45 @@ static int pmc_core_pkgc_show(struct seq_file *s, void *unused)  }  DEFINE_SHOW_ATTRIBUTE(pmc_core_pkgc); +static void pmc_core_get_low_power_modes(struct pmc_dev *pmcdev) +{ +	u8 lpm_priority[LPM_MAX_NUM_MODES]; +	u32 lpm_en; +	int mode, i, p; + +	/* Use LPM Maps to indicate support for substates */ +	if (!pmcdev->map->lpm_num_maps) +		return; + +	lpm_en = pmc_core_reg_read(pmcdev, pmcdev->map->lpm_en_offset); +	pmcdev->num_lpm_modes = hweight32(lpm_en); + +	/* Each byte contains information for 2 modes (7:4 and 3:0) */ +	for (mode = 0; mode < LPM_MAX_NUM_MODES; mode += 2) { +		u8 priority = pmc_core_reg_read_byte(pmcdev, +				pmcdev->map->lpm_priority_offset + (mode / 2)); +		int pri0 = GENMASK(3, 0) & priority; +		int pri1 = (GENMASK(7, 4) & priority) >> 4; + +		lpm_priority[pri0] = mode; +		lpm_priority[pri1] = mode + 1; +	} + +	/* +	 * Loop though all modes from lowest to highest priority, +	 * and capture all enabled modes in order +	 */ +	i = 0; +	for (p = LPM_MAX_NUM_MODES - 1; p >= 0; p--) { +		int mode = lpm_priority[p]; + +		if (!(BIT(mode) & lpm_en)) +			continue; + +		pmcdev->lpm_en_modes[i++] = mode; +	} +} +  static void pmc_core_dbgfs_unregister(struct pmc_dev *pmcdev)  {  	debugfs_remove_recursive(pmcdev->dbgfs_dir); @@ -1142,6 +1546,15 @@ static void pmc_core_dbgfs_register(struct pmc_dev *pmcdev)  		debugfs_create_file("substate_live_status_registers", 0444,  				    pmcdev->dbgfs_dir, pmcdev,  				    &pmc_core_substate_l_sts_regs_fops); +		debugfs_create_file("lpm_latch_mode", 0644, +				    pmcdev->dbgfs_dir, pmcdev, +				    &pmc_core_lpm_latch_mode_fops); +	} + +	if (pmcdev->lpm_req_regs) { +		debugfs_create_file("substate_requirements", 0444, +				    pmcdev->dbgfs_dir, pmcdev, +				    &pmc_core_substate_req_regs_fops);  	}  } @@ -1160,6 +1573,7 @@ static const struct x86_cpu_id intel_pmc_core_ids[] = {  	X86_MATCH_INTEL_FAM6_MODEL(ATOM_TREMONT,	&tgl_reg_map),  	X86_MATCH_INTEL_FAM6_MODEL(ATOM_TREMONT_L,	&icl_reg_map),  	X86_MATCH_INTEL_FAM6_MODEL(ROCKETLAKE,		&tgl_reg_map), +	X86_MATCH_INTEL_FAM6_MODEL(ALDERLAKE_L,		&tgl_reg_map),  	{}  }; @@ -1175,9 +1589,15 @@ static const struct pci_device_id pmc_pci_ids[] = {   * the platform BIOS enforces 24Mhz crystal to shutdown   * before PMC can assert SLP_S0#.   */ +static bool xtal_ignore;  static int quirk_xtal_ignore(const struct dmi_system_id *id)  { -	struct pmc_dev *pmcdev = &pmc; +	xtal_ignore = true; +	return 0; +} + +static void pmc_core_xtal_ignore(struct pmc_dev *pmcdev) +{  	u32 value;  	value = pmc_core_reg_read(pmcdev, pmcdev->map->pm_vric1_offset); @@ -1186,7 +1606,6 @@ static int quirk_xtal_ignore(const struct dmi_system_id *id)  	/* Low Voltage Mode Enable */  	value &= ~SPT_PMC_VRIC1_SLPS0LVEN;  	pmc_core_reg_write(pmcdev, pmcdev->map->pm_vric1_offset, value); -	return 0;  }  static const struct dmi_system_id pmc_core_dmi_table[]  = { @@ -1201,16 +1620,30 @@ static const struct dmi_system_id pmc_core_dmi_table[]  = {  	{}  }; +static void pmc_core_do_dmi_quirks(struct pmc_dev *pmcdev) +{ +	dmi_check_system(pmc_core_dmi_table); + +	if (xtal_ignore) +		pmc_core_xtal_ignore(pmcdev); +} +  static int pmc_core_probe(struct platform_device *pdev)  {  	static bool device_initialized; -	struct pmc_dev *pmcdev = &pmc; +	struct pmc_dev *pmcdev;  	const struct x86_cpu_id *cpu_id;  	u64 slp_s0_addr;  	if (device_initialized)  		return -ENODEV; +	pmcdev = devm_kzalloc(&pdev->dev, sizeof(*pmcdev), GFP_KERNEL); +	if (!pmcdev) +		return -ENOMEM; + +	platform_set_drvdata(pdev, pmcdev); +  	cpu_id = x86_match_cpu(intel_pmc_core_ids);  	if (!cpu_id)  		return -ENODEV; @@ -1240,9 +1673,22 @@ static int pmc_core_probe(struct platform_device *pdev)  		return -ENOMEM;  	mutex_init(&pmcdev->lock); -	platform_set_drvdata(pdev, pmcdev); -	pmcdev->pmc_xram_read_bit = pmc_core_check_read_lock_bit(); -	dmi_check_system(pmc_core_dmi_table); + +	pmcdev->pmc_xram_read_bit = pmc_core_check_read_lock_bit(pmcdev); +	pmc_core_get_low_power_modes(pmcdev); +	pmc_core_do_dmi_quirks(pmcdev); + +	if (pmcdev->map == &tgl_reg_map) +		pmc_core_get_tgl_lpm_reqs(pdev); + +	/* +	 * On TGL, due to a hardware limitation, the GBE LTR blocks PC10 when +	 * a cable is attached. Tell the PMC to ignore it. +	 */ +	if (pmcdev->map == &tgl_reg_map) { +		dev_dbg(&pdev->dev, "ignoring GBE LTR\n"); +		pmc_core_send_ltr_ignore(pmcdev, 3); +	}  	pmc_core_dbgfs_register(pmcdev); @@ -1364,6 +1810,7 @@ static struct platform_driver pmc_core_driver = {  		.name = "intel_pmc_core",  		.acpi_match_table = ACPI_PTR(pmc_core_acpi_ids),  		.pm = &pmc_core_pm_ops, +		.dev_groups = pmc_dev_groups,  	},  	.probe = pmc_core_probe,  	.remove = pmc_core_remove, diff --git a/drivers/platform/x86/intel_pmc_core.h b/drivers/platform/x86/intel_pmc_core.h index f33cd2c34835..e8dae9c6c45f 100644 --- a/drivers/platform/x86/intel_pmc_core.h +++ b/drivers/platform/x86/intel_pmc_core.h @@ -187,20 +187,38 @@ enum ppfear_regs {  #define ICL_PMC_LTR_WIGIG			0x1BFC  #define ICL_PMC_SLP_S0_RES_COUNTER_STEP		0x64 -#define TGL_NUM_IP_IGN_ALLOWED			22 +#define LPM_MAX_NUM_MODES			8 +#define GET_X2_COUNTER(v)			((v) >> 1) +#define LPM_STS_LATCH_MODE			BIT(31) +  #define TGL_PMC_SLP_S0_RES_COUNTER_STEP		0x7A +#define TGL_PMC_LTR_THC0			0x1C04 +#define TGL_PMC_LTR_THC1			0x1C08 +#define TGL_NUM_IP_IGN_ALLOWED			23 +#define TGL_PMC_LPM_RES_COUNTER_STEP_X2		61	/* 30.5us * 2 */  /*   * Tigerlake Power Management Controller register offsets   */ +#define TGL_LPM_STS_LATCH_EN_OFFSET		0x1C34  #define TGL_LPM_EN_OFFSET			0x1C78  #define TGL_LPM_RESIDENCY_OFFSET		0x1C80  /* Tigerlake Low Power Mode debug registers */  #define TGL_LPM_STATUS_OFFSET			0x1C3C  #define TGL_LPM_LIVE_STATUS_OFFSET		0x1C5C +#define TGL_LPM_PRI_OFFSET			0x1C7C +#define TGL_LPM_NUM_MAPS			6 + +/* Extended Test Mode Register 3 (CNL and later) */ +#define ETR3_OFFSET				0x1048 +#define ETR3_CF9GR				BIT(20) +#define ETR3_CF9LOCK				BIT(31) + +/* Extended Test Mode Register LPM bits (TGL and later */ +#define ETR3_CLEAR_LPM_EVENTS			BIT(28) -const char *tgl_lpm_modes[] = { +const char *pmc_lpm_modes[] = {  	"S0i2.0",  	"S0i2.1",  	"S0i2.2", @@ -258,11 +276,15 @@ struct pmc_reg_map {  	const u32 ltr_ignore_max;  	const u32 pm_vric1_offset;  	/* Low Power Mode registers */ -	const char **lpm_modes; +	const int lpm_num_maps; +	const int lpm_res_counter_step_x2; +	const u32 lpm_sts_latch_en_offset;  	const u32 lpm_en_offset; +	const u32 lpm_priority_offset;  	const u32 lpm_residency_offset;  	const u32 lpm_status_offset;  	const u32 lpm_live_status_offset; +	const u32 etr3_offset;  };  /** @@ -278,6 +300,9 @@ struct pmc_reg_map {   * @check_counters:	On resume, check if counters are getting incremented   * @pc10_counter:	PC10 residency counter   * @s0ix_counter:	S0ix residency (step adjusted) + * @num_lpm_modes:	Count of enabled modes + * @lpm_en_modes:	Array of enabled modes from lowest to highest priority + * @lpm_req_regs:	List of substate requirements   *   * pmc_dev contains info about power management controller device.   */ @@ -292,6 +317,28 @@ struct pmc_dev {  	bool check_counters; /* Check for counter increments on resume */  	u64 pc10_counter;  	u64 s0ix_counter; +	int num_lpm_modes; +	int lpm_en_modes[LPM_MAX_NUM_MODES]; +	u32 *lpm_req_regs;  }; +#define pmc_for_each_mode(i, mode, pmcdev)		\ +	for (i = 0, mode = pmcdev->lpm_en_modes[i];	\ +	     i < pmcdev->num_lpm_modes;			\ +	     i++, mode = pmcdev->lpm_en_modes[i]) + +#define DEFINE_PMC_CORE_ATTR_WRITE(__name)				\ +static int __name ## _open(struct inode *inode, struct file *file)	\ +{									\ +	return single_open(file, __name ## _show, inode->i_private);	\ +}									\ +									\ +static const struct file_operations __name ## _fops = {			\ +	.owner		= THIS_MODULE,					\ +	.open		= __name ## _open,				\ +	.read		= seq_read,					\ +	.write		= __name ## _write,				\ +	.release	= single_release,				\ +} +  #endif /* PMC_CORE_H */ diff --git a/drivers/platform/x86/intel_pmt_class.c b/drivers/platform/x86/intel_pmt_class.c index c8939fba4509..c86ff15b1ed5 100644 --- a/drivers/platform/x86/intel_pmt_class.c +++ b/drivers/platform/x86/intel_pmt_class.c @@ -20,6 +20,28 @@  #define PMT_XA_LIMIT		XA_LIMIT(PMT_XA_START, PMT_XA_MAX)  /* + * Early implementations of PMT on client platforms have some + * differences from the server platforms (which use the Out Of Band + * Management Services Module OOBMSM). This list tracks those + * platforms as needed to handle those differences. Newer client + * platforms are expected to be fully compatible with server. + */ +static const struct pci_device_id pmt_telem_early_client_pci_ids[] = { +	{ PCI_VDEVICE(INTEL, 0x467d) }, /* ADL */ +	{ PCI_VDEVICE(INTEL, 0x490e) }, /* DG1 */ +	{ PCI_VDEVICE(INTEL, 0x9a0d) }, /* TGL */ +	{ } +}; + +bool intel_pmt_is_early_client_hw(struct device *dev) +{ +	struct pci_dev *parent = to_pci_dev(dev->parent); + +	return !!pci_match_id(pmt_telem_early_client_pci_ids, parent); +} +EXPORT_SYMBOL_GPL(intel_pmt_is_early_client_hw); + +/*   * sysfs   */  static ssize_t @@ -147,6 +169,30 @@ static int intel_pmt_populate_entry(struct intel_pmt_entry *entry,  		 * base address = end of discovery region + base offset  		 */  		entry->base_addr = disc_res->end + 1 + header->base_offset; + +		/* +		 * Some hardware use a different calculation for the base address +		 * when access_type == ACCESS_LOCAL. On the these systems +		 * ACCCESS_LOCAL refers to an address in the same BAR as the +		 * header but at a fixed offset. But as the header address was +		 * supplied to the driver, we don't know which BAR it was in. +		 * So search for the bar whose range includes the header address. +		 */ +		if (intel_pmt_is_early_client_hw(dev)) { +			int i; + +			entry->base_addr = 0; +			for (i = 0; i < 6; i++) +				if (disc_res->start >= pci_resource_start(pci_dev, i) && +				   (disc_res->start <= pci_resource_end(pci_dev, i))) { +					entry->base_addr = pci_resource_start(pci_dev, i) + +							   header->base_offset; +					break; +				} +			if (!entry->base_addr) +				return -EINVAL; +		} +  		break;  	case ACCESS_BARID:  		/* @@ -173,7 +219,7 @@ static int intel_pmt_dev_register(struct intel_pmt_entry *entry,  				  struct intel_pmt_namespace *ns,  				  struct device *parent)  { -	struct resource res; +	struct resource res = {0};  	struct device *dev;  	int ret; diff --git a/drivers/platform/x86/intel_pmt_class.h b/drivers/platform/x86/intel_pmt_class.h index de8f8139ba31..1337019c2873 100644 --- a/drivers/platform/x86/intel_pmt_class.h +++ b/drivers/platform/x86/intel_pmt_class.h @@ -44,6 +44,7 @@ struct intel_pmt_namespace {  				 struct device *dev);  }; +bool intel_pmt_is_early_client_hw(struct device *dev);  int intel_pmt_dev_create(struct intel_pmt_entry *entry,  			 struct intel_pmt_namespace *ns,  			 struct platform_device *pdev, int idx); diff --git a/drivers/platform/x86/intel_pmt_crashlog.c b/drivers/platform/x86/intel_pmt_crashlog.c index 97dd749c8290..92d315a16cfd 100644 --- a/drivers/platform/x86/intel_pmt_crashlog.c +++ b/drivers/platform/x86/intel_pmt_crashlog.c @@ -23,18 +23,17 @@  #define CRASH_TYPE_OOBMSM	1  /* Control Flags */ -#define CRASHLOG_FLAG_DISABLE		BIT(27) +#define CRASHLOG_FLAG_DISABLE		BIT(28)  /* - * Bits 28 and 29 control the state of bit 31. + * Bits 29 and 30 control the state of bit 31.   * - * Bit 28 will clear bit 31, if set, allowing a new crashlog to be captured. - * Bit 29 will immediately trigger a crashlog to be generated, setting bit 31. - * Bit 30 is read-only and reserved as 0. + * Bit 29 will clear bit 31, if set, allowing a new crashlog to be captured. + * Bit 30 will immediately trigger a crashlog to be generated, setting bit 31.   * Bit 31 is the read-only status with a 1 indicating log is complete.   */ -#define CRASHLOG_FLAG_TRIGGER_CLEAR	BIT(28) -#define CRASHLOG_FLAG_TRIGGER_EXECUTE	BIT(29) +#define CRASHLOG_FLAG_TRIGGER_CLEAR	BIT(29) +#define CRASHLOG_FLAG_TRIGGER_EXECUTE	BIT(30)  #define CRASHLOG_FLAG_TRIGGER_COMPLETE	BIT(31)  #define CRASHLOG_FLAG_TRIGGER_MASK	GENMASK(31, 28) diff --git a/drivers/platform/x86/intel_pmt_telemetry.c b/drivers/platform/x86/intel_pmt_telemetry.c index f8a87614efa4..9b95ef050457 100644 --- a/drivers/platform/x86/intel_pmt_telemetry.c +++ b/drivers/platform/x86/intel_pmt_telemetry.c @@ -34,26 +34,6 @@ struct pmt_telem_priv {  	struct intel_pmt_entry		entry[];  }; -/* - * Early implementations of PMT on client platforms have some - * differences from the server platforms (which use the Out Of Band - * Management Services Module OOBMSM). This list tracks those - * platforms as needed to handle those differences. Newer client - * platforms are expected to be fully compatible with server. - */ -static const struct pci_device_id pmt_telem_early_client_pci_ids[] = { -	{ PCI_VDEVICE(INTEL, 0x9a0d) }, /* TGL */ -	{ PCI_VDEVICE(INTEL, 0x467d) }, /* ADL */ -	{ } -}; - -static bool intel_pmt_is_early_client_hw(struct device *dev) -{ -	struct pci_dev *parent = to_pci_dev(dev->parent); - -	return !!pci_match_id(pmt_telem_early_client_pci_ids, parent); -} -  static bool pmt_telem_region_overlaps(struct intel_pmt_entry *entry,  				      struct device *dev)  { diff --git a/drivers/platform/x86/intel_speed_select_if/isst_if_mbox_pci.c b/drivers/platform/x86/intel_speed_select_if/isst_if_mbox_pci.c index a2a2d923e60c..df1fc6c719f3 100644 --- a/drivers/platform/x86/intel_speed_select_if/isst_if_mbox_pci.c +++ b/drivers/platform/x86/intel_speed_select_if/isst_if_mbox_pci.c @@ -21,12 +21,16 @@  #define PUNIT_MAILBOX_BUSY_BIT		31  /* - * The average time to complete some commands is about 40us. The current - * count is enough to satisfy 40us. But when the firmware is very busy, this - * causes timeout occasionally.  So increase to deal with some worst case - * scenarios. Most of the command still complete in few us. + * The average time to complete mailbox commands is less than 40us. Most of + * the commands complete in few micro seconds. But the same firmware handles + * requests from all power management features. + * We can create a scenario where we flood the firmware with requests then + * the mailbox response can be delayed for 100s of micro seconds. So define + * two timeouts. One for average case and one for long. + * If the firmware is taking more than average, just call cond_resched().   */ -#define OS_MAILBOX_RETRY_COUNT		100 +#define OS_MAILBOX_TIMEOUT_AVG_US	40 +#define OS_MAILBOX_TIMEOUT_MAX_US	1000  struct isst_if_device {  	struct mutex mutex; @@ -35,11 +39,13 @@ struct isst_if_device {  static int isst_if_mbox_cmd(struct pci_dev *pdev,  			    struct isst_if_mbox_cmd *mbox_cmd)  { -	u32 retries, data; +	s64 tm_delta = 0; +	ktime_t tm; +	u32 data;  	int ret;  	/* Poll for rb bit == 0 */ -	retries = OS_MAILBOX_RETRY_COUNT; +	tm = ktime_get();  	do {  		ret = pci_read_config_dword(pdev, PUNIT_MAILBOX_INTERFACE,  					    &data); @@ -48,11 +54,14 @@ static int isst_if_mbox_cmd(struct pci_dev *pdev,  		if (data & BIT_ULL(PUNIT_MAILBOX_BUSY_BIT)) {  			ret = -EBUSY; +			tm_delta = ktime_us_delta(ktime_get(), tm); +			if (tm_delta > OS_MAILBOX_TIMEOUT_AVG_US) +				cond_resched();  			continue;  		}  		ret = 0;  		break; -	} while (--retries); +	} while (tm_delta < OS_MAILBOX_TIMEOUT_MAX_US);  	if (ret)  		return ret; @@ -74,7 +83,8 @@ static int isst_if_mbox_cmd(struct pci_dev *pdev,  		return ret;  	/* Poll for rb bit == 0 */ -	retries = OS_MAILBOX_RETRY_COUNT; +	tm_delta = 0; +	tm = ktime_get();  	do {  		ret = pci_read_config_dword(pdev, PUNIT_MAILBOX_INTERFACE,  					    &data); @@ -83,6 +93,9 @@ static int isst_if_mbox_cmd(struct pci_dev *pdev,  		if (data & BIT_ULL(PUNIT_MAILBOX_BUSY_BIT)) {  			ret = -EBUSY; +			tm_delta = ktime_us_delta(ktime_get(), tm); +			if (tm_delta > OS_MAILBOX_TIMEOUT_AVG_US) +				cond_resched();  			continue;  		} @@ -96,7 +109,7 @@ static int isst_if_mbox_cmd(struct pci_dev *pdev,  		mbox_cmd->resp_data = data;  		ret = 0;  		break; -	} while (--retries); +	} while (tm_delta < OS_MAILBOX_TIMEOUT_MAX_US);  	return ret;  } diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c index dd900a76d8de..20145b539335 100644 --- a/drivers/platform/x86/lg-laptop.c +++ b/drivers/platform/x86/lg-laptop.c @@ -678,7 +678,7 @@ static int __init acpi_init(void)  	result = acpi_bus_register_driver(&acpi_driver);  	if (result < 0) { -		ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Error registering driver\n")); +		pr_debug("Error registering driver\n");  		return -ENODEV;  	} diff --git a/drivers/platform/x86/panasonic-laptop.c b/drivers/platform/x86/panasonic-laptop.c index 6388c3c705a6..d4f444401496 100644 --- a/drivers/platform/x86/panasonic-laptop.c +++ b/drivers/platform/x86/panasonic-laptop.c @@ -973,7 +973,7 @@ static int acpi_pcc_hotkey_add(struct acpi_device *device)  	pcc->mute = pcc->sinf[SINF_MUTE];  	pcc->ac_brightness = pcc->sinf[SINF_AC_CUR_BRIGHT];  	pcc->dc_brightness = pcc->sinf[SINF_DC_CUR_BRIGHT]; -	result = pcc->current_brightness = pcc->sinf[SINF_CUR_BRIGHT]; +	pcc->current_brightness = pcc->sinf[SINF_CUR_BRIGHT];  	/* add sysfs attributes */  	result = sysfs_create_group(&device->dev.kobj, &pcc_attr_group); diff --git a/drivers/platform/x86/pmc_atom.c b/drivers/platform/x86/pmc_atom.c index ca684ed760d1..a9d2a4b98e57 100644 --- a/drivers/platform/x86/pmc_atom.c +++ b/drivers/platform/x86/pmc_atom.c @@ -393,34 +393,10 @@ static const struct dmi_system_id critclk_systems[] = {  	},  	{  		/* pmc_plt_clk* - are used for ethernet controllers */ -		.ident = "Beckhoff CB3163", +		.ident = "Beckhoff Baytrail",  		.matches = {  			DMI_MATCH(DMI_SYS_VENDOR, "Beckhoff Automation"), -			DMI_MATCH(DMI_BOARD_NAME, "CB3163"), -		}, -	}, -	{ -		/* pmc_plt_clk* - are used for ethernet controllers */ -		.ident = "Beckhoff CB4063", -		.matches = { -			DMI_MATCH(DMI_SYS_VENDOR, "Beckhoff Automation"), -			DMI_MATCH(DMI_BOARD_NAME, "CB4063"), -		}, -	}, -	{ -		/* pmc_plt_clk* - are used for ethernet controllers */ -		.ident = "Beckhoff CB6263", -		.matches = { -			DMI_MATCH(DMI_SYS_VENDOR, "Beckhoff Automation"), -			DMI_MATCH(DMI_BOARD_NAME, "CB6263"), -		}, -	}, -	{ -		/* pmc_plt_clk* - are used for ethernet controllers */ -		.ident = "Beckhoff CB6363", -		.matches = { -			DMI_MATCH(DMI_SYS_VENDOR, "Beckhoff Automation"), -			DMI_MATCH(DMI_BOARD_NAME, "CB6363"), +			DMI_MATCH(DMI_PRODUCT_FAMILY, "CBxx63"),  		},  	},  	{ diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index b881044b31b0..dd60c9397d35 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -175,6 +175,12 @@ enum tpacpi_hkey_event_t {  						     or port replicator */  	TP_HKEY_EV_HOTPLUG_UNDOCK	= 0x4011, /* undocked from hotplug  						     dock or port replicator */ +	/* +	 * Thinkpad X1 Tablet series devices emit 0x4012 and 0x4013 +	 * when keyboard cover is attached, detached or folded onto the back +	 */ +	TP_HKEY_EV_KBD_COVER_ATTACH	= 0x4012, /* keyboard cover attached */ +	TP_HKEY_EV_KBD_COVER_DETACH	= 0x4013, /* keyboard cover detached or folded back */  	/* User-interface events */  	TP_HKEY_EV_LID_CLOSE		= 0x5001, /* laptop lid closed */ @@ -3991,6 +3997,23 @@ static bool hotkey_notify_dockevent(const u32 hkey,  		pr_info("undocked from hotplug port replicator\n");  		return true; +	/* +	 * Deliberately ignore attaching and detaching the keybord cover to avoid +	 * duplicates from intel-vbtn, which already emits SW_TABLET_MODE events +	 * to userspace. +	 * +	 * Please refer to the following thread for more information and a preliminary +	 * implementation using the GTOP ("Get Tablet OPtions") interface that could be +	 * extended to other attachment options of the ThinkPad X1 Tablet series, such as +	 * the Pico cartridge dock module: +	 * https://lore.kernel.org/platform-driver-x86/38cb8265-1e30-d547-9e12-b4ae290be737@a-kobel.de/ +	 */ +	case TP_HKEY_EV_KBD_COVER_ATTACH: +	case TP_HKEY_EV_KBD_COVER_DETACH: +		*send_acpi_ev = false; +		*ignore_acpi_ev = true; +		return true; +  	default:  		return false;  	} @@ -4081,13 +4104,19 @@ static bool hotkey_notify_6xxx(const u32 hkey,  	case TP_HKEY_EV_KEY_NUMLOCK:  	case TP_HKEY_EV_KEY_FN: -	case TP_HKEY_EV_KEY_FN_ESC:  		/* key press events, we just ignore them as long as the EC  		 * is still reporting them in the normal keyboard stream */  		*send_acpi_ev = false;  		*ignore_acpi_ev = true;  		return true; +	case TP_HKEY_EV_KEY_FN_ESC: +		/* Get the media key status to force the status LED to update */ +		acpi_evalf(hkey_handle, NULL, "GMKS", "v"); +		*send_acpi_ev = false; +		*ignore_acpi_ev = true; +		return true; +  	case TP_HKEY_EV_TABLET_CHANGED:  		tpacpi_input_send_tabletsw();  		hotkey_tablet_mode_notify_change(); @@ -6254,6 +6283,7 @@ enum thermal_access_mode {  enum { /* TPACPI_THERMAL_TPEC_* */  	TP_EC_THERMAL_TMP0 = 0x78,	/* ACPI EC regs TMP 0..7 */  	TP_EC_THERMAL_TMP8 = 0xC0,	/* ACPI EC regs TMP 8..15 */ +	TP_EC_FUNCREV      = 0xEF,      /* ACPI EC Functional revision */  	TP_EC_THERMAL_TMP_NA = -128,	/* ACPI EC sensor not available */  	TPACPI_THERMAL_SENSOR_NA = -128000, /* Sensor not available */ @@ -6266,6 +6296,8 @@ struct ibm_thermal_sensors_struct {  };  static enum thermal_access_mode thermal_read_mode; +static const struct attribute_group *thermal_attr_group; +static bool thermal_use_labels;  /* idx is zero-based */  static int thermal_get_sensor(int idx, s32 *value) @@ -6448,11 +6480,33 @@ static const struct attribute_group thermal_temp_input8_group = {  #undef THERMAL_SENSOR_ATTR_TEMP  #undef THERMAL_ATTRS +static ssize_t temp1_label_show(struct device *dev, struct device_attribute *attr, char *buf) +{ +	return sysfs_emit(buf, "CPU\n"); +} +static DEVICE_ATTR_RO(temp1_label); + +static ssize_t temp2_label_show(struct device *dev, struct device_attribute *attr, char *buf) +{ +	return sysfs_emit(buf, "GPU\n"); +} +static DEVICE_ATTR_RO(temp2_label); + +static struct attribute *temp_label_attributes[] = { +	&dev_attr_temp1_label.attr, +	&dev_attr_temp2_label.attr, +	NULL +}; + +static const struct attribute_group temp_label_attr_group = { +	.attrs = temp_label_attributes, +}; +  /* --------------------------------------------------------------------- */  static int __init thermal_init(struct ibm_init_struct *iibm)  { -	u8 t, ta1, ta2; +	u8 t, ta1, ta2, ver = 0;  	int i;  	int acpi_tmp7;  	int res; @@ -6467,7 +6521,14 @@ static int __init thermal_init(struct ibm_init_struct *iibm)  		 * 0x78-0x7F, 0xC0-0xC7.  Registers return 0x00 for  		 * non-implemented, thermal sensors return 0x80 when  		 * not available +		 * The above rule is unfortunately flawed. This has been seen with +		 * 0xC2 (power supply ID) causing thermal control problems. +		 * The EC version can be determined by offset 0xEF and at least for +		 * version 3 the Lenovo firmware team confirmed that registers 0xC0-0xC7 +		 * are not thermal registers.  		 */ +		if (!acpi_ec_read(TP_EC_FUNCREV, &ver)) +			pr_warn("Thinkpad ACPI EC unable to access EC version\n");  		ta1 = ta2 = 0;  		for (i = 0; i < 8; i++) { @@ -6477,11 +6538,13 @@ static int __init thermal_init(struct ibm_init_struct *iibm)  				ta1 = 0;  				break;  			} -			if (acpi_ec_read(TP_EC_THERMAL_TMP8 + i, &t)) { -				ta2 |= t; -			} else { -				ta1 = 0; -				break; +			if (ver < 3) { +				if (acpi_ec_read(TP_EC_THERMAL_TMP8 + i, &t)) { +					ta2 |= t; +				} else { +					ta1 = 0; +					break; +				}  			}  		}  		if (ta1 == 0) { @@ -6494,9 +6557,14 @@ static int __init thermal_init(struct ibm_init_struct *iibm)  				thermal_read_mode = TPACPI_THERMAL_NONE;  			}  		} else { -			thermal_read_mode = -			    (ta2 != 0) ? -			    TPACPI_THERMAL_TPEC_16 : TPACPI_THERMAL_TPEC_8; +			if (ver >= 3) { +				thermal_read_mode = TPACPI_THERMAL_TPEC_8; +				thermal_use_labels = true; +			} else { +				thermal_read_mode = +					(ta2 != 0) ? +					TPACPI_THERMAL_TPEC_16 : TPACPI_THERMAL_TPEC_8; +			}  		}  	} else if (acpi_tmp7) {  		if (tpacpi_is_ibm() && @@ -6518,44 +6586,40 @@ static int __init thermal_init(struct ibm_init_struct *iibm)  	switch (thermal_read_mode) {  	case TPACPI_THERMAL_TPEC_16: -		res = sysfs_create_group(&tpacpi_hwmon->kobj, -				&thermal_temp_input16_group); -		if (res) -			return res; +		thermal_attr_group = &thermal_temp_input16_group;  		break;  	case TPACPI_THERMAL_TPEC_8:  	case TPACPI_THERMAL_ACPI_TMP07:  	case TPACPI_THERMAL_ACPI_UPDT: -		res = sysfs_create_group(&tpacpi_hwmon->kobj, -				&thermal_temp_input8_group); -		if (res) -			return res; +		thermal_attr_group = &thermal_temp_input8_group;  		break;  	case TPACPI_THERMAL_NONE:  	default:  		return 1;  	} +	res = sysfs_create_group(&tpacpi_hwmon->kobj, thermal_attr_group); +	if (res) +		return res; + +	if (thermal_use_labels) { +		res = sysfs_create_group(&tpacpi_hwmon->kobj, &temp_label_attr_group); +		if (res) { +			sysfs_remove_group(&tpacpi_hwmon->kobj, thermal_attr_group); +			return res; +		} +	} +  	return 0;  }  static void thermal_exit(void)  { -	switch (thermal_read_mode) { -	case TPACPI_THERMAL_TPEC_16: -		sysfs_remove_group(&tpacpi_hwmon->kobj, -				   &thermal_temp_input16_group); -		break; -	case TPACPI_THERMAL_TPEC_8: -	case TPACPI_THERMAL_ACPI_TMP07: -	case TPACPI_THERMAL_ACPI_UPDT: -		sysfs_remove_group(&tpacpi_hwmon->kobj, -				   &thermal_temp_input8_group); -		break; -	case TPACPI_THERMAL_NONE: -	default: -		break; -	} +	if (thermal_attr_group) +		sysfs_remove_group(&tpacpi_hwmon->kobj, thermal_attr_group); + +	if (thermal_use_labels) +		sysfs_remove_group(&tpacpi_hwmon->kobj, &temp_label_attr_group);  }  static int thermal_read(struct seq_file *m) @@ -9845,6 +9909,11 @@ static struct ibm_struct lcdshadow_driver_data = {   * Thinkpad sensor interfaces   */ +#define DYTC_CMD_QUERY        0 /* To get DYTC status - enable/revision */ +#define DYTC_QUERY_ENABLE_BIT 8  /* Bit        8 - 0 = disabled, 1 = enabled */ +#define DYTC_QUERY_SUBREV_BIT 16 /* Bits 16 - 27 - sub revision */ +#define DYTC_QUERY_REV_BIT    28 /* Bits 28 - 31 - revision */ +  #define DYTC_CMD_GET          2 /* To get current IC function and mode */  #define DYTC_GET_LAPMODE_BIT 17 /* Set when in lapmode */ @@ -9855,6 +9924,7 @@ static bool has_palmsensor;  static bool has_lapsensor;  static bool palm_state;  static bool lap_state; +static int dytc_version;  static int dytc_command(int command, int *output)  { @@ -9869,6 +9939,33 @@ static int dytc_command(int command, int *output)  	return 0;  } +static int dytc_get_version(void) +{ +	int err, output; + +	/* Check if we've been called before - and just return cached value */ +	if (dytc_version) +		return dytc_version; + +	/* Otherwise query DYTC and extract version information */ +	err = dytc_command(DYTC_CMD_QUERY, &output); +	/* +	 * If support isn't available (ENODEV) then don't return an error +	 * and don't create the sysfs group +	 */ +	if (err == -ENODEV) +		return 0; +	/* For all other errors we can flag the failure */ +	if (err) +		return err; + +	/* Check DYTC is enabled and supports mode setting */ +	if (output & BIT(DYTC_QUERY_ENABLE_BIT)) +		dytc_version = (output >> DYTC_QUERY_REV_BIT) & 0xF; + +	return 0; +} +  static int lapsensor_get(bool *present, bool *state)  {  	int output, err; @@ -9974,7 +10071,18 @@ static int tpacpi_proxsensor_init(struct ibm_init_struct *iibm)  		if (err)  			return err;  	} -	if (has_lapsensor) { + +	/* Check if we know the DYTC version, if we don't then get it */ +	if (!dytc_version) { +		err = dytc_get_version(); +		if (err) +			return err; +	} +	/* +	 * Platforms before DYTC version 5 claim to have a lap sensor, but it doesn't work, so we +	 * ignore them +	 */ +	if (has_lapsensor && (dytc_version >= 5)) {  		err = sysfs_create_file(&tpacpi_pdev->dev.kobj, &dev_attr_dytc_lapmode.attr);  		if (err)  			return err; @@ -9999,14 +10107,10 @@ static struct ibm_struct proxsensor_driver_data = {   * DYTC Platform Profile interface   */ -#define DYTC_CMD_QUERY        0 /* To get DYTC status - enable/revision */  #define DYTC_CMD_SET          1 /* To enable/disable IC function mode */ +#define DYTC_CMD_MMC_GET      8 /* To get current MMC function and mode */  #define DYTC_CMD_RESET    0x1ff /* To reset back to default */ -#define DYTC_QUERY_ENABLE_BIT 8  /* Bit        8 - 0 = disabled, 1 = enabled */ -#define DYTC_QUERY_SUBREV_BIT 16 /* Bits 16 - 27 - sub revision */ -#define DYTC_QUERY_REV_BIT    28 /* Bits 28 - 31 - revision */ -  #define DYTC_GET_FUNCTION_BIT 8  /* Bits  8-11 - function setting */  #define DYTC_GET_MODE_BIT     12 /* Bits 12-15 - mode setting */ @@ -10021,6 +10125,10 @@ static struct ibm_struct proxsensor_driver_data = {  #define DYTC_MODE_PERFORM     2  /* High power mode aka performance */  #define DYTC_MODE_LOWPOWER    3  /* Low power mode */  #define DYTC_MODE_BALANCE   0xF  /* Default mode aka balanced */ +#define DYTC_MODE_MMC_BALANCE 0  /* Default mode from MMC_GET, aka balanced */ + +#define DYTC_ERR_MASK       0xF  /* Bits 0-3 in cmd result are the error result */ +#define DYTC_ERR_SUCCESS      1  /* CMD completed successful */  #define DYTC_SET_COMMAND(function, mode, on) \  	(DYTC_CMD_SET | (function) << DYTC_SET_FUNCTION_BIT | \ @@ -10035,6 +10143,7 @@ static bool dytc_profile_available;  static enum platform_profile_option dytc_current_profile;  static atomic_t dytc_ignore_event = ATOMIC_INIT(0);  static DEFINE_MUTEX(dytc_mutex); +static bool dytc_mmc_get_available;  static int convert_dytc_to_profile(int dytcmode, enum platform_profile_option *profile)  { @@ -10043,6 +10152,7 @@ static int convert_dytc_to_profile(int dytcmode, enum platform_profile_option *p  		*profile = PLATFORM_PROFILE_LOW_POWER;  		break;  	case DYTC_MODE_BALANCE: +	case DYTC_MODE_MMC_BALANCE:  		*profile =  PLATFORM_PROFILE_BALANCED;  		break;  	case DYTC_MODE_PERFORM: @@ -10120,7 +10230,6 @@ static int dytc_cql_command(int command, int *output)  		if (err)  			return err;  	} -  	return cmd_err;  } @@ -10142,8 +10251,13 @@ static int dytc_profile_set(struct platform_profile_handler *pprof,  		return err;  	if (profile == PLATFORM_PROFILE_BALANCED) { -		/* To get back to balanced mode we just issue a reset command */ -		err = dytc_command(DYTC_CMD_RESET, &output); +		/* +		 * To get back to balanced mode we need to issue a reset command. +		 * Note we still need to disable CQL mode before hand and re-enable +		 * it afterwards, otherwise dytc_lapmode gets reset to 0 and stays +		 * stuck at 0 for aprox. 30 minutes. +		 */ +		err = dytc_cql_command(DYTC_CMD_RESET, &output);  		if (err)  			goto unlock;  	} else { @@ -10172,7 +10286,10 @@ static void dytc_profile_refresh(void)  	int perfmode;  	mutex_lock(&dytc_mutex); -	err = dytc_cql_command(DYTC_CMD_GET, &output); +	if (dytc_mmc_get_available) +		err = dytc_command(DYTC_CMD_MMC_GET, &output); +	else +		err = dytc_cql_command(DYTC_CMD_GET, &output);  	mutex_unlock(&dytc_mutex);  	if (err)  		return; @@ -10211,28 +10328,38 @@ static int tpacpi_dytc_profile_init(struct ibm_init_struct *iibm)  	if (err)  		return err; +	/* Check if we know the DYTC version, if we don't then get it */ +	if (!dytc_version) { +		err = dytc_get_version(); +		if (err) +			return err; +	}  	/* Check DYTC is enabled and supports mode setting */ -	if (output & BIT(DYTC_QUERY_ENABLE_BIT)) { -		/* Only DYTC v5.0 and later has this feature. */ -		int dytc_version; - -		dytc_version = (output >> DYTC_QUERY_REV_BIT) & 0xF; -		if (dytc_version >= 5) { -			dbg_printk(TPACPI_DBG_INIT, -				   "DYTC version %d: thermal mode available\n", dytc_version); -			/* Create platform_profile structure and register */ -			err = platform_profile_register(&dytc_profile); -			/* -			 * If for some reason platform_profiles aren't enabled -			 * don't quit terminally. -			 */ -			if (err) -				return 0; - -			dytc_profile_available = true; -			/* Ensure initial values are correct */ -			dytc_profile_refresh(); +	if (dytc_version >= 5) { +		dbg_printk(TPACPI_DBG_INIT, +				"DYTC version %d: thermal mode available\n", dytc_version); +		/* +		 * Check if MMC_GET functionality available +		 * Version > 6 and return success from MMC_GET command +		 */ +		dytc_mmc_get_available = false; +		if (dytc_version >= 6) { +			err = dytc_command(DYTC_CMD_MMC_GET, &output); +			if (!err && ((output & DYTC_ERR_MASK) == DYTC_ERR_SUCCESS)) +				dytc_mmc_get_available = true;  		} +		/* Create platform_profile structure and register */ +		err = platform_profile_register(&dytc_profile); +		/* +		 * If for some reason platform_profiles aren't enabled +		 * don't quit terminally. +		 */ +		if (err) +			return 0; + +		dytc_profile_available = true; +		/* Ensure initial values are correct */ +		dytc_profile_refresh();  	}  	return 0;  } @@ -10423,6 +10550,111 @@ static struct ibm_struct kbdlang_driver_data = {  	.exit = kbdlang_exit,  }; +/************************************************************************* + * DPRC(Dynamic Power Reduction Control) subdriver, for the Lenovo WWAN + * and WLAN feature. + */ +#define DPRC_GET_WWAN_ANTENNA_TYPE      0x40000 +#define DPRC_WWAN_ANTENNA_TYPE_A_BIT    BIT(4) +#define DPRC_WWAN_ANTENNA_TYPE_B_BIT    BIT(8) +static bool has_antennatype; +static int wwan_antennatype; + +static int dprc_command(int command, int *output) +{ +	acpi_handle dprc_handle; + +	if (ACPI_FAILURE(acpi_get_handle(hkey_handle, "DPRC", &dprc_handle))) { +		/* Platform doesn't support DPRC */ +		return -ENODEV; +	} + +	if (!acpi_evalf(dprc_handle, output, NULL, "dd", command)) +		return -EIO; + +	/* +	 * METHOD_ERR gets returned on devices where few commands are not supported +	 * for example command to get WWAN Antenna type command is not supported on +	 * some devices. +	 */ +	if (*output & METHOD_ERR) +		return -ENODEV; + +	return 0; +} + +static int get_wwan_antenna(int *wwan_antennatype) +{ +	int output, err; + +	/* Get current Antenna type */ +	err = dprc_command(DPRC_GET_WWAN_ANTENNA_TYPE, &output); +	if (err) +		return err; + +	if (output & DPRC_WWAN_ANTENNA_TYPE_A_BIT) +		*wwan_antennatype = 1; +	else if (output & DPRC_WWAN_ANTENNA_TYPE_B_BIT) +		*wwan_antennatype = 2; +	else +		return -ENODEV; + +	return 0; +} + +/* sysfs wwan antenna type entry */ +static ssize_t wwan_antenna_type_show(struct device *dev, +					struct device_attribute *attr, +					char *buf) +{ +	switch (wwan_antennatype) { +	case 1: +		return sysfs_emit(buf, "type a\n"); +	case 2: +		return sysfs_emit(buf, "type b\n"); +	default: +		return -ENODATA; +	} +} +static DEVICE_ATTR_RO(wwan_antenna_type); + +static int tpacpi_dprc_init(struct ibm_init_struct *iibm) +{ +	int wwanantenna_err, err; + +	wwanantenna_err = get_wwan_antenna(&wwan_antennatype); +	/* +	 * If support isn't available (ENODEV) then quit, but don't +	 * return an error. +	 */ +	if (wwanantenna_err == -ENODEV) +		return 0; + +	/* if there was an error return it */ +	if (wwanantenna_err && (wwanantenna_err != -ENODEV)) +		return wwanantenna_err; +	else if (!wwanantenna_err) +		has_antennatype = true; + +	if (has_antennatype) { +		err = sysfs_create_file(&tpacpi_pdev->dev.kobj, &dev_attr_wwan_antenna_type.attr); +		if (err) +			return err; +	} +	return 0; +} + +static void dprc_exit(void) +{ +	if (has_antennatype) +		sysfs_remove_file(&tpacpi_pdev->dev.kobj, &dev_attr_wwan_antenna_type.attr); +} + +static struct ibm_struct dprc_driver_data = { +	.name = "dprc", +	.exit = dprc_exit, +}; +  /****************************************************************************   ****************************************************************************   * @@ -10927,6 +11159,10 @@ static struct ibm_init_struct ibms_init[] __initdata = {  		.init = tpacpi_kbdlang_init,  		.data = &kbdlang_driver_data,  	}, +	{ +		.init = tpacpi_dprc_init, +		.data = &dprc_driver_data, +	},  };  static int __init set_ibm_param(const char *val, const struct kernel_param *kp) diff --git a/drivers/platform/x86/touchscreen_dmi.c b/drivers/platform/x86/touchscreen_dmi.c index c44a6e8dceb8..90fe4f8f3c2c 100644 --- a/drivers/platform/x86/touchscreen_dmi.c +++ b/drivers/platform/x86/touchscreen_dmi.c @@ -715,6 +715,32 @@ static const struct ts_dmi_data techbite_arc_11_6_data = {  	.properties	= techbite_arc_11_6_props,  }; +static const struct property_entry teclast_tbook11_props[] = { +	PROPERTY_ENTRY_U32("touchscreen-min-x", 8), +	PROPERTY_ENTRY_U32("touchscreen-min-y", 14), +	PROPERTY_ENTRY_U32("touchscreen-size-x", 1916), +	PROPERTY_ENTRY_U32("touchscreen-size-y", 1264), +	PROPERTY_ENTRY_BOOL("touchscreen-inverted-y"), +	PROPERTY_ENTRY_STRING("firmware-name", "gsl3692-teclast-tbook11.fw"), +	PROPERTY_ENTRY_U32("silead,max-fingers", 10), +	PROPERTY_ENTRY_BOOL("silead,home-button"), +	{ } +}; + +static const struct ts_dmi_data teclast_tbook11_data = { +	.embedded_fw = { +		.name	= "silead/gsl3692-teclast-tbook11.fw", +		.prefix = { 0xf0, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00 }, +		.length	= 43560, +		.sha256	= { 0x9d, 0xb0, 0x3d, 0xf1, 0x00, 0x3c, 0xb5, 0x25, +			    0x62, 0x8a, 0xa0, 0x93, 0x4b, 0xe0, 0x4e, 0x75, +			    0xd1, 0x27, 0xb1, 0x65, 0x3c, 0xba, 0xa5, 0x0f, +			    0xcd, 0xb4, 0xbe, 0x00, 0xbb, 0xf6, 0x43, 0x29 }, +	}, +	.acpi_name	= "MSSL1680:00", +	.properties	= teclast_tbook11_props, +}; +  static const struct property_entry teclast_x3_plus_props[] = {  	PROPERTY_ENTRY_U32("touchscreen-size-x", 1980),  	PROPERTY_ENTRY_U32("touchscreen-size-y", 1500), @@ -1244,6 +1270,15 @@ const struct dmi_system_id touchscreen_dmi_table[] = {  		},  	},  	{ +		/* Teclast Tbook 11 */ +		.driver_data = (void *)&teclast_tbook11_data, +		.matches = { +			DMI_MATCH(DMI_SYS_VENDOR, "TECLAST"), +			DMI_MATCH(DMI_PRODUCT_NAME, "TbooK 11"), +			DMI_MATCH(DMI_PRODUCT_SKU, "E5A6_A1"), +		}, +	}, +	{  		/* Teclast X3 Plus */  		.driver_data = (void *)&teclast_x3_plus_data,  		.matches = { @@ -1355,7 +1390,7 @@ static void ts_dmi_add_props(struct i2c_client *client)  	if (has_acpi_companion(dev) &&  	    !strncmp(ts_data->acpi_name, client->name, I2C_NAME_SIZE)) { -		error = device_add_properties(dev, ts_data->properties); +		error = device_create_managed_software_node(dev, ts_data->properties, NULL);  		if (error)  			dev_err(dev, "failed to add properties: %d\n", error);  	} diff --git a/drivers/platform/x86/wmi-bmof.c b/drivers/platform/x86/wmi-bmof.c index 66b434d6307f..80137afb9753 100644 --- a/drivers/platform/x86/wmi-bmof.c +++ b/drivers/platform/x86/wmi-bmof.c @@ -86,13 +86,12 @@ static int wmi_bmof_probe(struct wmi_device *wdev, const void *context)  	return ret;  } -static int wmi_bmof_remove(struct wmi_device *wdev) +static void wmi_bmof_remove(struct wmi_device *wdev)  {  	struct bmof_priv *priv = dev_get_drvdata(&wdev->dev);  	sysfs_remove_bin_file(&wdev->dev.kobj, &priv->bmof_bin_attr);  	kfree(priv->bmofdata); -	return 0;  }  static const struct wmi_device_id wmi_bmof_id_table[] = { diff --git a/drivers/platform/x86/wmi.c b/drivers/platform/x86/wmi.c index c669676ea8e8..62e0d56a3332 100644 --- a/drivers/platform/x86/wmi.c +++ b/drivers/platform/x86/wmi.c @@ -32,7 +32,6 @@  #include <linux/fs.h>  #include <uapi/linux/wmi.h> -ACPI_MODULE_NAME("wmi");  MODULE_AUTHOR("Carlos Corbacho");  MODULE_DESCRIPTION("ACPI-WMI Mapping Driver");  MODULE_LICENSE("GPL"); @@ -986,7 +985,6 @@ static int wmi_dev_remove(struct device *dev)  	struct wmi_block *wblock = dev_to_wblock(dev);  	struct wmi_driver *wdriver =  		container_of(dev->driver, struct wmi_driver, driver); -	int ret = 0;  	if (wdriver->filter_callback) {  		misc_deregister(&wblock->char_dev); @@ -995,12 +993,12 @@ static int wmi_dev_remove(struct device *dev)  	}  	if (wdriver->remove) -		ret = wdriver->remove(dev_to_wdev(dev)); +		wdriver->remove(dev_to_wdev(dev));  	if (ACPI_FAILURE(wmi_method_enable(wblock, 0)))  		dev_warn(dev, "failed to disable device\n"); -	return ret; +	return 0;  }  static struct class wmi_bus_class = { diff --git a/drivers/platform/x86/xo15-ebook.c b/drivers/platform/x86/xo15-ebook.c index 8337c99d2ce2..97440462aa25 100644 --- a/drivers/platform/x86/xo15-ebook.c +++ b/drivers/platform/x86/xo15-ebook.c @@ -26,8 +26,6 @@  #define XO15_EBOOK_HID			"XO15EBK"  #define XO15_EBOOK_DEVICE_NAME		"EBook Switch" -ACPI_MODULE_NAME(MODULE_NAME); -  MODULE_DESCRIPTION("OLPC XO-1.5 ebook switch driver");  MODULE_LICENSE("GPL"); @@ -66,8 +64,8 @@ static void ebook_switch_notify(struct acpi_device *device, u32 event)  		ebook_send_state(device);  		break;  	default: -		ACPI_DEBUG_PRINT((ACPI_DB_INFO, -				  "Unsupported event [0x%x]\n", event)); +		acpi_handle_debug(device->handle, +				  "Unsupported event [0x%x]\n", event);  		break;  	}  } | 
