blob: dd2fbf38e414889589b126315650d0410b9caba7 [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
Jiaxun Yang9d566532021-01-25 12:59:56 +010012static struct platform_profile_handler *cur_profile;
Mark Pearsona2ff95e2020-12-29 19:18:26 -050013static 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",
Maximilian Luz6c0b5e32021-02-11 21:17:02 +010020 [PLATFORM_PROFILE_BALANCED_PERFORMANCE] = "balanced-performance",
Mark Pearsona2ff95e2020-12-29 19:18:26 -050021 [PLATFORM_PROFILE_PERFORMANCE] = "performance",
22};
23static_assert(ARRAY_SIZE(profile_names) == PLATFORM_PROFILE_LAST);
24
25static ssize_t platform_profile_choices_show(struct device *dev,
26 struct device_attribute *attr,
27 char *buf)
28{
29 int len = 0;
30 int err, i;
31
32 err = mutex_lock_interruptible(&profile_lock);
33 if (err)
34 return err;
35
36 if (!cur_profile) {
37 mutex_unlock(&profile_lock);
38 return -ENODEV;
39 }
40
41 for_each_set_bit(i, cur_profile->choices, PLATFORM_PROFILE_LAST) {
42 if (len == 0)
43 len += sysfs_emit_at(buf, len, "%s", profile_names[i]);
44 else
45 len += sysfs_emit_at(buf, len, " %s", profile_names[i]);
46 }
47 len += sysfs_emit_at(buf, len, "\n");
48 mutex_unlock(&profile_lock);
49 return len;
50}
51
52static ssize_t platform_profile_show(struct device *dev,
53 struct device_attribute *attr,
54 char *buf)
55{
56 enum platform_profile_option profile = PLATFORM_PROFILE_BALANCED;
57 int err;
58
59 err = mutex_lock_interruptible(&profile_lock);
60 if (err)
61 return err;
62
63 if (!cur_profile) {
64 mutex_unlock(&profile_lock);
65 return -ENODEV;
66 }
67
Jiaxun Yang84f90172021-01-25 12:59:57 +010068 err = cur_profile->profile_get(cur_profile, &profile);
Mark Pearsona2ff95e2020-12-29 19:18:26 -050069 mutex_unlock(&profile_lock);
70 if (err)
71 return err;
72
73 /* Check that profile is valid index */
74 if (WARN_ON((profile < 0) || (profile >= ARRAY_SIZE(profile_names))))
75 return -EIO;
76
77 return sysfs_emit(buf, "%s\n", profile_names[profile]);
78}
79
80static ssize_t platform_profile_store(struct device *dev,
81 struct device_attribute *attr,
82 const char *buf, size_t count)
83{
84 int err, i;
85
86 err = mutex_lock_interruptible(&profile_lock);
87 if (err)
88 return err;
89
90 if (!cur_profile) {
91 mutex_unlock(&profile_lock);
92 return -ENODEV;
93 }
94
95 /* Scan for a matching profile */
96 i = sysfs_match_string(profile_names, buf);
97 if (i < 0) {
98 mutex_unlock(&profile_lock);
99 return -EINVAL;
100 }
101
102 /* Check that platform supports this profile choice */
103 if (!test_bit(i, cur_profile->choices)) {
104 mutex_unlock(&profile_lock);
105 return -EOPNOTSUPP;
106 }
107
Jiaxun Yang84f90172021-01-25 12:59:57 +0100108 err = cur_profile->profile_set(cur_profile, i);
Mark Pearsona2ff95e2020-12-29 19:18:26 -0500109 mutex_unlock(&profile_lock);
110 if (err)
111 return err;
112 return count;
113}
114
115static DEVICE_ATTR_RO(platform_profile_choices);
116static DEVICE_ATTR_RW(platform_profile);
117
118static struct attribute *platform_profile_attrs[] = {
119 &dev_attr_platform_profile_choices.attr,
120 &dev_attr_platform_profile.attr,
121 NULL
122};
123
124static const struct attribute_group platform_profile_group = {
125 .attrs = platform_profile_attrs
126};
127
128void platform_profile_notify(void)
129{
130 if (!cur_profile)
131 return;
132 sysfs_notify(acpi_kobj, NULL, "platform_profile");
133}
134EXPORT_SYMBOL_GPL(platform_profile_notify);
135
Jiaxun Yang9d566532021-01-25 12:59:56 +0100136int platform_profile_register(struct platform_profile_handler *pprof)
Mark Pearsona2ff95e2020-12-29 19:18:26 -0500137{
138 int err;
139
140 mutex_lock(&profile_lock);
141 /* We can only have one active profile */
142 if (cur_profile) {
143 mutex_unlock(&profile_lock);
144 return -EEXIST;
145 }
146
147 /* Sanity check the profile handler field are set */
148 if (!pprof || bitmap_empty(pprof->choices, PLATFORM_PROFILE_LAST) ||
149 !pprof->profile_set || !pprof->profile_get) {
150 mutex_unlock(&profile_lock);
151 return -EINVAL;
152 }
153
154 err = sysfs_create_group(acpi_kobj, &platform_profile_group);
155 if (err) {
156 mutex_unlock(&profile_lock);
157 return err;
158 }
159
160 cur_profile = pprof;
161 mutex_unlock(&profile_lock);
162 return 0;
163}
164EXPORT_SYMBOL_GPL(platform_profile_register);
165
166int platform_profile_remove(void)
167{
Mark Pearsona2ff95e2020-12-29 19:18:26 -0500168 sysfs_remove_group(acpi_kobj, &platform_profile_group);
Hans de Goede041142d2021-01-25 20:09:09 +0100169
170 mutex_lock(&profile_lock);
Mark Pearsona2ff95e2020-12-29 19:18:26 -0500171 cur_profile = NULL;
172 mutex_unlock(&profile_lock);
173 return 0;
174}
175EXPORT_SYMBOL_GPL(platform_profile_remove);
176
177MODULE_AUTHOR("Mark Pearson <markpearson@lenovo.com>");
178MODULE_LICENSE("GPL");