// SPDX-License-Identifier: GPL-2.0-or-later /* Platform profile sysfs interface */ #include #include #include #include #include #include static struct platform_profile_handler *cur_profile; static DEFINE_MUTEX(profile_lock); static const char * const profile_names[] = { [PLATFORM_PROFILE_LOW_POWER] = "low-power", [PLATFORM_PROFILE_COOL] = "cool", [PLATFORM_PROFILE_QUIET] = "quiet", [PLATFORM_PROFILE_BALANCED] = "balanced", [PLATFORM_PROFILE_PERFORMANCE] = "performance", }; static_assert(ARRAY_SIZE(profile_names) == PLATFORM_PROFILE_LAST); static ssize_t platform_profile_choices_show(struct device *dev, struct device_attribute *attr, char *buf) { int len = 0; int err, i; err = mutex_lock_interruptible(&profile_lock); if (err) return err; if (!cur_profile) { mutex_unlock(&profile_lock); return -ENODEV; } for_each_set_bit(i, cur_profile->choices, PLATFORM_PROFILE_LAST) { if (len == 0) len += sysfs_emit_at(buf, len, "%s", profile_names[i]); else len += sysfs_emit_at(buf, len, " %s", profile_names[i]); } len += sysfs_emit_at(buf, len, "\n"); mutex_unlock(&profile_lock); return len; } static ssize_t platform_profile_show(struct device *dev, struct device_attribute *attr, char *buf) { enum platform_profile_option profile = PLATFORM_PROFILE_BALANCED; int err; err = mutex_lock_interruptible(&profile_lock); if (err) return err; if (!cur_profile) { mutex_unlock(&profile_lock); return -ENODEV; } err = cur_profile->profile_get(cur_profile, &profile); mutex_unlock(&profile_lock); if (err) return err; /* Check that profile is valid index */ if (WARN_ON((profile < 0) || (profile >= ARRAY_SIZE(profile_names)))) return -EIO; return sysfs_emit(buf, "%s\n", profile_names[profile]); } static ssize_t platform_profile_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int err, i; err = mutex_lock_interruptible(&profile_lock); if (err) return err; if (!cur_profile) { mutex_unlock(&profile_lock); return -ENODEV; } /* Scan for a matching profile */ i = sysfs_match_string(profile_names, buf); if (i < 0) { mutex_unlock(&profile_lock); return -EINVAL; } /* Check that platform supports this profile choice */ if (!test_bit(i, cur_profile->choices)) { mutex_unlock(&profile_lock); return -EOPNOTSUPP; } err = cur_profile->profile_set(cur_profile, i); mutex_unlock(&profile_lock); if (err) return err; return count; } static DEVICE_ATTR_RO(platform_profile_choices); static DEVICE_ATTR_RW(platform_profile); static struct attribute *platform_profile_attrs[] = { &dev_attr_platform_profile_choices.attr, &dev_attr_platform_profile.attr, NULL }; static const struct attribute_group platform_profile_group = { .attrs = platform_profile_attrs }; void platform_profile_notify(void) { if (!cur_profile) return; sysfs_notify(acpi_kobj, NULL, "platform_profile"); } EXPORT_SYMBOL_GPL(platform_profile_notify); int platform_profile_register(struct platform_profile_handler *pprof) { int err; mutex_lock(&profile_lock); /* We can only have one active profile */ if (cur_profile) { mutex_unlock(&profile_lock); return -EEXIST; } /* Sanity check the profile handler field are set */ if (!pprof || bitmap_empty(pprof->choices, PLATFORM_PROFILE_LAST) || !pprof->profile_set || !pprof->profile_get) { mutex_unlock(&profile_lock); return -EINVAL; } err = sysfs_create_group(acpi_kobj, &platform_profile_group); if (err) { mutex_unlock(&profile_lock); return err; } cur_profile = pprof; mutex_unlock(&profile_lock); return 0; } EXPORT_SYMBOL_GPL(platform_profile_register); int platform_profile_remove(void) { mutex_lock(&profile_lock); if (!cur_profile) { mutex_unlock(&profile_lock); return -ENODEV; } sysfs_remove_group(acpi_kobj, &platform_profile_group); cur_profile = NULL; mutex_unlock(&profile_lock); return 0; } EXPORT_SYMBOL_GPL(platform_profile_remove); MODULE_AUTHOR("Mark Pearson "); MODULE_LICENSE("GPL");