forked from mirrors/linux
		
	ACPI: platform: Add platform profile support
This is the initial implementation of the platform-profile feature. It provides the details discussed and outlined in the sysfs-platform_profile document. Many modern systems have the ability to modify the operating profile to control aspects like fan speed, temperature and power levels. This module provides a common sysfs interface that platform modules can register against to control their individual profile options. Signed-off-by: Mark Pearson <markpearson@lenovo.com> Reviewed-by: Hans de Goede <hdegoede@redhat.com> [ rjw: Use full words in enum values names ] Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
This commit is contained in:
		
							parent
							
								
									8e0cbf3563
								
							
						
					
					
						commit
						a2ff95e018
					
				
					 4 changed files with 238 additions and 0 deletions
				
			
		|  | @ -326,6 +326,23 @@ config ACPI_THERMAL | ||||||
| 	  To compile this driver as a module, choose M here: | 	  To compile this driver as a module, choose M here: | ||||||
| 	  the module will be called thermal. | 	  the module will be called thermal. | ||||||
| 
 | 
 | ||||||
|  | config ACPI_PLATFORM_PROFILE | ||||||
|  | 	tristate "ACPI Platform Profile Driver" | ||||||
|  | 	default m | ||||||
|  | 	help | ||||||
|  | 	  This driver adds support for platform-profiles on platforms that | ||||||
|  | 	  support it. | ||||||
|  | 
 | ||||||
|  | 	  Platform-profiles can be used to control the platform behaviour. For | ||||||
|  | 	  example whether to operate in a lower power mode, in a higher | ||||||
|  | 	  power performance mode or between the two. | ||||||
|  | 
 | ||||||
|  | 	  This driver provides the sysfs interface and is used as the registration | ||||||
|  | 	  point for platform specific drivers. | ||||||
|  | 
 | ||||||
|  | 	  Which profiles are supported is determined on a per-platform basis and | ||||||
|  | 	  should be obtained from the platform specific driver. | ||||||
|  | 
 | ||||||
| config ACPI_CUSTOM_DSDT_FILE | config ACPI_CUSTOM_DSDT_FILE | ||||||
| 	string "Custom DSDT Table file to include" | 	string "Custom DSDT Table file to include" | ||||||
| 	default "" | 	default "" | ||||||
|  |  | ||||||
|  | @ -79,6 +79,7 @@ obj-$(CONFIG_ACPI_PCI_SLOT)	+= pci_slot.o | ||||||
| obj-$(CONFIG_ACPI_PROCESSOR)	+= processor.o | obj-$(CONFIG_ACPI_PROCESSOR)	+= processor.o | ||||||
| obj-$(CONFIG_ACPI)		+= container.o | obj-$(CONFIG_ACPI)		+= container.o | ||||||
| obj-$(CONFIG_ACPI_THERMAL)	+= thermal.o | obj-$(CONFIG_ACPI_THERMAL)	+= thermal.o | ||||||
|  | obj-$(CONFIG_ACPI_PLATFORM_PROFILE) 	+= platform_profile.o | ||||||
| obj-$(CONFIG_ACPI_NFIT)		+= nfit/ | obj-$(CONFIG_ACPI_NFIT)		+= nfit/ | ||||||
| obj-$(CONFIG_ACPI_NUMA)		+= numa/ | obj-$(CONFIG_ACPI_NUMA)		+= numa/ | ||||||
| obj-$(CONFIG_ACPI)		+= acpi_memhotplug.o | obj-$(CONFIG_ACPI)		+= acpi_memhotplug.o | ||||||
|  |  | ||||||
							
								
								
									
										181
									
								
								drivers/acpi/platform_profile.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										181
									
								
								drivers/acpi/platform_profile.c
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,181 @@ | ||||||
|  | // SPDX-License-Identifier: GPL-2.0-or-later
 | ||||||
|  | 
 | ||||||
|  | /* Platform profile sysfs interface */ | ||||||
|  | 
 | ||||||
|  | #include <linux/acpi.h> | ||||||
|  | #include <linux/bits.h> | ||||||
|  | #include <linux/init.h> | ||||||
|  | #include <linux/mutex.h> | ||||||
|  | #include <linux/platform_profile.h> | ||||||
|  | #include <linux/sysfs.h> | ||||||
|  | 
 | ||||||
|  | static const struct platform_profile_handler *cur_profile; | ||||||
|  | static DEFINE_MUTEX(profile_lock); | ||||||
|  | 
 | ||||||
|  | static const char * const profile_names[] = { | ||||||
|  | 	[PLATFORM_PROFILE_LOW_POWER] = "low-power", | ||||||
|  | 	[PLATFORM_PROFILE_COOL] = "cool", | ||||||
|  | 	[PLATFORM_PROFILE_QUIET] = "quiet", | ||||||
|  | 	[PLATFORM_PROFILE_BALANCED] = "balanced", | ||||||
|  | 	[PLATFORM_PROFILE_PERFORMANCE] = "performance", | ||||||
|  | }; | ||||||
|  | static_assert(ARRAY_SIZE(profile_names) == PLATFORM_PROFILE_LAST); | ||||||
|  | 
 | ||||||
|  | static ssize_t platform_profile_choices_show(struct device *dev, | ||||||
|  | 					struct device_attribute *attr, | ||||||
|  | 					char *buf) | ||||||
|  | { | ||||||
|  | 	int len = 0; | ||||||
|  | 	int err, i; | ||||||
|  | 
 | ||||||
|  | 	err = mutex_lock_interruptible(&profile_lock); | ||||||
|  | 	if (err) | ||||||
|  | 		return err; | ||||||
|  | 
 | ||||||
|  | 	if (!cur_profile) { | ||||||
|  | 		mutex_unlock(&profile_lock); | ||||||
|  | 		return -ENODEV; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for_each_set_bit(i, cur_profile->choices, PLATFORM_PROFILE_LAST) { | ||||||
|  | 		if (len == 0) | ||||||
|  | 			len += sysfs_emit_at(buf, len, "%s", profile_names[i]); | ||||||
|  | 		else | ||||||
|  | 			len += sysfs_emit_at(buf, len, " %s", profile_names[i]); | ||||||
|  | 	} | ||||||
|  | 	len += sysfs_emit_at(buf, len, "\n"); | ||||||
|  | 	mutex_unlock(&profile_lock); | ||||||
|  | 	return len; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static ssize_t platform_profile_show(struct device *dev, | ||||||
|  | 					struct device_attribute *attr, | ||||||
|  | 					char *buf) | ||||||
|  | { | ||||||
|  | 	enum platform_profile_option profile = PLATFORM_PROFILE_BALANCED; | ||||||
|  | 	int err; | ||||||
|  | 
 | ||||||
|  | 	err = mutex_lock_interruptible(&profile_lock); | ||||||
|  | 	if (err) | ||||||
|  | 		return err; | ||||||
|  | 
 | ||||||
|  | 	if (!cur_profile) { | ||||||
|  | 		mutex_unlock(&profile_lock); | ||||||
|  | 		return -ENODEV; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	err = cur_profile->profile_get(&profile); | ||||||
|  | 	mutex_unlock(&profile_lock); | ||||||
|  | 	if (err) | ||||||
|  | 		return err; | ||||||
|  | 
 | ||||||
|  | 	/* Check that profile is valid index */ | ||||||
|  | 	if (WARN_ON((profile < 0) || (profile >= ARRAY_SIZE(profile_names)))) | ||||||
|  | 		return -EIO; | ||||||
|  | 
 | ||||||
|  | 	return sysfs_emit(buf, "%s\n", profile_names[profile]); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static ssize_t platform_profile_store(struct device *dev, | ||||||
|  | 			    struct device_attribute *attr, | ||||||
|  | 			    const char *buf, size_t count) | ||||||
|  | { | ||||||
|  | 	int err, i; | ||||||
|  | 
 | ||||||
|  | 	err = mutex_lock_interruptible(&profile_lock); | ||||||
|  | 	if (err) | ||||||
|  | 		return err; | ||||||
|  | 
 | ||||||
|  | 	if (!cur_profile) { | ||||||
|  | 		mutex_unlock(&profile_lock); | ||||||
|  | 		return -ENODEV; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/* Scan for a matching profile */ | ||||||
|  | 	i = sysfs_match_string(profile_names, buf); | ||||||
|  | 	if (i < 0) { | ||||||
|  | 		mutex_unlock(&profile_lock); | ||||||
|  | 		return -EINVAL; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/* Check that platform supports this profile choice */ | ||||||
|  | 	if (!test_bit(i, cur_profile->choices)) { | ||||||
|  | 		mutex_unlock(&profile_lock); | ||||||
|  | 		return -EOPNOTSUPP; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	err = cur_profile->profile_set(i); | ||||||
|  | 	mutex_unlock(&profile_lock); | ||||||
|  | 	if (err) | ||||||
|  | 		return err; | ||||||
|  | 	return count; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static DEVICE_ATTR_RO(platform_profile_choices); | ||||||
|  | static DEVICE_ATTR_RW(platform_profile); | ||||||
|  | 
 | ||||||
|  | static struct attribute *platform_profile_attrs[] = { | ||||||
|  | 	&dev_attr_platform_profile_choices.attr, | ||||||
|  | 	&dev_attr_platform_profile.attr, | ||||||
|  | 	NULL | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static const struct attribute_group platform_profile_group = { | ||||||
|  | 	.attrs = platform_profile_attrs | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | void platform_profile_notify(void) | ||||||
|  | { | ||||||
|  | 	if (!cur_profile) | ||||||
|  | 		return; | ||||||
|  | 	sysfs_notify(acpi_kobj, NULL, "platform_profile"); | ||||||
|  | } | ||||||
|  | EXPORT_SYMBOL_GPL(platform_profile_notify); | ||||||
|  | 
 | ||||||
|  | int platform_profile_register(const struct platform_profile_handler *pprof) | ||||||
|  | { | ||||||
|  | 	int err; | ||||||
|  | 
 | ||||||
|  | 	mutex_lock(&profile_lock); | ||||||
|  | 	/* We can only have one active profile */ | ||||||
|  | 	if (cur_profile) { | ||||||
|  | 		mutex_unlock(&profile_lock); | ||||||
|  | 		return -EEXIST; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/* Sanity check the profile handler field are set */ | ||||||
|  | 	if (!pprof || bitmap_empty(pprof->choices, PLATFORM_PROFILE_LAST) || | ||||||
|  | 		!pprof->profile_set || !pprof->profile_get) { | ||||||
|  | 		mutex_unlock(&profile_lock); | ||||||
|  | 		return -EINVAL; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	err = sysfs_create_group(acpi_kobj, &platform_profile_group); | ||||||
|  | 	if (err) { | ||||||
|  | 		mutex_unlock(&profile_lock); | ||||||
|  | 		return err; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	cur_profile = pprof; | ||||||
|  | 	mutex_unlock(&profile_lock); | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | EXPORT_SYMBOL_GPL(platform_profile_register); | ||||||
|  | 
 | ||||||
|  | int platform_profile_remove(void) | ||||||
|  | { | ||||||
|  | 	mutex_lock(&profile_lock); | ||||||
|  | 	if (!cur_profile) { | ||||||
|  | 		mutex_unlock(&profile_lock); | ||||||
|  | 		return -ENODEV; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	sysfs_remove_group(acpi_kobj, &platform_profile_group); | ||||||
|  | 	cur_profile = NULL; | ||||||
|  | 	mutex_unlock(&profile_lock); | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | EXPORT_SYMBOL_GPL(platform_profile_remove); | ||||||
|  | 
 | ||||||
|  | MODULE_AUTHOR("Mark Pearson <markpearson@lenovo.com>"); | ||||||
|  | MODULE_LICENSE("GPL"); | ||||||
							
								
								
									
										39
									
								
								include/linux/platform_profile.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								include/linux/platform_profile.h
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,39 @@ | ||||||
|  | /* SPDX-License-Identifier: GPL-2.0-or-later */ | ||||||
|  | /*
 | ||||||
|  |  * Platform profile sysfs interface | ||||||
|  |  * | ||||||
|  |  * See Documentation/ABI/testing/sysfs-platform_profile.rst for more | ||||||
|  |  * information. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #ifndef _PLATFORM_PROFILE_H_ | ||||||
|  | #define _PLATFORM_PROFILE_H_ | ||||||
|  | 
 | ||||||
|  | #include <linux/bitops.h> | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  |  * If more options are added please update profile_names | ||||||
|  |  * array in platform-profile.c and sysfs-platform-profile.rst | ||||||
|  |  * documentation. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | enum platform_profile_option { | ||||||
|  | 	PLATFORM_PROFILE_LOW_POWER, | ||||||
|  | 	PLATFORM_PROFILE_COOL, | ||||||
|  | 	PLATFORM_PROFILE_QUIET, | ||||||
|  | 	PLATFORM_PROFILE_BALANCED, | ||||||
|  | 	PLATFORM_PROFILE_PERFORMANCE, | ||||||
|  | 	PLATFORM_PROFILE_LAST, /*must always be last */ | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | struct platform_profile_handler { | ||||||
|  | 	unsigned long choices[BITS_TO_LONGS(PLATFORM_PROFILE_LAST)]; | ||||||
|  | 	int (*profile_get)(enum platform_profile_option *profile); | ||||||
|  | 	int (*profile_set)(enum platform_profile_option profile); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | int platform_profile_register(const struct platform_profile_handler *pprof); | ||||||
|  | int platform_profile_remove(void); | ||||||
|  | void platform_profile_notify(void); | ||||||
|  | 
 | ||||||
|  | #endif  /*_PLATFORM_PROFILE_H_*/ | ||||||
		Loading…
	
		Reference in a new issue
	
	 Mark Pearson
						Mark Pearson