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