xref: /linux/drivers/acpi/platform_profile.c (revision a1ff5a7d78a036d6c2178ee5acd6ba4946243800)
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