Line 0
Link Here
|
|
|
1 |
// SPDX-License-Identifier: GPL-2.0+ |
2 |
/* |
3 |
* HWMON driver for ASUS B550/X570 motherboards that publish sensor |
4 |
* values via the embedded controller registers. |
5 |
* |
6 |
* Copyright (C) 2021 Eugene Shalygin <eugene.shalygin@gmail.com> |
7 |
* Copyright (C) 2018-2019 Ed Brindley <kernel@maidavale.org> |
8 |
* |
9 |
* EC provides: |
10 |
* Chipset temperature, |
11 |
* CPU temperature, |
12 |
* Motherboard temperature, |
13 |
* T_Sensor temperature, |
14 |
* VRM temperature, |
15 |
* Water In temperature, |
16 |
* Water Out temperature, |
17 |
* CPU Optional Fan RPM, |
18 |
* Chipset Fan RPM, |
19 |
* Water Flow Fan RPM, |
20 |
* CPU current. |
21 |
* |
22 |
*/ |
23 |
#include <asm/unaligned.h> |
24 |
#include <linux/acpi.h> |
25 |
#include <linux/dmi.h> |
26 |
#include <linux/hwmon.h> |
27 |
#include <linux/hwmon-sysfs.h> |
28 |
#include <linux/init.h> |
29 |
#include <linux/jiffies.h> |
30 |
#include <linux/kernel.h> |
31 |
#include <linux/module.h> |
32 |
#include <linux/mutex.h> |
33 |
#include <linux/nls.h> |
34 |
#include <linux/platform_device.h> |
35 |
#include <linux/units.h> |
36 |
#include <linux/wmi.h> |
37 |
|
38 |
#define ASUSWMI_MONITORING_GUID "466747A0-70EC-11DE-8A39-0800200C9A66" |
39 |
#define ASUSWMI_METHODID_BLOCK_READ_EC 0x42524543 /* BREC */ |
40 |
|
41 |
#define ASUS_WMI_BLOCK_READ_REGISTERS_MAX 0x10 /* from the ASUS DSDT source */ |
42 |
/* from the ASUS_WMI_BLOCK_READ_REGISTERS_MAX value */ |
43 |
#define ASUS_WMI_MAX_BUF_LEN 0x80 |
44 |
#define MAX_SENSOR_LABEL_LENGTH 0x10 |
45 |
|
46 |
static u32 hwmon_attributes[] = { |
47 |
[hwmon_chip] = HWMON_C_REGISTER_TZ, |
48 |
[hwmon_temp] = HWMON_T_INPUT | HWMON_T_LABEL, |
49 |
[hwmon_in] = HWMON_I_INPUT | HWMON_I_LABEL, |
50 |
[hwmon_curr] = HWMON_C_INPUT | HWMON_C_LABEL, |
51 |
[hwmon_fan] = HWMON_F_INPUT | HWMON_F_LABEL, |
52 |
}; |
53 |
|
54 |
struct asus_wmi_ec_sensor_address { |
55 |
u8 index; |
56 |
u8 bank; |
57 |
u8 size; |
58 |
}; |
59 |
|
60 |
#define MAKE_SENSOR_ADDRESS(size_i, bank_i, index_i) \ |
61 |
{ .size = size_i,\ |
62 |
.bank = bank_i,\ |
63 |
.index = index_i} |
64 |
|
65 |
struct ec_sensor_info { |
66 |
char label[MAX_SENSOR_LABEL_LENGTH]; |
67 |
enum hwmon_sensor_types type; |
68 |
struct asus_wmi_ec_sensor_address addr; |
69 |
}; |
70 |
|
71 |
#define EC_SENSOR(sensor_label, sensor_type, size, bank, index) \ |
72 |
{ .label = sensor_label,\ |
73 |
.type = sensor_type, \ |
74 |
.addr = MAKE_SENSOR_ADDRESS(size, bank, index) \ |
75 |
} |
76 |
|
77 |
enum known_ec_sensor { |
78 |
SENSOR_TEMP_CHIPSET, |
79 |
SENSOR_TEMP_CPU, |
80 |
SENSOR_TEMP_MB, |
81 |
SENSOR_TEMP_T_SENSOR, |
82 |
SENSOR_TEMP_VRM, |
83 |
SENSOR_FAN_CPU_OPT, |
84 |
SENSOR_FAN_CHIPSET, |
85 |
SENSOR_FAN_WATER_FLOW, |
86 |
SENSOR_CURR_CPU, |
87 |
SENSOR_TEMP_WATER_IN, |
88 |
SENSOR_TEMP_WATER_OUT, |
89 |
SENSOR_MAX |
90 |
}; |
91 |
|
92 |
/* |
93 |
* All the known sensors for ASUS EC controllers |
94 |
*/ |
95 |
static const struct ec_sensor_info known_ec_sensors[] = { |
96 |
[SENSOR_TEMP_CHIPSET] = EC_SENSOR("Chipset", hwmon_temp, 1, 0x00, 0x3a), |
97 |
[SENSOR_TEMP_CPU] = EC_SENSOR("CPU", hwmon_temp, 1, 0x00, 0x3b), |
98 |
[SENSOR_TEMP_MB] = EC_SENSOR("Motherboard", hwmon_temp, 1, 0x00, 0x3c), |
99 |
[SENSOR_TEMP_T_SENSOR] = EC_SENSOR("T_Sensor", hwmon_temp, 1, 0x00, 0x3d), |
100 |
[SENSOR_TEMP_VRM] = EC_SENSOR("VRM", hwmon_temp, 1, 0x00, 0x3e), |
101 |
[SENSOR_FAN_CPU_OPT] = EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xb0), |
102 |
[SENSOR_FAN_CHIPSET] = EC_SENSOR("Chipset", hwmon_fan, 2, 0x00, 0xb4), |
103 |
[SENSOR_FAN_WATER_FLOW] = EC_SENSOR("Water_Flow", hwmon_fan, 2, 0x00, 0xbc), |
104 |
[SENSOR_CURR_CPU] = EC_SENSOR("CPU", hwmon_curr, 1, 0x00, 0xf4), |
105 |
[SENSOR_TEMP_WATER_IN] = EC_SENSOR("Water_In", hwmon_temp, 1, 0x01, 0x00), |
106 |
[SENSOR_TEMP_WATER_OUT] = EC_SENSOR("Water_Out", hwmon_temp, 1, 0x01, 0x01), |
107 |
}; |
108 |
|
109 |
struct asus_wmi_data { |
110 |
const enum known_ec_sensor known_board_sensors[SENSOR_MAX + 1]; |
111 |
}; |
112 |
|
113 |
/* boards with EC support */ |
114 |
static struct asus_wmi_data sensors_board_PW_X570_P = { |
115 |
.known_board_sensors = { |
116 |
SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB, SENSOR_TEMP_VRM, |
117 |
SENSOR_FAN_CHIPSET, |
118 |
SENSOR_MAX |
119 |
}, |
120 |
}; |
121 |
|
122 |
static struct asus_wmi_data sensors_board_PW_X570_A = { |
123 |
.known_board_sensors = { |
124 |
SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB, SENSOR_TEMP_VRM, |
125 |
SENSOR_FAN_CHIPSET, |
126 |
SENSOR_CURR_CPU, |
127 |
SENSOR_MAX |
128 |
}, |
129 |
}; |
130 |
|
131 |
static struct asus_wmi_data sensors_board_R_C8H = { |
132 |
.known_board_sensors = { |
133 |
SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB, |
134 |
SENSOR_TEMP_T_SENSOR, SENSOR_TEMP_VRM, |
135 |
SENSOR_TEMP_WATER_IN, SENSOR_TEMP_WATER_OUT, |
136 |
SENSOR_FAN_CPU_OPT, SENSOR_FAN_CHIPSET, SENSOR_FAN_WATER_FLOW, |
137 |
SENSOR_CURR_CPU, |
138 |
SENSOR_MAX |
139 |
}, |
140 |
}; |
141 |
|
142 |
/* Same as Hero but without chipset fan */ |
143 |
static struct asus_wmi_data sensors_board_R_C8DH = { |
144 |
.known_board_sensors = { |
145 |
SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB, |
146 |
SENSOR_TEMP_T_SENSOR, SENSOR_TEMP_VRM, |
147 |
SENSOR_TEMP_WATER_IN, SENSOR_TEMP_WATER_OUT, |
148 |
SENSOR_FAN_CPU_OPT, SENSOR_FAN_WATER_FLOW, |
149 |
SENSOR_CURR_CPU, |
150 |
SENSOR_MAX |
151 |
}, |
152 |
}; |
153 |
|
154 |
/* Same as Hero but without water */ |
155 |
static struct asus_wmi_data sensors_board_R_C8F = { |
156 |
.known_board_sensors = { |
157 |
SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB, |
158 |
SENSOR_TEMP_T_SENSOR, SENSOR_TEMP_VRM, |
159 |
SENSOR_FAN_CPU_OPT, SENSOR_FAN_CHIPSET, |
160 |
SENSOR_CURR_CPU, |
161 |
SENSOR_MAX |
162 |
}, |
163 |
}; |
164 |
|
165 |
static struct asus_wmi_data sensors_board_RS_B550_E_G = { |
166 |
.known_board_sensors = { |
167 |
SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB, |
168 |
SENSOR_TEMP_T_SENSOR, SENSOR_TEMP_VRM, |
169 |
SENSOR_FAN_CPU_OPT, |
170 |
SENSOR_CURR_CPU, |
171 |
SENSOR_MAX |
172 |
}, |
173 |
}; |
174 |
|
175 |
static struct asus_wmi_data sensors_board_RS_X570_E_G = { |
176 |
.known_board_sensors = { |
177 |
SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB, |
178 |
SENSOR_TEMP_T_SENSOR, SENSOR_TEMP_VRM, |
179 |
SENSOR_FAN_CHIPSET, |
180 |
SENSOR_CURR_CPU, |
181 |
SENSOR_MAX |
182 |
}, |
183 |
}; |
184 |
|
185 |
static struct asus_wmi_data *board_sensors; |
186 |
|
187 |
static int __init asus_wmi_dmi_matched(const struct dmi_system_id *d) |
188 |
{ |
189 |
board_sensors = d->driver_data; |
190 |
return 0; |
191 |
} |
192 |
|
193 |
#define DMI_EXACT_MATCH_ASUS_BOARD_NAME(name, sensors) \ |
194 |
{ \ |
195 |
.callback = asus_wmi_dmi_matched, \ |
196 |
.matches = { \ |
197 |
DMI_EXACT_MATCH(DMI_BOARD_VENDOR, \ |
198 |
"ASUSTeK COMPUTER INC."), \ |
199 |
DMI_EXACT_MATCH(DMI_BOARD_NAME, name), \ |
200 |
}, \ |
201 |
.driver_data = sensors, \ |
202 |
} |
203 |
|
204 |
static const struct dmi_system_id asus_wmi_ec_dmi_table[] __initconst = { |
205 |
DMI_EXACT_MATCH_ASUS_BOARD_NAME("PRIME X570-PRO", &sensors_board_PW_X570_P), |
206 |
DMI_EXACT_MATCH_ASUS_BOARD_NAME("Pro WS X570-ACE", &sensors_board_PW_X570_A), |
207 |
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VIII DARK HERO", &sensors_board_R_C8DH), |
208 |
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VIII FORMULA", &sensors_board_R_C8F), |
209 |
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VIII HERO", &sensors_board_R_C8H), |
210 |
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B550-E GAMING", &sensors_board_RS_B550_E_G), |
211 |
DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X570-E GAMING", &sensors_board_RS_X570_E_G), |
212 |
{} |
213 |
}; |
214 |
MODULE_DEVICE_TABLE(dmi, asus_wmi_ec_dmi_table); |
215 |
|
216 |
struct ec_sensor { |
217 |
enum known_ec_sensor info_index; |
218 |
u32 cached_value; |
219 |
}; |
220 |
|
221 |
/** |
222 |
* struct asus_wmi_ec_info - sensor info. |
223 |
* @sensors: list of sensors. |
224 |
* @read_arg: UTF-16 string to pass to BRxx() WMI function. |
225 |
* @read_buffer: decoded output from WMI result. |
226 |
* @nr_sensors: number of board EC sensors. |
227 |
* @nr_registers: number of EC registers to read (sensor might span more than |
228 |
* 1 register). |
229 |
* @last_updated: in jiffies. |
230 |
*/ |
231 |
struct asus_wmi_ec_info { |
232 |
struct ec_sensor sensors[SENSOR_MAX]; |
233 |
char read_arg[((ASUS_WMI_BLOCK_READ_REGISTERS_MAX * 4) + 1) * 2]; |
234 |
u8 read_buffer[ASUS_WMI_BLOCK_READ_REGISTERS_MAX]; |
235 |
unsigned int nr_sensors; |
236 |
unsigned int nr_registers; |
237 |
unsigned long last_updated; |
238 |
}; |
239 |
|
240 |
struct asus_wmi_sensors { |
241 |
/* lock access to instrnal cache */ |
242 |
struct mutex lock; |
243 |
struct asus_wmi_ec_info ec; |
244 |
}; |
245 |
|
246 |
static int asus_wmi_ec_fill_board_sensors(struct asus_wmi_ec_info *ec) |
247 |
{ |
248 |
const enum known_ec_sensor *bsi; |
249 |
struct ec_sensor *s = ec->sensors; |
250 |
int i; |
251 |
|
252 |
if (!board_sensors) |
253 |
return -ENODEV; |
254 |
|
255 |
bsi = board_sensors->known_board_sensors; |
256 |
ec->nr_sensors = 0; |
257 |
ec->nr_registers = 0; |
258 |
|
259 |
for (i = 0; i < SENSOR_MAX && bsi[i] != SENSOR_MAX; i++) { |
260 |
s[i].info_index = bsi[i]; |
261 |
s[i].cached_value = 0; |
262 |
ec->nr_sensors++; |
263 |
ec->nr_registers += known_ec_sensors[bsi[i]].addr.size; |
264 |
} |
265 |
|
266 |
return 0; |
267 |
} |
268 |
|
269 |
/* |
270 |
* The next four functions converts to/from BRxx string argument format |
271 |
* The format of the string is as follows: |
272 |
* The string consists of two-byte UTF-16 characters |
273 |
* The value of the very first byte int the string is equal to the total length |
274 |
* of the next string in bytes, thus excluding the first two-byte character |
275 |
* The rest of the string encodes pairs of (bank, index) pairs, where both |
276 |
* values are byte-long (0x00 to 0xFF) |
277 |
* Numbers are encoded as UTF-16 hex values |
278 |
*/ |
279 |
static void asus_wmi_ec_decode_reply_buffer(const u8 *inp, u8 *out) |
280 |
{ |
281 |
unsigned int len = ACPI_MIN(ASUS_WMI_MAX_BUF_LEN, inp[0] / 4); |
282 |
char buffer[ASUS_WMI_MAX_BUF_LEN * 2]; |
283 |
const char *pos = buffer; |
284 |
const u8 *data = inp + 2; |
285 |
unsigned int i; |
286 |
|
287 |
utf16s_to_utf8s((wchar_t *)data, len * 2, UTF16_LITTLE_ENDIAN, buffer, len * 2); |
288 |
|
289 |
for (i = 0; i < len; i++, pos += 2) |
290 |
out[i] = (hex_to_bin(pos[0]) << 4) + hex_to_bin(pos[1]); |
291 |
} |
292 |
|
293 |
static void asus_wmi_ec_encode_registers(u16 *registers, u8 len, char *out) |
294 |
{ |
295 |
char buffer[ASUS_WMI_MAX_BUF_LEN * 2]; |
296 |
char *pos = buffer; |
297 |
unsigned int i; |
298 |
u8 byte; |
299 |
|
300 |
*out++ = len * 8; |
301 |
*out++ = 0; |
302 |
|
303 |
for (i = 0; i < len; i++) { |
304 |
byte = registers[i] >> 8; |
305 |
*pos = hex_asc_hi(byte); |
306 |
pos++; |
307 |
*pos = hex_asc_lo(byte); |
308 |
pos++; |
309 |
byte = registers[i]; |
310 |
*pos = hex_asc_hi(byte); |
311 |
pos++; |
312 |
*pos = hex_asc_lo(byte); |
313 |
pos++; |
314 |
} |
315 |
|
316 |
utf8s_to_utf16s(buffer, len * 4, UTF16_LITTLE_ENDIAN, (wchar_t *)out, len * 4); |
317 |
} |
318 |
|
319 |
static void asus_wmi_ec_make_block_read_query(struct asus_wmi_ec_info *ec) |
320 |
{ |
321 |
u16 registers[ASUS_WMI_BLOCK_READ_REGISTERS_MAX]; |
322 |
const struct ec_sensor_info *si; |
323 |
int i, j, register_idx = 0; |
324 |
|
325 |
/* |
326 |
* if we can get values for all the registers in a single query, |
327 |
* the query will not change from call to call |
328 |
*/ |
329 |
if (ec->nr_registers <= ASUS_WMI_BLOCK_READ_REGISTERS_MAX && |
330 |
ec->read_arg[0] > 0) { |
331 |
/* no need to update */ |
332 |
return; |
333 |
} |
334 |
|
335 |
for (i = 0; i < ec->nr_sensors; i++) { |
336 |
si = &known_ec_sensors[ec->sensors[i].info_index]; |
337 |
for (j = 0; j < si->addr.size; |
338 |
j++, register_idx++) { |
339 |
registers[register_idx] = (si->addr.bank << 8) + si->addr.index + j; |
340 |
} |
341 |
} |
342 |
|
343 |
asus_wmi_ec_encode_registers(registers, ec->nr_registers, ec->read_arg); |
344 |
} |
345 |
|
346 |
static int asus_wmi_ec_block_read(u32 method_id, char *query, u8 *out) |
347 |
{ |
348 |
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, |
349 |
NULL }; |
350 |
struct acpi_buffer input; |
351 |
union acpi_object *obj; |
352 |
acpi_status status; |
353 |
|
354 |
/* the first byte of the BRxx() argument string has to be the string size */ |
355 |
input.length = (acpi_size)query[0] + 2; |
356 |
input.pointer = query; |
357 |
status = wmi_evaluate_method(ASUSWMI_MONITORING_GUID, 0, method_id, &input, |
358 |
&output); |
359 |
if (ACPI_FAILURE(status)) |
360 |
return -EIO; |
361 |
|
362 |
obj = output.pointer; |
363 |
if (!obj || obj->type != ACPI_TYPE_BUFFER) { |
364 |
if (!obj) |
365 |
acpi_os_free(output.pointer); |
366 |
|
367 |
return -EIO; |
368 |
} |
369 |
asus_wmi_ec_decode_reply_buffer(obj->buffer.pointer, out); |
370 |
acpi_os_free(output.pointer); |
371 |
return 0; |
372 |
} |
373 |
|
374 |
static int asus_wmi_ec_update_ec_sensors(struct asus_wmi_ec_info *ec) |
375 |
{ |
376 |
const struct ec_sensor_info *si; |
377 |
struct ec_sensor *s; |
378 |
|
379 |
int status; |
380 |
u8 i_sensor, read_reg_ct; |
381 |
|
382 |
asus_wmi_ec_make_block_read_query(ec); |
383 |
status = asus_wmi_ec_block_read(ASUSWMI_METHODID_BLOCK_READ_EC, |
384 |
ec->read_arg, |
385 |
ec->read_buffer); |
386 |
if (status) |
387 |
return status; |
388 |
|
389 |
read_reg_ct = 0; |
390 |
for (i_sensor = 0; i_sensor < ec->nr_sensors; i_sensor++) { |
391 |
s = &ec->sensors[i_sensor]; |
392 |
si = &known_ec_sensors[s->info_index]; |
393 |
|
394 |
switch (si->addr.size) { |
395 |
case 1: |
396 |
s->cached_value = ec->read_buffer[read_reg_ct]; |
397 |
break; |
398 |
case 2: |
399 |
s->cached_value = get_unaligned_be16(&ec->read_buffer[read_reg_ct]); |
400 |
break; |
401 |
case 4: |
402 |
s->cached_value = get_unaligned_be32(&ec->read_buffer[read_reg_ct]); |
403 |
break; |
404 |
default: |
405 |
s->cached_value = 0; |
406 |
} |
407 |
read_reg_ct += si->addr.size; |
408 |
} |
409 |
return 0; |
410 |
} |
411 |
|
412 |
static int asus_wmi_ec_scale_sensor_value(u32 value, int data_type) |
413 |
{ |
414 |
switch (data_type) { |
415 |
case hwmon_curr: |
416 |
case hwmon_temp: |
417 |
case hwmon_in: |
418 |
return value * KILO; |
419 |
default: |
420 |
return value; |
421 |
} |
422 |
} |
423 |
|
424 |
static int asus_wmi_ec_find_sensor_index(const struct asus_wmi_ec_info *ec, |
425 |
enum hwmon_sensor_types type, int channel) |
426 |
{ |
427 |
int i; |
428 |
|
429 |
for (i = 0; i < ec->nr_sensors; i++) { |
430 |
if (known_ec_sensors[ec->sensors[i].info_index].type == type) { |
431 |
if (channel == 0) |
432 |
return i; |
433 |
|
434 |
channel--; |
435 |
} |
436 |
} |
437 |
return -EINVAL; |
438 |
} |
439 |
|
440 |
static int asus_wmi_ec_get_cached_value_or_update(int sensor_index, |
441 |
struct asus_wmi_sensors *state, |
442 |
u32 *value) |
443 |
{ |
444 |
int ret; |
445 |
|
446 |
if (time_after(jiffies, state->ec.last_updated + HZ)) { |
447 |
ret = asus_wmi_ec_update_ec_sensors(&state->ec); |
448 |
if (ret) |
449 |
return ret; |
450 |
|
451 |
state->ec.last_updated = jiffies; |
452 |
} |
453 |
|
454 |
*value = state->ec.sensors[sensor_index].cached_value; |
455 |
return 0; |
456 |
} |
457 |
|
458 |
/* |
459 |
* Now follow the functions that implement the hwmon interface |
460 |
*/ |
461 |
|
462 |
static int asus_wmi_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type, |
463 |
u32 attr, int channel, long *val) |
464 |
{ |
465 |
struct asus_wmi_sensors *sensor_data = dev_get_drvdata(dev); |
466 |
int ret, sidx, info_index; |
467 |
u32 value = 0; |
468 |
|
469 |
sidx = asus_wmi_ec_find_sensor_index(&sensor_data->ec, type, channel); |
470 |
if (sidx < 0) |
471 |
return sidx; |
472 |
|
473 |
mutex_lock(&sensor_data->lock); |
474 |
ret = asus_wmi_ec_get_cached_value_or_update(sidx, sensor_data, &value); |
475 |
mutex_unlock(&sensor_data->lock); |
476 |
if (ret) |
477 |
return ret; |
478 |
|
479 |
info_index = sensor_data->ec.sensors[sidx].info_index; |
480 |
*val = asus_wmi_ec_scale_sensor_value(value, |
481 |
known_ec_sensors[info_index].type); |
482 |
|
483 |
return ret; |
484 |
} |
485 |
|
486 |
static int asus_wmi_ec_hwmon_read_string(struct device *dev, |
487 |
enum hwmon_sensor_types type, u32 attr, |
488 |
int channel, const char **str) |
489 |
{ |
490 |
struct asus_wmi_sensors *sensor_data = dev_get_drvdata(dev); |
491 |
int sensor_index; |
492 |
|
493 |
sensor_index = asus_wmi_ec_find_sensor_index(&sensor_data->ec, type, channel); |
494 |
*str = known_ec_sensors[sensor_data->ec.sensors[sensor_index].info_index].label; |
495 |
|
496 |
return 0; |
497 |
} |
498 |
|
499 |
static umode_t asus_wmi_ec_hwmon_is_visible(const void *drvdata, |
500 |
enum hwmon_sensor_types type, u32 attr, |
501 |
int channel) |
502 |
{ |
503 |
int index; |
504 |
const struct asus_wmi_sensors *sensor_data = drvdata; |
505 |
|
506 |
index = asus_wmi_ec_find_sensor_index(&sensor_data->ec, type, channel); |
507 |
|
508 |
return index == 0xff ? 0 : 0444; |
509 |
} |
510 |
|
511 |
static int asus_wmi_hwmon_add_chan_info(struct hwmon_channel_info *asus_wmi_hwmon_chan, |
512 |
struct device *dev, int num, |
513 |
enum hwmon_sensor_types type, u32 config) |
514 |
{ |
515 |
u32 *cfg; |
516 |
|
517 |
cfg = devm_kcalloc(dev, num + 1, sizeof(*cfg), GFP_KERNEL); |
518 |
if (!cfg) |
519 |
return -ENOMEM; |
520 |
|
521 |
asus_wmi_hwmon_chan->type = type; |
522 |
asus_wmi_hwmon_chan->config = cfg; |
523 |
memset32(cfg, config, num); |
524 |
|
525 |
return 0; |
526 |
} |
527 |
|
528 |
static const struct hwmon_ops asus_wmi_ec_hwmon_ops = { |
529 |
.is_visible = asus_wmi_ec_hwmon_is_visible, |
530 |
.read = asus_wmi_ec_hwmon_read, |
531 |
.read_string = asus_wmi_ec_hwmon_read_string, |
532 |
}; |
533 |
|
534 |
static struct hwmon_chip_info asus_wmi_ec_chip_info = { |
535 |
.ops = &asus_wmi_ec_hwmon_ops, |
536 |
}; |
537 |
|
538 |
static int asus_wmi_ec_configure_sensor_setup(struct platform_device *pdev, |
539 |
struct asus_wmi_sensors *sensor_data) |
540 |
{ |
541 |
struct hwmon_channel_info *asus_wmi_hwmon_chan; |
542 |
const struct hwmon_channel_info **ptr_asus_wmi_ci; |
543 |
int nr_count[hwmon_max] = { 0 }, nr_types = 0; |
544 |
const struct hwmon_chip_info *chip_info; |
545 |
struct device *dev = &pdev->dev; |
546 |
const struct ec_sensor_info *si; |
547 |
enum hwmon_sensor_types type; |
548 |
struct device *hwdev; |
549 |
int i, ret; |
550 |
|
551 |
ret = asus_wmi_ec_fill_board_sensors(&sensor_data->ec); |
552 |
if (ret) |
553 |
return ret; |
554 |
|
555 |
if (!sensor_data->ec.nr_sensors) |
556 |
return -ENODEV; |
557 |
|
558 |
for (i = 0; i < sensor_data->ec.nr_sensors; i++) { |
559 |
si = &known_ec_sensors[sensor_data->ec.sensors[i].info_index]; |
560 |
if (!nr_count[si->type]) |
561 |
nr_types++; |
562 |
nr_count[si->type]++; |
563 |
} |
564 |
|
565 |
if (nr_count[hwmon_temp]) { |
566 |
nr_count[hwmon_chip]++; |
567 |
nr_types++; |
568 |
} |
569 |
|
570 |
asus_wmi_hwmon_chan = devm_kcalloc(dev, nr_types, |
571 |
sizeof(*asus_wmi_hwmon_chan), |
572 |
GFP_KERNEL); |
573 |
if (!asus_wmi_hwmon_chan) |
574 |
return -ENOMEM; |
575 |
|
576 |
ptr_asus_wmi_ci = devm_kcalloc(dev, nr_types + 1, |
577 |
sizeof(*ptr_asus_wmi_ci), GFP_KERNEL); |
578 |
if (!ptr_asus_wmi_ci) |
579 |
return -ENOMEM; |
580 |
|
581 |
asus_wmi_ec_chip_info.info = ptr_asus_wmi_ci; |
582 |
chip_info = &asus_wmi_ec_chip_info; |
583 |
|
584 |
for (type = 0; type < hwmon_max; type++) { |
585 |
if (!nr_count[type]) |
586 |
continue; |
587 |
|
588 |
ret = asus_wmi_hwmon_add_chan_info(asus_wmi_hwmon_chan, dev, |
589 |
nr_count[type], type, |
590 |
hwmon_attributes[type]); |
591 |
if (ret) |
592 |
return ret; |
593 |
|
594 |
*ptr_asus_wmi_ci++ = asus_wmi_hwmon_chan++; |
595 |
} |
596 |
|
597 |
dev_dbg(&pdev->dev, "board has %d EC sensors that span %d registers", |
598 |
sensor_data->ec.nr_sensors, |
599 |
sensor_data->ec.nr_registers); |
600 |
|
601 |
hwdev = devm_hwmon_device_register_with_info(dev, KBUILD_MODNAME, |
602 |
sensor_data, chip_info, NULL); |
603 |
|
604 |
return PTR_ERR_OR_ZERO(hwdev); |
605 |
} |
606 |
|
607 |
static int asus_wmi_probe(struct platform_device *pdev) |
608 |
{ |
609 |
struct asus_wmi_sensors *sensor_data; |
610 |
struct device *dev = &pdev->dev; |
611 |
|
612 |
sensor_data = devm_kzalloc(dev, sizeof(struct asus_wmi_sensors), |
613 |
GFP_KERNEL); |
614 |
if (!sensor_data) |
615 |
return -ENOMEM; |
616 |
|
617 |
mutex_init(&sensor_data->lock); |
618 |
|
619 |
platform_set_drvdata(pdev, sensor_data); |
620 |
|
621 |
/* ec init */ |
622 |
return asus_wmi_ec_configure_sensor_setup(pdev, |
623 |
sensor_data); |
624 |
} |
625 |
|
626 |
static struct platform_driver asus_wmi_sensors_platform_driver = { |
627 |
.driver = { |
628 |
.name = "asus-wmi-sensors", |
629 |
}, |
630 |
.probe = asus_wmi_probe, |
631 |
}; |
632 |
|
633 |
static struct platform_device *sensors_pdev; |
634 |
|
635 |
static int __init asus_wmi_init(void) |
636 |
{ |
637 |
if (!dmi_check_system(asus_wmi_ec_dmi_table)) |
638 |
return -ENODEV; |
639 |
|
640 |
sensors_pdev = platform_create_bundle(&asus_wmi_sensors_platform_driver, |
641 |
asus_wmi_probe, |
642 |
NULL, 0, |
643 |
NULL, 0); |
644 |
|
645 |
return PTR_ERR_OR_ZERO(sensors_pdev); |
646 |
} |
647 |
module_init(asus_wmi_init); |
648 |
|
649 |
static void __exit asus_wmi_exit(void) |
650 |
{ |
651 |
platform_device_unregister(sensors_pdev); |
652 |
platform_driver_unregister(&asus_wmi_sensors_platform_driver); |
653 |
} |
654 |
module_exit(asus_wmi_exit); |
655 |
|
656 |
MODULE_AUTHOR("Ed Brindley <kernel@maidavale.org>"); |
657 |
MODULE_AUTHOR("Eugene Shalygin <eugene.shalygin@gmail.com>"); |
658 |
MODULE_DESCRIPTION("Asus WMI Sensors Driver"); |
659 |
MODULE_LICENSE("GPL"); |