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_PERFORMANCE] = "performance", 21 }; 22 static_assert(ARRAY_SIZE(profile_names) == PLATFORM_PROFILE_LAST); 23 24 static ssize_t platform_profile_choices_show(struct device *dev, 25 struct device_attribute *attr, 26 char *buf) 27 { 28 int len = 0; 29 int err, i; 30 31 err = mutex_lock_interruptible(&profile_lock); 32 if (err) 33 return err; 34 35 if (!cur_profile) { 36 mutex_unlock(&profile_lock); 37 return -ENODEV; 38 } 39 40 for_each_set_bit(i, cur_profile->choices, PLATFORM_PROFILE_LAST) { 41 if (len == 0) 42 len += sysfs_emit_at(buf, len, "%s", profile_names[i]); 43 else 44 len += sysfs_emit_at(buf, len, " %s", profile_names[i]); 45 } 46 len += sysfs_emit_at(buf, len, "\n"); 47 mutex_unlock(&profile_lock); 48 return len; 49 } 50 51 static ssize_t platform_profile_show(struct device *dev, 52 struct device_attribute *attr, 53 char *buf) 54 { 55 enum platform_profile_option profile = PLATFORM_PROFILE_BALANCED; 56 int err; 57 58 err = mutex_lock_interruptible(&profile_lock); 59 if (err) 60 return err; 61 62 if (!cur_profile) { 63 mutex_unlock(&profile_lock); 64 return -ENODEV; 65 } 66 67 err = cur_profile->profile_get(cur_profile, &profile); 68 mutex_unlock(&profile_lock); 69 if (err) 70 return err; 71 72 /* Check that profile is valid index */ 73 if (WARN_ON((profile < 0) || (profile >= ARRAY_SIZE(profile_names)))) 74 return -EIO; 75 76 return sysfs_emit(buf, "%s\n", profile_names[profile]); 77 } 78 79 static ssize_t platform_profile_store(struct device *dev, 80 struct device_attribute *attr, 81 const char *buf, size_t count) 82 { 83 int err, i; 84 85 err = mutex_lock_interruptible(&profile_lock); 86 if (err) 87 return err; 88 89 if (!cur_profile) { 90 mutex_unlock(&profile_lock); 91 return -ENODEV; 92 } 93 94 /* Scan for a matching profile */ 95 i = sysfs_match_string(profile_names, buf); 96 if (i < 0) { 97 mutex_unlock(&profile_lock); 98 return -EINVAL; 99 } 100 101 /* Check that platform supports this profile choice */ 102 if (!test_bit(i, cur_profile->choices)) { 103 mutex_unlock(&profile_lock); 104 return -EOPNOTSUPP; 105 } 106 107 err = cur_profile->profile_set(cur_profile, i); 108 mutex_unlock(&profile_lock); 109 if (err) 110 return err; 111 return count; 112 } 113 114 static DEVICE_ATTR_RO(platform_profile_choices); 115 static DEVICE_ATTR_RW(platform_profile); 116 117 static struct attribute *platform_profile_attrs[] = { 118 &dev_attr_platform_profile_choices.attr, 119 &dev_attr_platform_profile.attr, 120 NULL 121 }; 122 123 static const struct attribute_group platform_profile_group = { 124 .attrs = platform_profile_attrs 125 }; 126 127 void platform_profile_notify(void) 128 { 129 if (!cur_profile) 130 return; 131 sysfs_notify(acpi_kobj, NULL, "platform_profile"); 132 } 133 EXPORT_SYMBOL_GPL(platform_profile_notify); 134 135 int platform_profile_register(struct platform_profile_handler *pprof) 136 { 137 int err; 138 139 mutex_lock(&profile_lock); 140 /* We can only have one active profile */ 141 if (cur_profile) { 142 mutex_unlock(&profile_lock); 143 return -EEXIST; 144 } 145 146 /* Sanity check the profile handler field are set */ 147 if (!pprof || bitmap_empty(pprof->choices, PLATFORM_PROFILE_LAST) || 148 !pprof->profile_set || !pprof->profile_get) { 149 mutex_unlock(&profile_lock); 150 return -EINVAL; 151 } 152 153 err = sysfs_create_group(acpi_kobj, &platform_profile_group); 154 if (err) { 155 mutex_unlock(&profile_lock); 156 return err; 157 } 158 159 cur_profile = pprof; 160 mutex_unlock(&profile_lock); 161 return 0; 162 } 163 EXPORT_SYMBOL_GPL(platform_profile_register); 164 165 int platform_profile_remove(void) 166 { 167 sysfs_remove_group(acpi_kobj, &platform_profile_group); 168 169 mutex_lock(&profile_lock); 170 cur_profile = NULL; 171 mutex_unlock(&profile_lock); 172 return 0; 173 } 174 EXPORT_SYMBOL_GPL(platform_profile_remove); 175 176 MODULE_AUTHOR("Mark Pearson <markpearson@lenovo.com>"); 177 MODULE_LICENSE("GPL"); 178