Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
at v6.13-rc3 221 lines 5.1 kB view raw
1// SPDX-License-Identifier: GPL-2.0-or-later 2 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 else 45 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 NULL 125}; 126 127static const struct attribute_group platform_profile_group = { 128 .attrs = platform_profile_attrs 129}; 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");