1 | // 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 | |
12 | static struct platform_profile_handler *cur_profile; |
13 | static DEFINE_MUTEX(profile_lock); |
14 | |
15 | static 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_BALANCED_PERFORMANCE] = "balanced-performance" , |
21 | [PLATFORM_PROFILE_PERFORMANCE] = "performance" , |
22 | }; |
23 | static_assert(ARRAY_SIZE(profile_names) == PLATFORM_PROFILE_LAST); |
24 | |
25 | static 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(lock: &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, at: len, fmt: "%s" , profile_names[i]); |
44 | else |
45 | len += sysfs_emit_at(buf, at: len, fmt: " %s" , profile_names[i]); |
46 | } |
47 | len += sysfs_emit_at(buf, at: len, fmt: "\n" ); |
48 | mutex_unlock(lock: &profile_lock); |
49 | return len; |
50 | } |
51 | |
52 | static 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(lock: &profile_lock); |
65 | return -ENODEV; |
66 | } |
67 | |
68 | err = cur_profile->profile_get(cur_profile, &profile); |
69 | mutex_unlock(lock: &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, fmt: "%s\n" , profile_names[profile]); |
78 | } |
79 | |
80 | static 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(lock: &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(lock: &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(lock: &profile_lock); |
105 | return -EOPNOTSUPP; |
106 | } |
107 | |
108 | err = cur_profile->profile_set(cur_profile, i); |
109 | if (!err) |
110 | sysfs_notify(kobj: acpi_kobj, NULL, attr: "platform_profile" ); |
111 | |
112 | mutex_unlock(lock: &profile_lock); |
113 | if (err) |
114 | return err; |
115 | return count; |
116 | } |
117 | |
118 | static DEVICE_ATTR_RO(platform_profile_choices); |
119 | static DEVICE_ATTR_RW(platform_profile); |
120 | |
121 | static struct attribute *platform_profile_attrs[] = { |
122 | &dev_attr_platform_profile_choices.attr, |
123 | &dev_attr_platform_profile.attr, |
124 | NULL |
125 | }; |
126 | |
127 | static const struct attribute_group platform_profile_group = { |
128 | .attrs = platform_profile_attrs |
129 | }; |
130 | |
131 | void platform_profile_notify(void) |
132 | { |
133 | if (!cur_profile) |
134 | return; |
135 | sysfs_notify(kobj: acpi_kobj, NULL, attr: "platform_profile" ); |
136 | } |
137 | EXPORT_SYMBOL_GPL(platform_profile_notify); |
138 | |
139 | int platform_profile_register(struct platform_profile_handler *pprof) |
140 | { |
141 | int err; |
142 | |
143 | mutex_lock(&profile_lock); |
144 | /* We can only have one active profile */ |
145 | if (cur_profile) { |
146 | mutex_unlock(lock: &profile_lock); |
147 | return -EEXIST; |
148 | } |
149 | |
150 | /* Sanity check the profile handler field are set */ |
151 | if (!pprof || bitmap_empty(src: pprof->choices, nbits: PLATFORM_PROFILE_LAST) || |
152 | !pprof->profile_set || !pprof->profile_get) { |
153 | mutex_unlock(lock: &profile_lock); |
154 | return -EINVAL; |
155 | } |
156 | |
157 | err = sysfs_create_group(kobj: acpi_kobj, grp: &platform_profile_group); |
158 | if (err) { |
159 | mutex_unlock(lock: &profile_lock); |
160 | return err; |
161 | } |
162 | |
163 | cur_profile = pprof; |
164 | mutex_unlock(lock: &profile_lock); |
165 | return 0; |
166 | } |
167 | EXPORT_SYMBOL_GPL(platform_profile_register); |
168 | |
169 | int platform_profile_remove(void) |
170 | { |
171 | sysfs_remove_group(kobj: acpi_kobj, grp: &platform_profile_group); |
172 | |
173 | mutex_lock(&profile_lock); |
174 | cur_profile = NULL; |
175 | mutex_unlock(lock: &profile_lock); |
176 | return 0; |
177 | } |
178 | EXPORT_SYMBOL_GPL(platform_profile_remove); |
179 | |
180 | MODULE_AUTHOR("Mark Pearson <markpearson@lenovo.com>" ); |
181 | MODULE_LICENSE("GPL" ); |
182 | |