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 12 static struct platform_profile_handler *cur_profile; 13 static DEFINE_MUTEX(profile_lock); 14 15 static 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 }; 23 static_assert(ARRAY_SIZE(profile_names) == PLATFORM_PROFILE_LAST); 24 25 static 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 52 static 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 80 static 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 118 static DEVICE_ATTR_RO(platform_profile_choices); 119 static DEVICE_ATTR_RW(platform_profile); 120 121 static struct attribute *platform_profile_attrs[] = { 122 &dev_attr_platform_profile_choices.attr, 123 &dev_attr_platform_profile.attr, 124 NULL 125 }; 126 127 static const struct attribute_group platform_profile_group = { 128 .attrs = platform_profile_attrs 129 }; 130 131 void platform_profile_notify(void) 132 { 133 if (!cur_profile) 134 return; 135 sysfs_notify(acpi_kobj, NULL, "platform_profile"); 136 } 137 EXPORT_SYMBOL_GPL(platform_profile_notify); 138 139 int 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 } 176 EXPORT_SYMBOL_GPL(platform_profile_cycle); 177 178 int 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 } 206 EXPORT_SYMBOL_GPL(platform_profile_register); 207 208 int 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 } 217 EXPORT_SYMBOL_GPL(platform_profile_remove); 218 219 MODULE_AUTHOR("Mark Pearson <markpearson@lenovo.com>"); 220 MODULE_DESCRIPTION("ACPI platform profile sysfs interface"); 221 MODULE_LICENSE("GPL"); 222