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);
109b25d5a1cSHans de Goede if (!err)
110b25d5a1cSHans de Goede sysfs_notify(acpi_kobj, NULL, "platform_profile");
111b25d5a1cSHans 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_cycle(void)139ba95eb44SGergo Koteles int platform_profile_cycle(void)
140ba95eb44SGergo Koteles {
141ba95eb44SGergo Koteles enum platform_profile_option profile;
142ba95eb44SGergo Koteles enum platform_profile_option next;
143ba95eb44SGergo Koteles int err;
144ba95eb44SGergo Koteles
145ba95eb44SGergo Koteles err = mutex_lock_interruptible(&profile_lock);
146ba95eb44SGergo Koteles if (err)
147ba95eb44SGergo Koteles return err;
148ba95eb44SGergo Koteles
149ba95eb44SGergo Koteles if (!cur_profile) {
150ba95eb44SGergo Koteles mutex_unlock(&profile_lock);
151ba95eb44SGergo Koteles return -ENODEV;
152ba95eb44SGergo Koteles }
153ba95eb44SGergo Koteles
154ba95eb44SGergo Koteles err = cur_profile->profile_get(cur_profile, &profile);
155ba95eb44SGergo Koteles if (err) {
156ba95eb44SGergo Koteles mutex_unlock(&profile_lock);
157ba95eb44SGergo Koteles return err;
158ba95eb44SGergo Koteles }
159ba95eb44SGergo Koteles
160ba95eb44SGergo Koteles next = find_next_bit_wrap(cur_profile->choices, PLATFORM_PROFILE_LAST,
161ba95eb44SGergo Koteles profile + 1);
162ba95eb44SGergo Koteles
163ba95eb44SGergo Koteles if (WARN_ON(next == PLATFORM_PROFILE_LAST)) {
164ba95eb44SGergo Koteles mutex_unlock(&profile_lock);
165ba95eb44SGergo Koteles return -EINVAL;
166ba95eb44SGergo Koteles }
167ba95eb44SGergo Koteles
168ba95eb44SGergo Koteles err = cur_profile->profile_set(cur_profile, next);
169ba95eb44SGergo Koteles mutex_unlock(&profile_lock);
170ba95eb44SGergo Koteles
171ba95eb44SGergo Koteles if (!err)
172ba95eb44SGergo Koteles sysfs_notify(acpi_kobj, NULL, "platform_profile");
173ba95eb44SGergo Koteles
174ba95eb44SGergo Koteles return err;
175ba95eb44SGergo Koteles }
176ba95eb44SGergo Koteles EXPORT_SYMBOL_GPL(platform_profile_cycle);
177ba95eb44SGergo Koteles
platform_profile_register(struct platform_profile_handler * pprof)1789d56653dSJiaxun Yang int platform_profile_register(struct platform_profile_handler *pprof)
179a2ff95e0SMark Pearson {
180a2ff95e0SMark Pearson int err;
181a2ff95e0SMark Pearson
182a2ff95e0SMark Pearson mutex_lock(&profile_lock);
183a2ff95e0SMark Pearson /* We can only have one active profile */
184a2ff95e0SMark Pearson if (cur_profile) {
185a2ff95e0SMark Pearson mutex_unlock(&profile_lock);
186a2ff95e0SMark Pearson return -EEXIST;
187a2ff95e0SMark Pearson }
188a2ff95e0SMark Pearson
189a2ff95e0SMark Pearson /* Sanity check the profile handler field are set */
190a2ff95e0SMark Pearson if (!pprof || bitmap_empty(pprof->choices, PLATFORM_PROFILE_LAST) ||
191a2ff95e0SMark Pearson !pprof->profile_set || !pprof->profile_get) {
192a2ff95e0SMark Pearson mutex_unlock(&profile_lock);
193a2ff95e0SMark Pearson return -EINVAL;
194a2ff95e0SMark Pearson }
195a2ff95e0SMark Pearson
196a2ff95e0SMark Pearson err = sysfs_create_group(acpi_kobj, &platform_profile_group);
197a2ff95e0SMark Pearson if (err) {
198a2ff95e0SMark Pearson mutex_unlock(&profile_lock);
199a2ff95e0SMark Pearson return err;
200a2ff95e0SMark Pearson }
201a2ff95e0SMark Pearson
202a2ff95e0SMark Pearson cur_profile = pprof;
203a2ff95e0SMark Pearson mutex_unlock(&profile_lock);
204a2ff95e0SMark Pearson return 0;
205a2ff95e0SMark Pearson }
206a2ff95e0SMark Pearson EXPORT_SYMBOL_GPL(platform_profile_register);
207a2ff95e0SMark Pearson
platform_profile_remove(void)208a2ff95e0SMark Pearson int platform_profile_remove(void)
209a2ff95e0SMark Pearson {
210a2ff95e0SMark Pearson sysfs_remove_group(acpi_kobj, &platform_profile_group);
211041142d7SHans de Goede
212041142d7SHans de Goede mutex_lock(&profile_lock);
213a2ff95e0SMark Pearson cur_profile = NULL;
214a2ff95e0SMark Pearson mutex_unlock(&profile_lock);
215a2ff95e0SMark Pearson return 0;
216a2ff95e0SMark Pearson }
217a2ff95e0SMark Pearson EXPORT_SYMBOL_GPL(platform_profile_remove);
218a2ff95e0SMark Pearson
219a2ff95e0SMark Pearson MODULE_AUTHOR("Mark Pearson <markpearson@lenovo.com>");
220*abba7f92SJeff Johnson MODULE_DESCRIPTION("ACPI platform profile sysfs interface");
221a2ff95e0SMark Pearson MODULE_LICENSE("GPL");
222