1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | // Copyright IBM Corp 2019 |
3 | |
4 | #include <linux/bitops.h> |
5 | #include <linux/device.h> |
6 | #include <linux/export.h> |
7 | #include <linux/hwmon-sysfs.h> |
8 | #include <linux/kernel.h> |
9 | #include <linux/kstrtox.h> |
10 | #include <linux/sysfs.h> |
11 | |
12 | #include "common.h" |
13 | |
14 | /* OCC status register */ |
15 | #define OCC_STAT_MASTER BIT(7) |
16 | |
17 | /* OCC extended status register */ |
18 | #define OCC_EXT_STAT_DVFS_OT BIT(7) |
19 | #define OCC_EXT_STAT_DVFS_POWER BIT(6) |
20 | #define OCC_EXT_STAT_MEM_THROTTLE BIT(5) |
21 | #define OCC_EXT_STAT_QUICK_DROP BIT(4) |
22 | #define OCC_EXT_STAT_DVFS_VDD BIT(3) |
23 | #define OCC_EXT_STAT_GPU_THROTTLE GENMASK(2, 0) |
24 | |
25 | static ssize_t occ_active_store(struct device *dev, |
26 | struct device_attribute *attr, |
27 | const char *buf, size_t count) |
28 | { |
29 | int rc; |
30 | bool active; |
31 | struct occ *occ = dev_get_drvdata(dev); |
32 | |
33 | rc = kstrtobool(s: buf, res: &active); |
34 | if (rc) |
35 | return rc; |
36 | |
37 | rc = occ_active(occ, active); |
38 | if (rc) |
39 | return rc; |
40 | |
41 | return count; |
42 | } |
43 | |
44 | static ssize_t occ_sysfs_show(struct device *dev, |
45 | struct device_attribute *attr, char *buf) |
46 | { |
47 | int rc; |
48 | int val = 0; |
49 | struct occ *occ = dev_get_drvdata(dev); |
50 | struct occ_poll_response_header *; |
51 | struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); |
52 | |
53 | if (occ->active) { |
54 | rc = occ_update_response(occ); |
55 | if (rc) |
56 | return rc; |
57 | |
58 | header = (struct occ_poll_response_header *)occ->resp.data; |
59 | |
60 | switch (sattr->index) { |
61 | case 0: |
62 | val = !!(header->status & OCC_STAT_MASTER); |
63 | break; |
64 | case 1: |
65 | val = 1; |
66 | break; |
67 | case 2: |
68 | val = !!(header->ext_status & OCC_EXT_STAT_DVFS_OT); |
69 | break; |
70 | case 3: |
71 | val = !!(header->ext_status & OCC_EXT_STAT_DVFS_POWER); |
72 | break; |
73 | case 4: |
74 | val = !!(header->ext_status & |
75 | OCC_EXT_STAT_MEM_THROTTLE); |
76 | break; |
77 | case 5: |
78 | val = !!(header->ext_status & OCC_EXT_STAT_QUICK_DROP); |
79 | break; |
80 | case 6: |
81 | val = header->occ_state; |
82 | break; |
83 | case 7: |
84 | if (header->status & OCC_STAT_MASTER) |
85 | val = hweight8(header->occs_present); |
86 | else |
87 | val = 1; |
88 | break; |
89 | case 8: |
90 | val = header->ips_status; |
91 | break; |
92 | case 9: |
93 | val = header->mode; |
94 | break; |
95 | case 10: |
96 | val = !!(header->ext_status & OCC_EXT_STAT_DVFS_VDD); |
97 | break; |
98 | case 11: |
99 | val = header->ext_status & OCC_EXT_STAT_GPU_THROTTLE; |
100 | break; |
101 | default: |
102 | return -EINVAL; |
103 | } |
104 | } else { |
105 | if (sattr->index == 1) |
106 | val = 0; |
107 | else if (sattr->index <= 11) |
108 | val = -ENODATA; |
109 | else |
110 | return -EINVAL; |
111 | } |
112 | |
113 | return sysfs_emit(buf, fmt: "%d\n" , val); |
114 | } |
115 | |
116 | static ssize_t occ_error_show(struct device *dev, |
117 | struct device_attribute *attr, char *buf) |
118 | { |
119 | struct occ *occ = dev_get_drvdata(dev); |
120 | |
121 | occ_update_response(occ); |
122 | |
123 | return sysfs_emit(buf, fmt: "%d\n" , occ->error); |
124 | } |
125 | |
126 | static SENSOR_DEVICE_ATTR(occ_master, 0444, occ_sysfs_show, NULL, 0); |
127 | static SENSOR_DEVICE_ATTR(occ_active, 0644, occ_sysfs_show, occ_active_store, |
128 | 1); |
129 | static SENSOR_DEVICE_ATTR(occ_dvfs_overtemp, 0444, occ_sysfs_show, NULL, 2); |
130 | static SENSOR_DEVICE_ATTR(occ_dvfs_power, 0444, occ_sysfs_show, NULL, 3); |
131 | static SENSOR_DEVICE_ATTR(occ_mem_throttle, 0444, occ_sysfs_show, NULL, 4); |
132 | static SENSOR_DEVICE_ATTR(occ_quick_pwr_drop, 0444, occ_sysfs_show, NULL, 5); |
133 | static SENSOR_DEVICE_ATTR(occ_state, 0444, occ_sysfs_show, NULL, 6); |
134 | static SENSOR_DEVICE_ATTR(occs_present, 0444, occ_sysfs_show, NULL, 7); |
135 | static SENSOR_DEVICE_ATTR(occ_ips_status, 0444, occ_sysfs_show, NULL, 8); |
136 | static SENSOR_DEVICE_ATTR(occ_mode, 0444, occ_sysfs_show, NULL, 9); |
137 | static SENSOR_DEVICE_ATTR(occ_dvfs_vdd, 0444, occ_sysfs_show, NULL, 10); |
138 | static SENSOR_DEVICE_ATTR(occ_gpu_throttle, 0444, occ_sysfs_show, NULL, 11); |
139 | static DEVICE_ATTR_RO(occ_error); |
140 | |
141 | static struct attribute *occ_attributes[] = { |
142 | &sensor_dev_attr_occ_master.dev_attr.attr, |
143 | &sensor_dev_attr_occ_active.dev_attr.attr, |
144 | &sensor_dev_attr_occ_dvfs_overtemp.dev_attr.attr, |
145 | &sensor_dev_attr_occ_dvfs_power.dev_attr.attr, |
146 | &sensor_dev_attr_occ_mem_throttle.dev_attr.attr, |
147 | &sensor_dev_attr_occ_quick_pwr_drop.dev_attr.attr, |
148 | &sensor_dev_attr_occ_state.dev_attr.attr, |
149 | &sensor_dev_attr_occs_present.dev_attr.attr, |
150 | &sensor_dev_attr_occ_ips_status.dev_attr.attr, |
151 | &sensor_dev_attr_occ_mode.dev_attr.attr, |
152 | &sensor_dev_attr_occ_dvfs_vdd.dev_attr.attr, |
153 | &sensor_dev_attr_occ_gpu_throttle.dev_attr.attr, |
154 | &dev_attr_occ_error.attr, |
155 | NULL |
156 | }; |
157 | |
158 | static const struct attribute_group occ_sysfs = { |
159 | .attrs = occ_attributes, |
160 | }; |
161 | |
162 | void occ_sysfs_poll_done(struct occ *occ) |
163 | { |
164 | const char *name; |
165 | struct occ_poll_response_header * = |
166 | (struct occ_poll_response_header *)occ->resp.data; |
167 | |
168 | /* |
169 | * On the first poll response, we haven't yet created the sysfs |
170 | * attributes, so don't make any notify calls. |
171 | */ |
172 | if (!occ->active) |
173 | goto done; |
174 | |
175 | if ((header->status & OCC_STAT_MASTER) != |
176 | (occ->prev_stat & OCC_STAT_MASTER)) { |
177 | name = sensor_dev_attr_occ_master.dev_attr.attr.name; |
178 | sysfs_notify(kobj: &occ->bus_dev->kobj, NULL, attr: name); |
179 | } |
180 | |
181 | if ((header->ext_status & OCC_EXT_STAT_DVFS_OT) != |
182 | (occ->prev_ext_stat & OCC_EXT_STAT_DVFS_OT)) { |
183 | name = sensor_dev_attr_occ_dvfs_overtemp.dev_attr.attr.name; |
184 | sysfs_notify(kobj: &occ->bus_dev->kobj, NULL, attr: name); |
185 | } |
186 | |
187 | if ((header->ext_status & OCC_EXT_STAT_DVFS_POWER) != |
188 | (occ->prev_ext_stat & OCC_EXT_STAT_DVFS_POWER)) { |
189 | name = sensor_dev_attr_occ_dvfs_power.dev_attr.attr.name; |
190 | sysfs_notify(kobj: &occ->bus_dev->kobj, NULL, attr: name); |
191 | } |
192 | |
193 | if ((header->ext_status & OCC_EXT_STAT_MEM_THROTTLE) != |
194 | (occ->prev_ext_stat & OCC_EXT_STAT_MEM_THROTTLE)) { |
195 | name = sensor_dev_attr_occ_mem_throttle.dev_attr.attr.name; |
196 | sysfs_notify(kobj: &occ->bus_dev->kobj, NULL, attr: name); |
197 | } |
198 | |
199 | if ((header->ext_status & OCC_EXT_STAT_QUICK_DROP) != |
200 | (occ->prev_ext_stat & OCC_EXT_STAT_QUICK_DROP)) { |
201 | name = sensor_dev_attr_occ_quick_pwr_drop.dev_attr.attr.name; |
202 | sysfs_notify(kobj: &occ->bus_dev->kobj, NULL, attr: name); |
203 | } |
204 | |
205 | if ((header->ext_status & OCC_EXT_STAT_DVFS_VDD) != |
206 | (occ->prev_ext_stat & OCC_EXT_STAT_DVFS_VDD)) { |
207 | name = sensor_dev_attr_occ_dvfs_vdd.dev_attr.attr.name; |
208 | sysfs_notify(kobj: &occ->bus_dev->kobj, NULL, attr: name); |
209 | } |
210 | |
211 | if ((header->ext_status & OCC_EXT_STAT_GPU_THROTTLE) != |
212 | (occ->prev_ext_stat & OCC_EXT_STAT_GPU_THROTTLE)) { |
213 | name = sensor_dev_attr_occ_gpu_throttle.dev_attr.attr.name; |
214 | sysfs_notify(kobj: &occ->bus_dev->kobj, NULL, attr: name); |
215 | } |
216 | |
217 | if ((header->status & OCC_STAT_MASTER) && |
218 | header->occs_present != occ->prev_occs_present) { |
219 | name = sensor_dev_attr_occs_present.dev_attr.attr.name; |
220 | sysfs_notify(kobj: &occ->bus_dev->kobj, NULL, attr: name); |
221 | } |
222 | |
223 | if (header->ips_status != occ->prev_ips_status) { |
224 | name = sensor_dev_attr_occ_ips_status.dev_attr.attr.name; |
225 | sysfs_notify(kobj: &occ->bus_dev->kobj, NULL, attr: name); |
226 | } |
227 | |
228 | if (header->mode != occ->prev_mode) { |
229 | name = sensor_dev_attr_occ_mode.dev_attr.attr.name; |
230 | sysfs_notify(kobj: &occ->bus_dev->kobj, NULL, attr: name); |
231 | } |
232 | |
233 | if (occ->error && occ->error != occ->prev_error) { |
234 | name = dev_attr_occ_error.attr.name; |
235 | sysfs_notify(kobj: &occ->bus_dev->kobj, NULL, attr: name); |
236 | } |
237 | |
238 | /* no notifications for OCC state; doesn't indicate error condition */ |
239 | |
240 | done: |
241 | occ->prev_error = occ->error; |
242 | occ->prev_stat = header->status; |
243 | occ->prev_ext_stat = header->ext_status; |
244 | occ->prev_occs_present = header->occs_present; |
245 | occ->prev_ips_status = header->ips_status; |
246 | occ->prev_mode = header->mode; |
247 | } |
248 | |
249 | int occ_setup_sysfs(struct occ *occ) |
250 | { |
251 | return sysfs_create_group(kobj: &occ->bus_dev->kobj, grp: &occ_sysfs); |
252 | } |
253 | |
254 | void occ_shutdown_sysfs(struct occ *occ) |
255 | { |
256 | sysfs_remove_group(kobj: &occ->bus_dev->kobj, grp: &occ_sysfs); |
257 | } |
258 | |