1a2ff95e0SMark Pearson // SPDX-License-Identifier: GPL-2.0-or-later
2a2ff95e0SMark Pearson
3a2ff95e0SMark Pearson /* Platform profile sysfs interface */
4a2ff95e0SMark Pearson
5a2ff95e0SMark Pearson #include <linux/acpi.h>
6a2ff95e0SMark Pearson #include <linux/bits.h>
7a2ff95e0SMark Pearson #include <linux/init.h>
8a2ff95e0SMark Pearson #include <linux/mutex.h>
9a2ff95e0SMark Pearson #include <linux/platform_profile.h>
10a2ff95e0SMark Pearson #include <linux/sysfs.h>
11a2ff95e0SMark Pearson
129d56653dSJiaxun Yang static struct platform_profile_handler *cur_profile;
13a2ff95e0SMark Pearson static DEFINE_MUTEX(profile_lock);
14a2ff95e0SMark Pearson
15a2ff95e0SMark Pearson static const char * const profile_names[] = {
16a2ff95e0SMark Pearson [PLATFORM_PROFILE_LOW_POWER] = "low-power",
17a2ff95e0SMark Pearson [PLATFORM_PROFILE_COOL] = "cool",
18a2ff95e0SMark Pearson [PLATFORM_PROFILE_QUIET] = "quiet",
19a2ff95e0SMark Pearson [PLATFORM_PROFILE_BALANCED] = "balanced",
206c0b5e3fSMaximilian Luz [PLATFORM_PROFILE_BALANCED_PERFORMANCE] = "balanced-performance",
21a2ff95e0SMark Pearson [PLATFORM_PROFILE_PERFORMANCE] = "performance",
22a2ff95e0SMark Pearson };
23a2ff95e0SMark Pearson static_assert(ARRAY_SIZE(profile_names) == PLATFORM_PROFILE_LAST);
24a2ff95e0SMark Pearson
platform_profile_choices_show(struct device * dev,struct device_attribute * attr,char * buf)25a2ff95e0SMark Pearson static ssize_t platform_profile_choices_show(struct device *dev,
26a2ff95e0SMark Pearson struct device_attribute *attr,
27a2ff95e0SMark Pearson char *buf)
28a2ff95e0SMark Pearson {
29a2ff95e0SMark Pearson int len = 0;
30a2ff95e0SMark Pearson int err, i;
31a2ff95e0SMark Pearson
32a2ff95e0SMark Pearson err = mutex_lock_interruptible(&profile_lock);
33a2ff95e0SMark Pearson if (err)
34a2ff95e0SMark Pearson return err;
35a2ff95e0SMark Pearson
36a2ff95e0SMark Pearson if (!cur_profile) {
37a2ff95e0SMark Pearson mutex_unlock(&profile_lock);
38a2ff95e0SMark Pearson return -ENODEV;
39a2ff95e0SMark Pearson }
40a2ff95e0SMark Pearson
41a2ff95e0SMark Pearson for_each_set_bit(i, cur_profile->choices, PLATFORM_PROFILE_LAST) {
42a2ff95e0SMark Pearson if (len == 0)
43a2ff95e0SMark Pearson len += sysfs_emit_at(buf, len, "%s", profile_names[i]);
44a2ff95e0SMark Pearson else
45a2ff95e0SMark Pearson len += sysfs_emit_at(buf, len, " %s", profile_names[i]);
46a2ff95e0SMark Pearson }
47a2ff95e0SMark Pearson len += sysfs_emit_at(buf, len, "\n");
48a2ff95e0SMark Pearson mutex_unlock(&profile_lock);
49a2ff95e0SMark Pearson return len;
50a2ff95e0SMark Pearson }
51a2ff95e0SMark Pearson
platform_profile_show(struct device * dev,struct device_attribute * attr,char * buf)52a2ff95e0SMark Pearson static ssize_t platform_profile_show(struct device *dev,
53a2ff95e0SMark Pearson struct device_attribute *attr,
54a2ff95e0SMark Pearson char *buf)
55a2ff95e0SMark Pearson {
56a2ff95e0SMark Pearson enum platform_profile_option profile = PLATFORM_PROFILE_BALANCED;
57a2ff95e0SMark Pearson int err;
58a2ff95e0SMark Pearson
59a2ff95e0SMark Pearson err = mutex_lock_interruptible(&profile_lock);
60a2ff95e0SMark Pearson if (err)
61a2ff95e0SMark Pearson return err;
62a2ff95e0SMark Pearson
63a2ff95e0SMark Pearson if (!cur_profile) {
64a2ff95e0SMark Pearson mutex_unlock(&profile_lock);
65a2ff95e0SMark Pearson return -ENODEV;
66a2ff95e0SMark Pearson }
67a2ff95e0SMark Pearson
6884f9017cSJiaxun Yang err = cur_profile->profile_get(cur_profile, &profile);
69a2ff95e0SMark Pearson mutex_unlock(&profile_lock);
70a2ff95e0SMark Pearson if (err)
71a2ff95e0SMark Pearson return err;
72a2ff95e0SMark Pearson
73a2ff95e0SMark Pearson /* Check that profile is valid index */
74a2ff95e0SMark Pearson if (WARN_ON((profile < 0) || (profile >= ARRAY_SIZE(profile_names))))
75a2ff95e0SMark Pearson return -EIO;
76a2ff95e0SMark Pearson
77a2ff95e0SMark Pearson return sysfs_emit(buf, "%s\n", profile_names[profile]);
78a2ff95e0SMark Pearson }
79a2ff95e0SMark Pearson
platform_profile_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)80a2ff95e0SMark Pearson static ssize_t platform_profile_store(struct device *dev,
81a2ff95e0SMark Pearson struct device_attribute *attr,
82a2ff95e0SMark Pearson const char *buf, size_t count)
83a2ff95e0SMark Pearson {
84a2ff95e0SMark Pearson int err, i;
85a2ff95e0SMark Pearson
86a2ff95e0SMark Pearson err = mutex_lock_interruptible(&profile_lock);
87a2ff95e0SMark Pearson if (err)
88a2ff95e0SMark Pearson return err;
89a2ff95e0SMark Pearson
90a2ff95e0SMark Pearson if (!cur_profile) {
91a2ff95e0SMark Pearson mutex_unlock(&profile_lock);
92a2ff95e0SMark Pearson return -ENODEV;
93a2ff95e0SMark Pearson }
94a2ff95e0SMark Pearson
95a2ff95e0SMark Pearson /* Scan for a matching profile */
96a2ff95e0SMark Pearson i = sysfs_match_string(profile_names, buf);
97a2ff95e0SMark Pearson if (i < 0) {
98a2ff95e0SMark Pearson mutex_unlock(&profile_lock);
99a2ff95e0SMark Pearson return -EINVAL;
100a2ff95e0SMark Pearson }
101a2ff95e0SMark Pearson
102a2ff95e0SMark Pearson /* Check that platform supports this profile choice */
103a2ff95e0SMark Pearson if (!test_bit(i, cur_profile->choices)) {
104a2ff95e0SMark Pearson mutex_unlock(&profile_lock);
105a2ff95e0SMark Pearson return -EOPNOTSUPP;
106a2ff95e0SMark Pearson }
107a2ff95e0SMark Pearson
10884f9017cSJiaxun Yang err = cur_profile->profile_set(cur_profile, i);
109*b25d5a1cSHans de Goede if (!err)
110*b25d5a1cSHans de Goede sysfs_notify(acpi_kobj, NULL, "platform_profile");
111*b25d5a1cSHans de Goede
112a2ff95e0SMark Pearson mutex_unlock(&profile_lock);
113a2ff95e0SMark Pearson if (err)
114a2ff95e0SMark Pearson return err;
115a2ff95e0SMark Pearson return count;
116a2ff95e0SMark Pearson }
117a2ff95e0SMark Pearson
118a2ff95e0SMark Pearson static DEVICE_ATTR_RO(platform_profile_choices);
119a2ff95e0SMark Pearson static DEVICE_ATTR_RW(platform_profile);
120a2ff95e0SMark Pearson
121a2ff95e0SMark Pearson static struct attribute *platform_profile_attrs[] = {
122a2ff95e0SMark Pearson &dev_attr_platform_profile_choices.attr,
123a2ff95e0SMark Pearson &dev_attr_platform_profile.attr,
124a2ff95e0SMark Pearson NULL
125a2ff95e0SMark Pearson };
126a2ff95e0SMark Pearson
127a2ff95e0SMark Pearson static const struct attribute_group platform_profile_group = {
128a2ff95e0SMark Pearson .attrs = platform_profile_attrs
129a2ff95e0SMark Pearson };
130a2ff95e0SMark Pearson
platform_profile_notify(void)131a2ff95e0SMark Pearson void platform_profile_notify(void)
132a2ff95e0SMark Pearson {
133a2ff95e0SMark Pearson if (!cur_profile)
134a2ff95e0SMark Pearson return;
135a2ff95e0SMark Pearson sysfs_notify(acpi_kobj, NULL, "platform_profile");
136a2ff95e0SMark Pearson }
137a2ff95e0SMark Pearson EXPORT_SYMBOL_GPL(platform_profile_notify);
138a2ff95e0SMark Pearson
platform_profile_register(struct platform_profile_handler * pprof)1399d56653dSJiaxun Yang int platform_profile_register(struct platform_profile_handler *pprof)
140a2ff95e0SMark Pearson {
141a2ff95e0SMark Pearson int err;
142a2ff95e0SMark Pearson
143a2ff95e0SMark Pearson mutex_lock(&profile_lock);
144a2ff95e0SMark Pearson /* We can only have one active profile */
145a2ff95e0SMark Pearson if (cur_profile) {
146a2ff95e0SMark Pearson mutex_unlock(&profile_lock);
147a2ff95e0SMark Pearson return -EEXIST;
148a2ff95e0SMark Pearson }
149a2ff95e0SMark Pearson
150a2ff95e0SMark Pearson /* Sanity check the profile handler field are set */
151a2ff95e0SMark Pearson if (!pprof || bitmap_empty(pprof->choices, PLATFORM_PROFILE_LAST) ||
152a2ff95e0SMark Pearson !pprof->profile_set || !pprof->profile_get) {
153a2ff95e0SMark Pearson mutex_unlock(&profile_lock);
154a2ff95e0SMark Pearson return -EINVAL;
155a2ff95e0SMark Pearson }
156a2ff95e0SMark Pearson
157a2ff95e0SMark Pearson err = sysfs_create_group(acpi_kobj, &platform_profile_group);
158a2ff95e0SMark Pearson if (err) {
159a2ff95e0SMark Pearson mutex_unlock(&profile_lock);
160a2ff95e0SMark Pearson return err;
161a2ff95e0SMark Pearson }
162a2ff95e0SMark Pearson
163a2ff95e0SMark Pearson cur_profile = pprof;
164a2ff95e0SMark Pearson mutex_unlock(&profile_lock);
165a2ff95e0SMark Pearson return 0;
166a2ff95e0SMark Pearson }
167a2ff95e0SMark Pearson EXPORT_SYMBOL_GPL(platform_profile_register);
168a2ff95e0SMark Pearson
platform_profile_remove(void)169a2ff95e0SMark Pearson int platform_profile_remove(void)
170a2ff95e0SMark Pearson {
171a2ff95e0SMark Pearson sysfs_remove_group(acpi_kobj, &platform_profile_group);
172041142d7SHans de Goede
173041142d7SHans de Goede mutex_lock(&profile_lock);
174a2ff95e0SMark Pearson cur_profile = NULL;
175a2ff95e0SMark Pearson mutex_unlock(&profile_lock);
176a2ff95e0SMark Pearson return 0;
177a2ff95e0SMark Pearson }
178a2ff95e0SMark Pearson EXPORT_SYMBOL_GPL(platform_profile_remove);
179a2ff95e0SMark Pearson
180a2ff95e0SMark Pearson MODULE_AUTHOR("Mark Pearson <markpearson@lenovo.com>");
181a2ff95e0SMark Pearson MODULE_LICENSE("GPL");
182