blob: 91be50a32cc82f86207fe719d7b4a579138e226c [file] [log] [blame]
Mark Pearsona2ff95e2020-12-29 19:18:26 -05001// 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
12static const struct platform_profile_handler *cur_profile;
13static DEFINE_MUTEX(profile_lock);
14
15static 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};
22static_assert(ARRAY_SIZE(profile_names) == PLATFORM_PROFILE_LAST);
23
24static 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
51static 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(&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
79static 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(i);
108 mutex_unlock(&profile_lock);
109 if (err)
110 return err;
111 return count;
112}
113
114static DEVICE_ATTR_RO(platform_profile_choices);
115static DEVICE_ATTR_RW(platform_profile);
116
117static struct attribute *platform_profile_attrs[] = {
118 &dev_attr_platform_profile_choices.attr,
119 &dev_attr_platform_profile.attr,
120 NULL
121};
122
123static const struct attribute_group platform_profile_group = {
124 .attrs = platform_profile_attrs
125};
126
127void platform_profile_notify(void)
128{
129 if (!cur_profile)
130 return;
131 sysfs_notify(acpi_kobj, NULL, "platform_profile");
132}
133EXPORT_SYMBOL_GPL(platform_profile_notify);
134
135int platform_profile_register(const 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}
163EXPORT_SYMBOL_GPL(platform_profile_register);
164
165int platform_profile_remove(void)
166{
167 mutex_lock(&profile_lock);
168 if (!cur_profile) {
169 mutex_unlock(&profile_lock);
170 return -ENODEV;
171 }
172
173 sysfs_remove_group(acpi_kobj, &platform_profile_group);
174 cur_profile = NULL;
175 mutex_unlock(&profile_lock);
176 return 0;
177}
178EXPORT_SYMBOL_GPL(platform_profile_remove);
179
180MODULE_AUTHOR("Mark Pearson <markpearson@lenovo.com>");
181MODULE_LICENSE("GPL");