-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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>
- Loading branch information
Mark Pearson
authored and
Rafael J. Wysocki
committed
Dec 30, 2020
1 parent
8e0cbf3
commit a2ff95e
Showing
4 changed files
with
238 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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_*/ |