222 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2 3/* Platform profile sysfs interface */4 5#include <linux/acpi.h>6#include <linux/bits.h>7#include <linux/init.h>8#include <linux/mutex.h>9#include <linux/platform_profile.h>10#include <linux/sysfs.h>11 12static struct platform_profile_handler *cur_profile;13static DEFINE_MUTEX(profile_lock);14 15static const char * const profile_names[] = {16 [PLATFORM_PROFILE_LOW_POWER] = "low-power",17 [PLATFORM_PROFILE_COOL] = "cool",18 [PLATFORM_PROFILE_QUIET] = "quiet",19 [PLATFORM_PROFILE_BALANCED] = "balanced",20 [PLATFORM_PROFILE_BALANCED_PERFORMANCE] = "balanced-performance",21 [PLATFORM_PROFILE_PERFORMANCE] = "performance",22};23static_assert(ARRAY_SIZE(profile_names) == PLATFORM_PROFILE_LAST);24 25static ssize_t platform_profile_choices_show(struct device *dev,26 struct device_attribute *attr,27 char *buf)28{29 int len = 0;30 int err, i;31 32 err = mutex_lock_interruptible(&profile_lock);33 if (err)34 return err;35 36 if (!cur_profile) {37 mutex_unlock(&profile_lock);38 return -ENODEV;39 }40 41 for_each_set_bit(i, cur_profile->choices, PLATFORM_PROFILE_LAST) {42 if (len == 0)43 len += sysfs_emit_at(buf, len, "%s", profile_names[i]);44 else45 len += sysfs_emit_at(buf, len, " %s", profile_names[i]);46 }47 len += sysfs_emit_at(buf, len, "\n");48 mutex_unlock(&profile_lock);49 return len;50}51 52static ssize_t platform_profile_show(struct device *dev,53 struct device_attribute *attr,54 char *buf)55{56 enum platform_profile_option profile = PLATFORM_PROFILE_BALANCED;57 int err;58 59 err = mutex_lock_interruptible(&profile_lock);60 if (err)61 return err;62 63 if (!cur_profile) {64 mutex_unlock(&profile_lock);65 return -ENODEV;66 }67 68 err = cur_profile->profile_get(cur_profile, &profile);69 mutex_unlock(&profile_lock);70 if (err)71 return err;72 73 /* Check that profile is valid index */74 if (WARN_ON((profile < 0) || (profile >= ARRAY_SIZE(profile_names))))75 return -EIO;76 77 return sysfs_emit(buf, "%s\n", profile_names[profile]);78}79 80static ssize_t platform_profile_store(struct device *dev,81 struct device_attribute *attr,82 const char *buf, size_t count)83{84 int err, i;85 86 err = mutex_lock_interruptible(&profile_lock);87 if (err)88 return err;89 90 if (!cur_profile) {91 mutex_unlock(&profile_lock);92 return -ENODEV;93 }94 95 /* Scan for a matching profile */96 i = sysfs_match_string(profile_names, buf);97 if (i < 0) {98 mutex_unlock(&profile_lock);99 return -EINVAL;100 }101 102 /* Check that platform supports this profile choice */103 if (!test_bit(i, cur_profile->choices)) {104 mutex_unlock(&profile_lock);105 return -EOPNOTSUPP;106 }107 108 err = cur_profile->profile_set(cur_profile, i);109 if (!err)110 sysfs_notify(acpi_kobj, NULL, "platform_profile");111 112 mutex_unlock(&profile_lock);113 if (err)114 return err;115 return count;116}117 118static DEVICE_ATTR_RO(platform_profile_choices);119static DEVICE_ATTR_RW(platform_profile);120 121static struct attribute *platform_profile_attrs[] = {122 &dev_attr_platform_profile_choices.attr,123 &dev_attr_platform_profile.attr,124 NULL125};126 127static const struct attribute_group platform_profile_group = {128 .attrs = platform_profile_attrs129};130 131void platform_profile_notify(void)132{133 if (!cur_profile)134 return;135 sysfs_notify(acpi_kobj, NULL, "platform_profile");136}137EXPORT_SYMBOL_GPL(platform_profile_notify);138 139int platform_profile_cycle(void)140{141 enum platform_profile_option profile;142 enum platform_profile_option next;143 int err;144 145 err = mutex_lock_interruptible(&profile_lock);146 if (err)147 return err;148 149 if (!cur_profile) {150 mutex_unlock(&profile_lock);151 return -ENODEV;152 }153 154 err = cur_profile->profile_get(cur_profile, &profile);155 if (err) {156 mutex_unlock(&profile_lock);157 return err;158 }159 160 next = find_next_bit_wrap(cur_profile->choices, PLATFORM_PROFILE_LAST,161 profile + 1);162 163 if (WARN_ON(next == PLATFORM_PROFILE_LAST)) {164 mutex_unlock(&profile_lock);165 return -EINVAL;166 }167 168 err = cur_profile->profile_set(cur_profile, next);169 mutex_unlock(&profile_lock);170 171 if (!err)172 sysfs_notify(acpi_kobj, NULL, "platform_profile");173 174 return err;175}176EXPORT_SYMBOL_GPL(platform_profile_cycle);177 178int platform_profile_register(struct platform_profile_handler *pprof)179{180 int err;181 182 mutex_lock(&profile_lock);183 /* We can only have one active profile */184 if (cur_profile) {185 mutex_unlock(&profile_lock);186 return -EEXIST;187 }188 189 /* Sanity check the profile handler field are set */190 if (!pprof || bitmap_empty(pprof->choices, PLATFORM_PROFILE_LAST) ||191 !pprof->profile_set || !pprof->profile_get) {192 mutex_unlock(&profile_lock);193 return -EINVAL;194 }195 196 err = sysfs_create_group(acpi_kobj, &platform_profile_group);197 if (err) {198 mutex_unlock(&profile_lock);199 return err;200 }201 202 cur_profile = pprof;203 mutex_unlock(&profile_lock);204 return 0;205}206EXPORT_SYMBOL_GPL(platform_profile_register);207 208int platform_profile_remove(void)209{210 sysfs_remove_group(acpi_kobj, &platform_profile_group);211 212 mutex_lock(&profile_lock);213 cur_profile = NULL;214 mutex_unlock(&profile_lock);215 return 0;216}217EXPORT_SYMBOL_GPL(platform_profile_remove);218 219MODULE_AUTHOR("Mark Pearson <markpearson@lenovo.com>");220MODULE_DESCRIPTION("ACPI platform profile sysfs interface");221MODULE_LICENSE("GPL");222