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_register(struct platform_profile_handler *pprof) 140 { 141 int err; 142 143 mutex_lock(&profile_lock); 144 /* We can only have one active profile */ 145 if (cur_profile) { 146 mutex_unlock(&profile_lock); 147 return -EEXIST; 148 } 149 150 /* Sanity check the profile handler field are set */ 151 if (!pprof || bitmap_empty(pprof->choices, PLATFORM_PROFILE_LAST) || 152 !pprof->profile_set || !pprof->profile_get) { 153 mutex_unlock(&profile_lock); 154 return -EINVAL; 155 } 156 157 err = sysfs_create_group(acpi_kobj, &platform_profile_group); 158 if (err) { 159 mutex_unlock(&profile_lock); 160 return err; 161 } 162 163 cur_profile = pprof; 164 mutex_unlock(&profile_lock); 165 return 0; 166 } 167 EXPORT_SYMBOL_GPL(platform_profile_register); 168 169 int platform_profile_remove(void) 170 { 171 sysfs_remove_group(acpi_kobj, &platform_profile_group); 172 173 mutex_lock(&profile_lock); 174 cur_profile = NULL; 175 mutex_unlock(&profile_lock); 176 return 0; 177 } 178 EXPORT_SYMBOL_GPL(platform_profile_remove); 179 180 MODULE_AUTHOR("Mark Pearson <markpearson@lenovo.com>"); 181 MODULE_LICENSE("GPL"); 182