Commit | Line | Data |
---|---|---|
b23688ae SH |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * System Control and Management Interface(SCMI) based hwmon sensor driver | |
4 | * | |
987bae41 | 5 | * Copyright (C) 2018-2021 ARM Ltd. |
b23688ae SH |
6 | * Sudeep Holla <sudeep.holla@arm.com> |
7 | */ | |
8 | ||
9 | #include <linux/hwmon.h> | |
10 | #include <linux/module.h> | |
11 | #include <linux/scmi_protocol.h> | |
12 | #include <linux/slab.h> | |
13 | #include <linux/sysfs.h> | |
14 | #include <linux/thermal.h> | |
15 | ||
987bae41 CM |
16 | static const struct scmi_sensor_proto_ops *sensor_ops; |
17 | ||
b23688ae | 18 | struct scmi_sensors { |
987bae41 | 19 | const struct scmi_protocol_handle *ph; |
b23688ae SH |
20 | const struct scmi_sensor_info **info[hwmon_max]; |
21 | }; | |
22 | ||
ac778e62 FF |
23 | static inline u64 __pow10(u8 x) |
24 | { | |
25 | u64 r = 1; | |
26 | ||
27 | while (x--) | |
28 | r *= 10; | |
29 | ||
30 | return r; | |
31 | } | |
32 | ||
33 | static int scmi_hwmon_scale(const struct scmi_sensor_info *sensor, u64 *value) | |
34 | { | |
d7971d57 | 35 | int scale = sensor->scale; |
ac778e62 FF |
36 | u64 f; |
37 | ||
38 | switch (sensor->type) { | |
39 | case TEMPERATURE_C: | |
40 | case VOLTAGE: | |
41 | case CURRENT: | |
42 | scale += 3; | |
43 | break; | |
44 | case POWER: | |
45 | case ENERGY: | |
46 | scale += 6; | |
47 | break; | |
48 | default: | |
49 | break; | |
50 | } | |
51 | ||
52 | if (scale == 0) | |
53 | return 0; | |
54 | ||
55 | if (abs(scale) > 19) | |
56 | return -E2BIG; | |
57 | ||
58 | f = __pow10(abs(scale)); | |
59 | if (scale > 0) | |
60 | *value *= f; | |
61 | else | |
62 | *value = div64_u64(*value, f); | |
63 | ||
64 | return 0; | |
65 | } | |
66 | ||
b23688ae SH |
67 | static int scmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, |
68 | u32 attr, int channel, long *val) | |
69 | { | |
70 | int ret; | |
71 | u64 value; | |
72 | const struct scmi_sensor_info *sensor; | |
73 | struct scmi_sensors *scmi_sensors = dev_get_drvdata(dev); | |
b23688ae SH |
74 | |
75 | sensor = *(scmi_sensors->info[type] + channel); | |
987bae41 | 76 | ret = sensor_ops->reading_get(scmi_sensors->ph, sensor->id, &value); |
ac778e62 FF |
77 | if (ret) |
78 | return ret; | |
79 | ||
80 | ret = scmi_hwmon_scale(sensor, &value); | |
b23688ae SH |
81 | if (!ret) |
82 | *val = value; | |
83 | ||
84 | return ret; | |
85 | } | |
86 | ||
87 | static int | |
88 | scmi_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type, | |
89 | u32 attr, int channel, const char **str) | |
90 | { | |
91 | const struct scmi_sensor_info *sensor; | |
92 | struct scmi_sensors *scmi_sensors = dev_get_drvdata(dev); | |
93 | ||
94 | sensor = *(scmi_sensors->info[type] + channel); | |
95 | *str = sensor->name; | |
96 | ||
97 | return 0; | |
98 | } | |
99 | ||
100 | static umode_t | |
101 | scmi_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type, | |
102 | u32 attr, int channel) | |
103 | { | |
104 | const struct scmi_sensor_info *sensor; | |
105 | const struct scmi_sensors *scmi_sensors = drvdata; | |
106 | ||
107 | sensor = *(scmi_sensors->info[type] + channel); | |
a31796c3 | 108 | if (sensor) |
626c4a06 | 109 | return 0444; |
b23688ae SH |
110 | |
111 | return 0; | |
112 | } | |
113 | ||
114 | static const struct hwmon_ops scmi_hwmon_ops = { | |
115 | .is_visible = scmi_hwmon_is_visible, | |
116 | .read = scmi_hwmon_read, | |
117 | .read_string = scmi_hwmon_read_string, | |
118 | }; | |
119 | ||
120 | static struct hwmon_chip_info scmi_chip_info = { | |
121 | .ops = &scmi_hwmon_ops, | |
122 | .info = NULL, | |
123 | }; | |
124 | ||
125 | static int scmi_hwmon_add_chan_info(struct hwmon_channel_info *scmi_hwmon_chan, | |
126 | struct device *dev, int num, | |
127 | enum hwmon_sensor_types type, u32 config) | |
128 | { | |
129 | int i; | |
130 | u32 *cfg = devm_kcalloc(dev, num + 1, sizeof(*cfg), GFP_KERNEL); | |
131 | ||
132 | if (!cfg) | |
133 | return -ENOMEM; | |
134 | ||
135 | scmi_hwmon_chan->type = type; | |
136 | scmi_hwmon_chan->config = cfg; | |
137 | for (i = 0; i < num; i++, cfg++) | |
138 | *cfg = config; | |
139 | ||
140 | return 0; | |
141 | } | |
142 | ||
143 | static enum hwmon_sensor_types scmi_types[] = { | |
144 | [TEMPERATURE_C] = hwmon_temp, | |
145 | [VOLTAGE] = hwmon_in, | |
146 | [CURRENT] = hwmon_curr, | |
147 | [POWER] = hwmon_power, | |
148 | [ENERGY] = hwmon_energy, | |
149 | }; | |
150 | ||
3ce17cd2 | 151 | static u32 hwmon_attributes[hwmon_max] = { |
b23688ae SH |
152 | [hwmon_chip] = HWMON_C_REGISTER_TZ, |
153 | [hwmon_temp] = HWMON_T_INPUT | HWMON_T_LABEL, | |
154 | [hwmon_in] = HWMON_I_INPUT | HWMON_I_LABEL, | |
155 | [hwmon_curr] = HWMON_C_INPUT | HWMON_C_LABEL, | |
156 | [hwmon_power] = HWMON_P_INPUT | HWMON_P_LABEL, | |
157 | [hwmon_energy] = HWMON_E_INPUT | HWMON_E_LABEL, | |
158 | }; | |
159 | ||
160 | static int scmi_hwmon_probe(struct scmi_device *sdev) | |
161 | { | |
162 | int i, idx; | |
163 | u16 nr_sensors; | |
164 | enum hwmon_sensor_types type; | |
165 | struct scmi_sensors *scmi_sensors; | |
166 | const struct scmi_sensor_info *sensor; | |
167 | int nr_count[hwmon_max] = {0}, nr_types = 0; | |
168 | const struct hwmon_chip_info *chip_info; | |
169 | struct device *hwdev, *dev = &sdev->dev; | |
170 | struct hwmon_channel_info *scmi_hwmon_chan; | |
171 | const struct hwmon_channel_info **ptr_scmi_ci; | |
172 | const struct scmi_handle *handle = sdev->handle; | |
987bae41 | 173 | struct scmi_protocol_handle *ph; |
b23688ae | 174 | |
987bae41 | 175 | if (!handle) |
b23688ae SH |
176 | return -ENODEV; |
177 | ||
987bae41 CM |
178 | sensor_ops = handle->devm_protocol_get(sdev, SCMI_PROTOCOL_SENSOR, &ph); |
179 | if (IS_ERR(sensor_ops)) | |
180 | return PTR_ERR(sensor_ops); | |
181 | ||
182 | nr_sensors = sensor_ops->count_get(ph); | |
b23688ae SH |
183 | if (!nr_sensors) |
184 | return -EIO; | |
185 | ||
186 | scmi_sensors = devm_kzalloc(dev, sizeof(*scmi_sensors), GFP_KERNEL); | |
187 | if (!scmi_sensors) | |
188 | return -ENOMEM; | |
189 | ||
987bae41 | 190 | scmi_sensors->ph = ph; |
b23688ae SH |
191 | |
192 | for (i = 0; i < nr_sensors; i++) { | |
987bae41 | 193 | sensor = sensor_ops->info_get(ph, i); |
b23688ae | 194 | if (!sensor) |
c09880ce | 195 | return -EINVAL; |
b23688ae SH |
196 | |
197 | switch (sensor->type) { | |
198 | case TEMPERATURE_C: | |
199 | case VOLTAGE: | |
200 | case CURRENT: | |
201 | case POWER: | |
202 | case ENERGY: | |
203 | type = scmi_types[sensor->type]; | |
204 | if (!nr_count[type]) | |
205 | nr_types++; | |
206 | nr_count[type]++; | |
207 | break; | |
208 | } | |
209 | } | |
210 | ||
0ef0f755 JP |
211 | if (nr_count[hwmon_temp]) { |
212 | nr_count[hwmon_chip]++; | |
213 | nr_types++; | |
214 | } | |
b23688ae SH |
215 | |
216 | scmi_hwmon_chan = devm_kcalloc(dev, nr_types, sizeof(*scmi_hwmon_chan), | |
217 | GFP_KERNEL); | |
218 | if (!scmi_hwmon_chan) | |
219 | return -ENOMEM; | |
220 | ||
221 | ptr_scmi_ci = devm_kcalloc(dev, nr_types + 1, sizeof(*ptr_scmi_ci), | |
222 | GFP_KERNEL); | |
223 | if (!ptr_scmi_ci) | |
224 | return -ENOMEM; | |
225 | ||
226 | scmi_chip_info.info = ptr_scmi_ci; | |
227 | chip_info = &scmi_chip_info; | |
228 | ||
f18a36cf SH |
229 | for (type = 0; type < hwmon_max; type++) { |
230 | if (!nr_count[type]) | |
231 | continue; | |
232 | ||
b23688ae SH |
233 | scmi_hwmon_add_chan_info(scmi_hwmon_chan, dev, nr_count[type], |
234 | type, hwmon_attributes[type]); | |
235 | *ptr_scmi_ci++ = scmi_hwmon_chan++; | |
236 | ||
237 | scmi_sensors->info[type] = | |
238 | devm_kcalloc(dev, nr_count[type], | |
239 | sizeof(*scmi_sensors->info), GFP_KERNEL); | |
240 | if (!scmi_sensors->info[type]) | |
241 | return -ENOMEM; | |
242 | } | |
243 | ||
244 | for (i = nr_sensors - 1; i >= 0 ; i--) { | |
987bae41 | 245 | sensor = sensor_ops->info_get(ph, i); |
b23688ae SH |
246 | if (!sensor) |
247 | continue; | |
248 | ||
249 | switch (sensor->type) { | |
250 | case TEMPERATURE_C: | |
251 | case VOLTAGE: | |
252 | case CURRENT: | |
253 | case POWER: | |
254 | case ENERGY: | |
255 | type = scmi_types[sensor->type]; | |
256 | idx = --nr_count[type]; | |
257 | *(scmi_sensors->info[type] + idx) = sensor; | |
258 | break; | |
259 | } | |
260 | } | |
261 | ||
262 | hwdev = devm_hwmon_device_register_with_info(dev, "scmi_sensors", | |
263 | scmi_sensors, chip_info, | |
264 | NULL); | |
265 | ||
266 | return PTR_ERR_OR_ZERO(hwdev); | |
267 | } | |
268 | ||
269 | static const struct scmi_device_id scmi_id_table[] = { | |
b017b279 | 270 | { SCMI_PROTOCOL_SENSOR, "hwmon" }, |
b23688ae SH |
271 | { }, |
272 | }; | |
273 | MODULE_DEVICE_TABLE(scmi, scmi_id_table); | |
274 | ||
275 | static struct scmi_driver scmi_hwmon_drv = { | |
276 | .name = "scmi-hwmon", | |
277 | .probe = scmi_hwmon_probe, | |
278 | .id_table = scmi_id_table, | |
279 | }; | |
280 | module_scmi_driver(scmi_hwmon_drv); | |
281 | ||
282 | MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>"); | |
283 | MODULE_DESCRIPTION("ARM SCMI HWMON interface driver"); | |
284 | MODULE_LICENSE("GPL v2"); |