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 mutex_unlock(&profile_lock); 110 if (err) 111 return err; 112 return count; 113 } 114 115 static DEVICE_ATTR_RO(platform_profile_choices); 116 static DEVICE_ATTR_RW(platform_profile); 117 118 static struct attribute *platform_profile_attrs[] = { 119 &dev_attr_platform_profile_choices.attr, 120 &dev_attr_platform_profile.attr, 121 NULL 122 }; 123 124 static const struct attribute_group platform_profile_group = { 125 .attrs = platform_profile_attrs 126 }; 127 128 void platform_profile_notify(void) 129 { 130 if (!cur_profile) 131 return; 132 sysfs_notify(acpi_kobj, NULL, "platform_profile"); 133 } 134 EXPORT_SYMBOL_GPL(platform_profile_notify); 135 136 int platform_profile_register(struct platform_profile_handler *pprof) 137 { 138 int err; 139 140 mutex_lock(&profile_lock); 141 /* We can only have one active profile */ 142 if (cur_profile) { 143 mutex_unlock(&profile_lock); 144 return -EEXIST; 145 } 146 147 /* Sanity check the profile handler field are set */ 148 if (!pprof || bitmap_empty(pprof->choices, PLATFORM_PROFILE_LAST) || 149 !pprof->profile_set || !pprof->profile_get) { 150 mutex_unlock(&profile_lock); 151 return -EINVAL; 152 } 153 154 err = sysfs_create_group(acpi_kobj, &platform_profile_group); 155 if (err) { 156 mutex_unlock(&profile_lock); 157 return err; 158 } 159 160 cur_profile = pprof; 161 mutex_unlock(&profile_lock); 162 return 0; 163 } 164 EXPORT_SYMBOL_GPL(platform_profile_register); 165 166 int platform_profile_remove(void) 167 { 168 sysfs_remove_group(acpi_kobj, &platform_profile_group); 169 170 mutex_lock(&profile_lock); 171 cur_profile = NULL; 172 mutex_unlock(&profile_lock); 173 return 0; 174 } 175 EXPORT_SYMBOL_GPL(platform_profile_remove); 176 177 MODULE_AUTHOR("Mark Pearson <markpearson@lenovo.com>"); 178 MODULE_LICENSE("GPL"); 179