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: | ||||
| 	  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 | ||||
| 	string "Custom DSDT Table file to include" | ||||
| 	default "" | ||||
|  |  | |||
|  | @ -79,6 +79,7 @@ obj-$(CONFIG_ACPI_PCI_SLOT)	+= pci_slot.o | |||
| obj-$(CONFIG_ACPI_PROCESSOR)	+= processor.o | ||||
| obj-$(CONFIG_ACPI)		+= container.o | ||||
| obj-$(CONFIG_ACPI_THERMAL)	+= thermal.o | ||||
| obj-$(CONFIG_ACPI_PLATFORM_PROFILE) 	+= platform_profile.o | ||||
| obj-$(CONFIG_ACPI_NFIT)		+= nfit/ | ||||
| obj-$(CONFIG_ACPI_NUMA)		+= numa/ | ||||
| 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