Commit | Line | Data |
---|---|---|
74ba9207 | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
1da177e4 LT |
2 | /* |
3 | * pc87360.c - Part of lm_sensors, Linux kernel modules | |
4 | * for hardware monitoring | |
7c81c60f | 5 | * Copyright (C) 2004, 2007 Jean Delvare <jdelvare@suse.de> |
1da177e4 LT |
6 | * |
7 | * Copied from smsc47m1.c: | |
8 | * Copyright (C) 2002 Mark D. Studebaker <mdsxyz123@yahoo.com> | |
9 | * | |
1da177e4 LT |
10 | * Supports the following chips: |
11 | * | |
12 | * Chip #vin #fan #pwm #temp devid | |
13 | * PC87360 - 2 2 - 0xE1 | |
14 | * PC87363 - 2 2 - 0xE8 | |
15 | * PC87364 - 3 3 - 0xE4 | |
16 | * PC87365 11 3 3 2 0xE5 | |
17 | * PC87366 11 3 3 3-4 0xE9 | |
18 | * | |
19 | * This driver assumes that no more than one chip is present, and one of | |
20 | * the standard Super-I/O addresses is used (0x2E/0x2F or 0x4E/0x4F). | |
21 | */ | |
22 | ||
9e991c6f JP |
23 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
24 | ||
1da177e4 LT |
25 | #include <linux/module.h> |
26 | #include <linux/init.h> | |
27 | #include <linux/slab.h> | |
28 | #include <linux/jiffies.h> | |
f641b588 | 29 | #include <linux/platform_device.h> |
943b0830 | 30 | #include <linux/hwmon.h> |
f0986bd8 | 31 | #include <linux/hwmon-sysfs.h> |
303760b4 | 32 | #include <linux/hwmon-vid.h> |
943b0830 | 33 | #include <linux/err.h> |
9a61bf63 | 34 | #include <linux/mutex.h> |
b9acb64a | 35 | #include <linux/acpi.h> |
6055fae8 | 36 | #include <linux/io.h> |
1da177e4 | 37 | |
02e05005 UKK |
38 | #define DRIVER_NAME "pc87360" |
39 | ||
070affa8 UKK |
40 | /* (temp & vin) channel conversion status register flags (pdf sec.11.5.12) */ |
41 | #define CHAN_CNVRTD 0x80 /* new data ready */ | |
42 | #define CHAN_ENA 0x01 /* enabled channel (temp or vin) */ | |
43 | #define CHAN_ALM_ENA 0x10 /* propagate to alarms-reg ?? (chk val!) */ | |
44 | #define CHAN_READY (CHAN_ENA|CHAN_CNVRTD) /* sample ready mask */ | |
45 | ||
46 | #define TEMP_OTS_OE 0x20 /* OTS Output Enable */ | |
47 | #define VIN_RW1C_MASK (CHAN_READY|CHAN_ALM_MAX|CHAN_ALM_MIN) /* 0x87 */ | |
48 | #define TEMP_RW1C_MASK (VIN_RW1C_MASK|TEMP_ALM_CRIT|TEMP_FAULT) /* 0xCF */ | |
49 | ||
1da177e4 | 50 | static u8 devid; |
f641b588 | 51 | static struct platform_device *pdev; |
2d8672c5 | 52 | static unsigned short extra_isa[3]; |
1da177e4 LT |
53 | static u8 confreg[4]; |
54 | ||
1da177e4 LT |
55 | static int init = 1; |
56 | module_param(init, int, 0); | |
57 | MODULE_PARM_DESC(init, | |
449a7a07 GR |
58 | "Chip initialization level:\n" |
59 | " 0: None\n" | |
60 | "*1: Forcibly enable internal voltage and temperature channels, except in9\n" | |
61 | " 2: Forcibly enable all voltage and temperature channels, except in9\n" | |
62 | " 3: Forcibly enable all voltage and temperature channels, including in9"); | |
1da177e4 | 63 | |
67b671bc JD |
64 | static unsigned short force_id; |
65 | module_param(force_id, ushort, 0); | |
66 | MODULE_PARM_DESC(force_id, "Override the detected device ID"); | |
67 | ||
1da177e4 LT |
68 | /* |
69 | * Super-I/O registers and operations | |
70 | */ | |
71 | ||
72 | #define DEV 0x07 /* Register: Logical device select */ | |
73 | #define DEVID 0x20 /* Register: Device ID */ | |
74 | #define ACT 0x30 /* Register: Device activation */ | |
75 | #define BASE 0x60 /* Register: Base address */ | |
76 | ||
77 | #define FSCM 0x09 /* Logical device: fans */ | |
78 | #define VLM 0x0d /* Logical device: voltages */ | |
79 | #define TMS 0x0e /* Logical device: temperatures */ | |
2a32ec25 JC |
80 | #define LDNI_MAX 3 |
81 | static const u8 logdev[LDNI_MAX] = { FSCM, VLM, TMS }; | |
1da177e4 LT |
82 | |
83 | #define LD_FAN 0 | |
84 | #define LD_IN 1 | |
85 | #define LD_TEMP 2 | |
86 | ||
87 | static inline void superio_outb(int sioaddr, int reg, int val) | |
88 | { | |
89 | outb(reg, sioaddr); | |
449a7a07 | 90 | outb(val, sioaddr + 1); |
1da177e4 LT |
91 | } |
92 | ||
93 | static inline int superio_inb(int sioaddr, int reg) | |
94 | { | |
95 | outb(reg, sioaddr); | |
449a7a07 | 96 | return inb(sioaddr + 1); |
1da177e4 LT |
97 | } |
98 | ||
99 | static inline void superio_exit(int sioaddr) | |
100 | { | |
101 | outb(0x02, sioaddr); | |
449a7a07 | 102 | outb(0x02, sioaddr + 1); |
1da177e4 LT |
103 | } |
104 | ||
105 | /* | |
106 | * Logical devices | |
107 | */ | |
108 | ||
109 | #define PC87360_EXTENT 0x10 | |
110 | #define PC87365_REG_BANK 0x09 | |
111 | #define NO_BANK 0xff | |
112 | ||
113 | /* | |
114 | * Fan registers and conversions | |
115 | */ | |
116 | ||
117 | /* nr has to be 0 or 1 (PC87360/87363) or 2 (PC87364/87365/87366) */ | |
118 | #define PC87360_REG_PRESCALE(nr) (0x00 + 2 * (nr)) | |
119 | #define PC87360_REG_PWM(nr) (0x01 + 2 * (nr)) | |
120 | #define PC87360_REG_FAN_MIN(nr) (0x06 + 3 * (nr)) | |
121 | #define PC87360_REG_FAN(nr) (0x07 + 3 * (nr)) | |
122 | #define PC87360_REG_FAN_STATUS(nr) (0x08 + 3 * (nr)) | |
123 | ||
449a7a07 GR |
124 | #define FAN_FROM_REG(val, div) ((val) == 0 ? 0 : \ |
125 | 480000 / ((val) * (div))) | |
126 | #define FAN_TO_REG(val, div) ((val) <= 100 ? 0 : \ | |
127 | 480000 / ((val) * (div))) | |
128 | #define FAN_DIV_FROM_REG(val) (1 << (((val) >> 5) & 0x03)) | |
1da177e4 LT |
129 | #define FAN_STATUS_FROM_REG(val) ((val) & 0x07) |
130 | ||
449a7a07 GR |
131 | #define FAN_CONFIG_MONITOR(val, nr) (((val) >> (2 + (nr) * 3)) & 1) |
132 | #define FAN_CONFIG_CONTROL(val, nr) (((val) >> (3 + (nr) * 3)) & 1) | |
133 | #define FAN_CONFIG_INVERT(val, nr) (((val) >> (4 + (nr) * 3)) & 1) | |
1da177e4 | 134 | |
449a7a07 | 135 | #define PWM_FROM_REG(val, inv) ((inv) ? 255 - (val) : (val)) |
1da177e4 LT |
136 | static inline u8 PWM_TO_REG(int val, int inv) |
137 | { | |
138 | if (inv) | |
139 | val = 255 - val; | |
140 | if (val < 0) | |
141 | return 0; | |
142 | if (val > 255) | |
143 | return 255; | |
144 | return val; | |
145 | } | |
146 | ||
147 | /* | |
148 | * Voltage registers and conversions | |
149 | */ | |
150 | ||
151 | #define PC87365_REG_IN_CONVRATE 0x07 | |
152 | #define PC87365_REG_IN_CONFIG 0x08 | |
153 | #define PC87365_REG_IN 0x0B | |
154 | #define PC87365_REG_IN_MIN 0x0D | |
155 | #define PC87365_REG_IN_MAX 0x0C | |
156 | #define PC87365_REG_IN_STATUS 0x0A | |
157 | #define PC87365_REG_IN_ALARMS1 0x00 | |
158 | #define PC87365_REG_IN_ALARMS2 0x01 | |
159 | #define PC87365_REG_VID 0x06 | |
160 | ||
449a7a07 GR |
161 | #define IN_FROM_REG(val, ref) (((val) * (ref) + 128) / 256) |
162 | #define IN_TO_REG(val, ref) ((val) < 0 ? 0 : \ | |
163 | (val) * 256 >= (ref) * 255 ? 255 : \ | |
164 | ((val) * 256 + (ref) / 2) / (ref)) | |
1da177e4 LT |
165 | |
166 | /* | |
167 | * Temperature registers and conversions | |
168 | */ | |
169 | ||
170 | #define PC87365_REG_TEMP_CONFIG 0x08 | |
171 | #define PC87365_REG_TEMP 0x0B | |
172 | #define PC87365_REG_TEMP_MIN 0x0D | |
173 | #define PC87365_REG_TEMP_MAX 0x0C | |
174 | #define PC87365_REG_TEMP_CRIT 0x0E | |
175 | #define PC87365_REG_TEMP_STATUS 0x0A | |
176 | #define PC87365_REG_TEMP_ALARMS 0x00 | |
177 | ||
178 | #define TEMP_FROM_REG(val) ((val) * 1000) | |
179 | #define TEMP_TO_REG(val) ((val) < -55000 ? -55 : \ | |
180 | (val) > 127000 ? 127 : \ | |
181 | (val) < 0 ? ((val) - 500) / 1000 : \ | |
182 | ((val) + 500) / 1000) | |
183 | ||
184 | /* | |
f641b588 | 185 | * Device data |
1da177e4 LT |
186 | */ |
187 | ||
188 | struct pc87360_data { | |
f641b588 | 189 | const char *name; |
1beeffe4 | 190 | struct device *hwmon_dev; |
9a61bf63 IM |
191 | struct mutex lock; |
192 | struct mutex update_lock; | |
952a11ca | 193 | bool valid; /* true if following fields are valid */ |
1da177e4 LT |
194 | unsigned long last_updated; /* In jiffies */ |
195 | ||
196 | int address[3]; | |
197 | ||
198 | u8 fannr, innr, tempnr; | |
199 | ||
200 | u8 fan[3]; /* Register value */ | |
201 | u8 fan_min[3]; /* Register value */ | |
202 | u8 fan_status[3]; /* Register value */ | |
203 | u8 pwm[3]; /* Register value */ | |
204 | u16 fan_conf; /* Configuration register values, combined */ | |
205 | ||
206 | u16 in_vref; /* 1 mV/bit */ | |
207 | u8 in[14]; /* Register value */ | |
208 | u8 in_min[14]; /* Register value */ | |
209 | u8 in_max[14]; /* Register value */ | |
210 | u8 in_crit[3]; /* Register value */ | |
211 | u8 in_status[14]; /* Register value */ | |
212 | u16 in_alarms; /* Register values, combined, masked */ | |
213 | u8 vid_conf; /* Configuration register value */ | |
214 | u8 vrm; | |
215 | u8 vid; /* Register value */ | |
216 | ||
217 | s8 temp[3]; /* Register value */ | |
218 | s8 temp_min[3]; /* Register value */ | |
219 | s8 temp_max[3]; /* Register value */ | |
220 | s8 temp_crit[3]; /* Register value */ | |
221 | u8 temp_status[3]; /* Register value */ | |
222 | u8 temp_alarms; /* Register value, masked */ | |
223 | }; | |
224 | ||
225 | /* | |
070affa8 UKK |
226 | * ldi is the logical device index |
227 | * bank is for voltages and temperatures only | |
1da177e4 | 228 | */ |
1da177e4 | 229 | static int pc87360_read_value(struct pc87360_data *data, u8 ldi, u8 bank, |
070affa8 UKK |
230 | u8 reg) |
231 | { | |
232 | int res; | |
1da177e4 | 233 | |
070affa8 UKK |
234 | mutex_lock(&(data->lock)); |
235 | if (bank != NO_BANK) | |
236 | outb_p(bank, data->address[ldi] + PC87365_REG_BANK); | |
237 | res = inb_p(data->address[ldi] + reg); | |
238 | mutex_unlock(&(data->lock)); | |
1da177e4 | 239 | |
070affa8 | 240 | return res; |
f0986bd8 | 241 | } |
070affa8 UKK |
242 | |
243 | static void pc87360_write_value(struct pc87360_data *data, u8 ldi, u8 bank, | |
244 | u8 reg, u8 value) | |
f0986bd8 | 245 | { |
070affa8 UKK |
246 | mutex_lock(&(data->lock)); |
247 | if (bank != NO_BANK) | |
248 | outb_p(bank, data->address[ldi] + PC87365_REG_BANK); | |
249 | outb_p(value, data->address[ldi] + reg); | |
250 | mutex_unlock(&(data->lock)); | |
f0986bd8 | 251 | } |
070affa8 UKK |
252 | |
253 | static void pc87360_autodiv(struct device *dev, int nr) | |
f0986bd8 | 254 | { |
f641b588 | 255 | struct pc87360_data *data = dev_get_drvdata(dev); |
070affa8 | 256 | u8 old_min = data->fan_min[nr]; |
11be27ea | 257 | |
070affa8 UKK |
258 | /* Increase clock divider if needed and possible */ |
259 | if ((data->fan_status[nr] & 0x04) /* overflow flag */ | |
260 | || (data->fan[nr] >= 224)) { /* next to overflow */ | |
261 | if ((data->fan_status[nr] & 0x60) != 0x60) { | |
262 | data->fan_status[nr] += 0x20; | |
263 | data->fan_min[nr] >>= 1; | |
264 | data->fan[nr] >>= 1; | |
265 | dev_dbg(dev, | |
266 | "Increasing clock divider to %d for fan %d\n", | |
267 | FAN_DIV_FROM_REG(data->fan_status[nr]), nr + 1); | |
268 | } | |
269 | } else { | |
270 | /* Decrease clock divider if possible */ | |
271 | while (!(data->fan_min[nr] & 0x80) /* min "nails" divider */ | |
272 | && data->fan[nr] < 85 /* bad accuracy */ | |
273 | && (data->fan_status[nr] & 0x60) != 0x00) { | |
274 | data->fan_status[nr] -= 0x20; | |
275 | data->fan_min[nr] <<= 1; | |
276 | data->fan[nr] <<= 1; | |
277 | dev_dbg(dev, | |
278 | "Decreasing clock divider to %d for fan %d\n", | |
279 | FAN_DIV_FROM_REG(data->fan_status[nr]), | |
280 | nr + 1); | |
281 | } | |
11be27ea | 282 | } |
1da177e4 | 283 | |
070affa8 UKK |
284 | /* Write new fan min if it changed */ |
285 | if (old_min != data->fan_min[nr]) { | |
286 | pc87360_write_value(data, LD_FAN, NO_BANK, | |
287 | PC87360_REG_FAN_MIN(nr), | |
288 | data->fan_min[nr]); | |
289 | } | |
bce2778d | 290 | } |
941c5c05 | 291 | |
070affa8 | 292 | static struct pc87360_data *pc87360_update_device(struct device *dev) |
f0986bd8 | 293 | { |
f641b588 | 294 | struct pc87360_data *data = dev_get_drvdata(dev); |
070affa8 | 295 | u8 i; |
f0986bd8 | 296 | |
9a61bf63 | 297 | mutex_lock(&data->update_lock); |
1da177e4 | 298 | |
070affa8 UKK |
299 | if (time_after(jiffies, data->last_updated + HZ * 2) || !data->valid) { |
300 | dev_dbg(dev, "Data update\n"); | |
bce2778d | 301 | |
070affa8 UKK |
302 | /* Fans */ |
303 | for (i = 0; i < data->fannr; i++) { | |
304 | if (FAN_CONFIG_MONITOR(data->fan_conf, i)) { | |
305 | data->fan_status[i] = | |
306 | pc87360_read_value(data, LD_FAN, | |
307 | NO_BANK, PC87360_REG_FAN_STATUS(i)); | |
308 | data->fan[i] = pc87360_read_value(data, LD_FAN, | |
309 | NO_BANK, PC87360_REG_FAN(i)); | |
310 | data->fan_min[i] = pc87360_read_value(data, | |
311 | LD_FAN, NO_BANK, | |
312 | PC87360_REG_FAN_MIN(i)); | |
313 | /* Change clock divider if needed */ | |
314 | pc87360_autodiv(dev, i); | |
315 | /* Clear bits and write new divider */ | |
316 | pc87360_write_value(data, LD_FAN, NO_BANK, | |
317 | PC87360_REG_FAN_STATUS(i), | |
318 | data->fan_status[i]); | |
319 | } | |
320 | if (FAN_CONFIG_CONTROL(data->fan_conf, i)) | |
321 | data->pwm[i] = pc87360_read_value(data, LD_FAN, | |
322 | NO_BANK, PC87360_REG_PWM(i)); | |
323 | } | |
941c5c05 | 324 | |
070affa8 UKK |
325 | /* Voltages */ |
326 | for (i = 0; i < data->innr; i++) { | |
327 | data->in_status[i] = pc87360_read_value(data, LD_IN, i, | |
328 | PC87365_REG_IN_STATUS); | |
329 | /* Clear bits */ | |
330 | pc87360_write_value(data, LD_IN, i, | |
331 | PC87365_REG_IN_STATUS, | |
332 | data->in_status[i]); | |
333 | if ((data->in_status[i] & CHAN_READY) == CHAN_READY) { | |
334 | data->in[i] = pc87360_read_value(data, LD_IN, | |
335 | i, PC87365_REG_IN); | |
336 | } | |
337 | if (data->in_status[i] & CHAN_ENA) { | |
338 | data->in_min[i] = pc87360_read_value(data, | |
339 | LD_IN, i, | |
340 | PC87365_REG_IN_MIN); | |
341 | data->in_max[i] = pc87360_read_value(data, | |
342 | LD_IN, i, | |
343 | PC87365_REG_IN_MAX); | |
344 | if (i >= 11) | |
345 | data->in_crit[i-11] = | |
346 | pc87360_read_value(data, LD_IN, | |
347 | i, PC87365_REG_TEMP_CRIT); | |
348 | } | |
349 | } | |
350 | if (data->innr) { | |
351 | data->in_alarms = pc87360_read_value(data, LD_IN, | |
352 | NO_BANK, PC87365_REG_IN_ALARMS1) | |
353 | | ((pc87360_read_value(data, LD_IN, | |
354 | NO_BANK, PC87365_REG_IN_ALARMS2) | |
355 | & 0x07) << 8); | |
356 | data->vid = (data->vid_conf & 0xE0) ? | |
357 | pc87360_read_value(data, LD_IN, | |
358 | NO_BANK, PC87365_REG_VID) : 0x1F; | |
359 | } | |
360 | ||
361 | /* Temperatures */ | |
362 | for (i = 0; i < data->tempnr; i++) { | |
363 | data->temp_status[i] = pc87360_read_value(data, | |
364 | LD_TEMP, i, | |
365 | PC87365_REG_TEMP_STATUS); | |
366 | /* Clear bits */ | |
367 | pc87360_write_value(data, LD_TEMP, i, | |
368 | PC87365_REG_TEMP_STATUS, | |
369 | data->temp_status[i]); | |
370 | if ((data->temp_status[i] & CHAN_READY) == CHAN_READY) { | |
371 | data->temp[i] = pc87360_read_value(data, | |
372 | LD_TEMP, i, | |
373 | PC87365_REG_TEMP); | |
374 | } | |
375 | if (data->temp_status[i] & CHAN_ENA) { | |
376 | data->temp_min[i] = pc87360_read_value(data, | |
377 | LD_TEMP, i, | |
378 | PC87365_REG_TEMP_MIN); | |
379 | data->temp_max[i] = pc87360_read_value(data, | |
380 | LD_TEMP, i, | |
381 | PC87365_REG_TEMP_MAX); | |
382 | data->temp_crit[i] = pc87360_read_value(data, | |
383 | LD_TEMP, i, | |
384 | PC87365_REG_TEMP_CRIT); | |
385 | } | |
386 | } | |
387 | if (data->tempnr) { | |
388 | data->temp_alarms = pc87360_read_value(data, LD_TEMP, | |
389 | NO_BANK, PC87365_REG_TEMP_ALARMS) | |
390 | & 0x3F; | |
391 | } | |
392 | ||
393 | data->last_updated = jiffies; | |
394 | data->valid = true; | |
395 | } | |
396 | ||
397 | mutex_unlock(&data->update_lock); | |
398 | ||
399 | return data; | |
400 | } | |
401 | ||
402 | static ssize_t in_input_show(struct device *dev, | |
403 | struct device_attribute *devattr, char *buf) | |
404 | { | |
405 | struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | |
f0986bd8 | 406 | struct pc87360_data *data = pc87360_update_device(dev); |
070affa8 | 407 | return sprintf(buf, "%u\n", IN_FROM_REG(data->in[attr->index], |
f0986bd8 JC |
408 | data->in_vref)); |
409 | } | |
070affa8 UKK |
410 | |
411 | static struct sensor_device_attribute in_input[] = { | |
412 | SENSOR_ATTR_RO(in0_input, in_input, 0), | |
413 | SENSOR_ATTR_RO(in1_input, in_input, 1), | |
414 | SENSOR_ATTR_RO(in2_input, in_input, 2), | |
415 | SENSOR_ATTR_RO(in3_input, in_input, 3), | |
416 | SENSOR_ATTR_RO(in4_input, in_input, 4), | |
417 | SENSOR_ATTR_RO(in5_input, in_input, 5), | |
418 | SENSOR_ATTR_RO(in6_input, in_input, 6), | |
419 | SENSOR_ATTR_RO(in7_input, in_input, 7), | |
420 | SENSOR_ATTR_RO(in8_input, in_input, 8), | |
421 | SENSOR_ATTR_RO(in9_input, in_input, 9), | |
422 | SENSOR_ATTR_RO(in10_input, in_input, 10), | |
423 | }; | |
424 | ||
eba42d30 | 425 | static ssize_t in_status_show(struct device *dev, |
449a7a07 | 426 | struct device_attribute *devattr, char *buf) |
f0986bd8 JC |
427 | { |
428 | struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | |
429 | struct pc87360_data *data = pc87360_update_device(dev); | |
430 | return sprintf(buf, "%u\n", data->in_status[attr->index]); | |
431 | } | |
070affa8 UKK |
432 | |
433 | static struct sensor_device_attribute in_status[] = { | |
434 | SENSOR_ATTR_RO(in0_status, in_status, 0), | |
435 | SENSOR_ATTR_RO(in1_status, in_status, 1), | |
436 | SENSOR_ATTR_RO(in2_status, in_status, 2), | |
437 | SENSOR_ATTR_RO(in3_status, in_status, 3), | |
438 | SENSOR_ATTR_RO(in4_status, in_status, 4), | |
439 | SENSOR_ATTR_RO(in5_status, in_status, 5), | |
440 | SENSOR_ATTR_RO(in6_status, in_status, 6), | |
441 | SENSOR_ATTR_RO(in7_status, in_status, 7), | |
442 | SENSOR_ATTR_RO(in8_status, in_status, 8), | |
443 | SENSOR_ATTR_RO(in9_status, in_status, 9), | |
444 | SENSOR_ATTR_RO(in10_status, in_status, 10), | |
445 | }; | |
446 | ||
447 | static ssize_t in_min_show(struct device *dev, | |
448 | struct device_attribute *devattr, char *buf) | |
449 | { | |
450 | struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | |
451 | struct pc87360_data *data = pc87360_update_device(dev); | |
452 | return sprintf(buf, "%u\n", IN_FROM_REG(data->in_min[attr->index], | |
453 | data->in_vref)); | |
454 | } | |
455 | ||
eba42d30 GR |
456 | static ssize_t in_min_store(struct device *dev, |
457 | struct device_attribute *devattr, const char *buf, | |
458 | size_t count) | |
f0986bd8 JC |
459 | { |
460 | struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | |
f641b588 | 461 | struct pc87360_data *data = dev_get_drvdata(dev); |
449a7a07 GR |
462 | long val; |
463 | int err; | |
464 | ||
465 | err = kstrtol(buf, 10, &val); | |
466 | if (err) | |
467 | return err; | |
f0986bd8 | 468 | |
9a61bf63 | 469 | mutex_lock(&data->update_lock); |
f0986bd8 JC |
470 | data->in_min[attr->index] = IN_TO_REG(val, data->in_vref); |
471 | pc87360_write_value(data, LD_IN, attr->index, PC87365_REG_IN_MIN, | |
472 | data->in_min[attr->index]); | |
9a61bf63 | 473 | mutex_unlock(&data->update_lock); |
f0986bd8 JC |
474 | return count; |
475 | } | |
070affa8 UKK |
476 | |
477 | static struct sensor_device_attribute in_min[] = { | |
478 | SENSOR_ATTR_RW(in0_min, in_min, 0), | |
479 | SENSOR_ATTR_RW(in1_min, in_min, 1), | |
480 | SENSOR_ATTR_RW(in2_min, in_min, 2), | |
481 | SENSOR_ATTR_RW(in3_min, in_min, 3), | |
482 | SENSOR_ATTR_RW(in4_min, in_min, 4), | |
483 | SENSOR_ATTR_RW(in5_min, in_min, 5), | |
484 | SENSOR_ATTR_RW(in6_min, in_min, 6), | |
485 | SENSOR_ATTR_RW(in7_min, in_min, 7), | |
486 | SENSOR_ATTR_RW(in8_min, in_min, 8), | |
487 | SENSOR_ATTR_RW(in9_min, in_min, 9), | |
488 | SENSOR_ATTR_RW(in10_min, in_min, 10), | |
489 | }; | |
490 | ||
491 | static ssize_t in_max_show(struct device *dev, | |
492 | struct device_attribute *devattr, char *buf) | |
493 | { | |
494 | struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | |
495 | struct pc87360_data *data = pc87360_update_device(dev); | |
496 | return sprintf(buf, "%u\n", IN_FROM_REG(data->in_max[attr->index], | |
497 | data->in_vref)); | |
498 | } | |
499 | ||
eba42d30 GR |
500 | static ssize_t in_max_store(struct device *dev, |
501 | struct device_attribute *devattr, const char *buf, | |
502 | size_t count) | |
f0986bd8 JC |
503 | { |
504 | struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | |
f641b588 | 505 | struct pc87360_data *data = dev_get_drvdata(dev); |
449a7a07 GR |
506 | long val; |
507 | int err; | |
508 | ||
509 | err = kstrtol(buf, 10, &val); | |
510 | if (err) | |
511 | return err; | |
f0986bd8 | 512 | |
9a61bf63 | 513 | mutex_lock(&data->update_lock); |
f0986bd8 JC |
514 | data->in_max[attr->index] = IN_TO_REG(val, |
515 | data->in_vref); | |
516 | pc87360_write_value(data, LD_IN, attr->index, PC87365_REG_IN_MAX, | |
517 | data->in_max[attr->index]); | |
9a61bf63 | 518 | mutex_unlock(&data->update_lock); |
f0986bd8 JC |
519 | return count; |
520 | } | |
521 | ||
dedc6a78 | 522 | static struct sensor_device_attribute in_max[] = { |
eba42d30 GR |
523 | SENSOR_ATTR_RW(in0_max, in_max, 0), |
524 | SENSOR_ATTR_RW(in1_max, in_max, 1), | |
525 | SENSOR_ATTR_RW(in2_max, in_max, 2), | |
526 | SENSOR_ATTR_RW(in3_max, in_max, 3), | |
527 | SENSOR_ATTR_RW(in4_max, in_max, 4), | |
528 | SENSOR_ATTR_RW(in5_max, in_max, 5), | |
529 | SENSOR_ATTR_RW(in6_max, in_max, 6), | |
530 | SENSOR_ATTR_RW(in7_max, in_max, 7), | |
531 | SENSOR_ATTR_RW(in8_max, in_max, 8), | |
532 | SENSOR_ATTR_RW(in9_max, in_max, 9), | |
533 | SENSOR_ATTR_RW(in10_max, in_max, 10), | |
dedc6a78 | 534 | }; |
1da177e4 | 535 | |
28f74e71 JC |
536 | /* (temp & vin) channel status register alarm bits (pdf sec.11.5.12) */ |
537 | #define CHAN_ALM_MIN 0x02 /* min limit crossed */ | |
538 | #define CHAN_ALM_MAX 0x04 /* max limit exceeded */ | |
539 | #define TEMP_ALM_CRIT 0x08 /* temp crit exceeded (temp only) */ | |
540 | ||
3af2861e GR |
541 | /* |
542 | * show_in_min/max_alarm() reads data from the per-channel status | |
543 | * register (sec 11.5.12), not the vin event status registers (sec | |
544 | * 11.5.2) that (legacy) show_in_alarm() resds (via data->in_alarms) | |
545 | */ | |
492e9657 | 546 | |
eba42d30 GR |
547 | static ssize_t in_min_alarm_show(struct device *dev, |
548 | struct device_attribute *devattr, char *buf) | |
492e9657 JC |
549 | { |
550 | struct pc87360_data *data = pc87360_update_device(dev); | |
551 | unsigned nr = to_sensor_dev_attr(devattr)->index; | |
552 | ||
553 | return sprintf(buf, "%u\n", !!(data->in_status[nr] & CHAN_ALM_MIN)); | |
554 | } | |
492e9657 JC |
555 | |
556 | static struct sensor_device_attribute in_min_alarm[] = { | |
eba42d30 GR |
557 | SENSOR_ATTR_RO(in0_min_alarm, in_min_alarm, 0), |
558 | SENSOR_ATTR_RO(in1_min_alarm, in_min_alarm, 1), | |
559 | SENSOR_ATTR_RO(in2_min_alarm, in_min_alarm, 2), | |
560 | SENSOR_ATTR_RO(in3_min_alarm, in_min_alarm, 3), | |
561 | SENSOR_ATTR_RO(in4_min_alarm, in_min_alarm, 4), | |
562 | SENSOR_ATTR_RO(in5_min_alarm, in_min_alarm, 5), | |
563 | SENSOR_ATTR_RO(in6_min_alarm, in_min_alarm, 6), | |
564 | SENSOR_ATTR_RO(in7_min_alarm, in_min_alarm, 7), | |
565 | SENSOR_ATTR_RO(in8_min_alarm, in_min_alarm, 8), | |
566 | SENSOR_ATTR_RO(in9_min_alarm, in_min_alarm, 9), | |
567 | SENSOR_ATTR_RO(in10_min_alarm, in_min_alarm, 10), | |
492e9657 | 568 | }; |
070affa8 UKK |
569 | |
570 | static ssize_t in_max_alarm_show(struct device *dev, | |
571 | struct device_attribute *devattr, char *buf) | |
572 | { | |
573 | struct pc87360_data *data = pc87360_update_device(dev); | |
574 | unsigned nr = to_sensor_dev_attr(devattr)->index; | |
575 | ||
576 | return sprintf(buf, "%u\n", !!(data->in_status[nr] & CHAN_ALM_MAX)); | |
577 | } | |
578 | ||
492e9657 | 579 | static struct sensor_device_attribute in_max_alarm[] = { |
eba42d30 GR |
580 | SENSOR_ATTR_RO(in0_max_alarm, in_max_alarm, 0), |
581 | SENSOR_ATTR_RO(in1_max_alarm, in_max_alarm, 1), | |
582 | SENSOR_ATTR_RO(in2_max_alarm, in_max_alarm, 2), | |
583 | SENSOR_ATTR_RO(in3_max_alarm, in_max_alarm, 3), | |
584 | SENSOR_ATTR_RO(in4_max_alarm, in_max_alarm, 4), | |
585 | SENSOR_ATTR_RO(in5_max_alarm, in_max_alarm, 5), | |
586 | SENSOR_ATTR_RO(in6_max_alarm, in_max_alarm, 6), | |
587 | SENSOR_ATTR_RO(in7_max_alarm, in_max_alarm, 7), | |
588 | SENSOR_ATTR_RO(in8_max_alarm, in_max_alarm, 8), | |
589 | SENSOR_ATTR_RO(in9_max_alarm, in_max_alarm, 9), | |
590 | SENSOR_ATTR_RO(in10_max_alarm, in_max_alarm, 10), | |
492e9657 JC |
591 | }; |
592 | ||
941c5c05 JC |
593 | #define VIN_UNIT_ATTRS(X) \ |
594 | &in_input[X].dev_attr.attr, \ | |
595 | &in_status[X].dev_attr.attr, \ | |
596 | &in_min[X].dev_attr.attr, \ | |
492e9657 JC |
597 | &in_max[X].dev_attr.attr, \ |
598 | &in_min_alarm[X].dev_attr.attr, \ | |
599 | &in_max_alarm[X].dev_attr.attr | |
941c5c05 | 600 | |
e33110d0 JL |
601 | static ssize_t cpu0_vid_show(struct device *dev, |
602 | struct device_attribute *attr, char *buf) | |
44646c19 JC |
603 | { |
604 | struct pc87360_data *data = pc87360_update_device(dev); | |
605 | return sprintf(buf, "%u\n", vid_from_reg(data->vid, data->vrm)); | |
606 | } | |
e33110d0 | 607 | static DEVICE_ATTR_RO(cpu0_vid); |
44646c19 | 608 | |
e33110d0 | 609 | static ssize_t vrm_show(struct device *dev, struct device_attribute *attr, |
449a7a07 | 610 | char *buf) |
44646c19 | 611 | { |
90d6619a | 612 | struct pc87360_data *data = dev_get_drvdata(dev); |
44646c19 JC |
613 | return sprintf(buf, "%u\n", data->vrm); |
614 | } | |
070affa8 | 615 | |
e33110d0 JL |
616 | static ssize_t vrm_store(struct device *dev, struct device_attribute *attr, |
617 | const char *buf, size_t count) | |
44646c19 | 618 | { |
f641b588 | 619 | struct pc87360_data *data = dev_get_drvdata(dev); |
449a7a07 GR |
620 | unsigned long val; |
621 | int err; | |
622 | ||
623 | err = kstrtoul(buf, 10, &val); | |
624 | if (err) | |
625 | return err; | |
626 | ||
5e3b5610 AL |
627 | if (val > 255) |
628 | return -EINVAL; | |
629 | ||
449a7a07 | 630 | data->vrm = val; |
44646c19 JC |
631 | return count; |
632 | } | |
e33110d0 | 633 | static DEVICE_ATTR_RW(vrm); |
44646c19 | 634 | |
e33110d0 | 635 | static ssize_t alarms_in_show(struct device *dev, |
449a7a07 | 636 | struct device_attribute *attr, char *buf) |
44646c19 JC |
637 | { |
638 | struct pc87360_data *data = pc87360_update_device(dev); | |
639 | return sprintf(buf, "%u\n", data->in_alarms); | |
640 | } | |
e33110d0 | 641 | static DEVICE_ATTR_RO(alarms_in); |
44646c19 | 642 | |
941c5c05 JC |
643 | static struct attribute *pc8736x_vin_attr_array[] = { |
644 | VIN_UNIT_ATTRS(0), | |
645 | VIN_UNIT_ATTRS(1), | |
646 | VIN_UNIT_ATTRS(2), | |
647 | VIN_UNIT_ATTRS(3), | |
648 | VIN_UNIT_ATTRS(4), | |
649 | VIN_UNIT_ATTRS(5), | |
650 | VIN_UNIT_ATTRS(6), | |
651 | VIN_UNIT_ATTRS(7), | |
652 | VIN_UNIT_ATTRS(8), | |
653 | VIN_UNIT_ATTRS(9), | |
654 | VIN_UNIT_ATTRS(10), | |
655 | &dev_attr_cpu0_vid.attr, | |
656 | &dev_attr_vrm.attr, | |
657 | &dev_attr_alarms_in.attr, | |
658 | NULL | |
659 | }; | |
660 | static const struct attribute_group pc8736x_vin_group = { | |
661 | .attrs = pc8736x_vin_attr_array, | |
662 | }; | |
663 | ||
eba42d30 | 664 | static ssize_t therm_input_show(struct device *dev, |
449a7a07 | 665 | struct device_attribute *devattr, char *buf) |
f0986bd8 JC |
666 | { |
667 | struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | |
668 | struct pc87360_data *data = pc87360_update_device(dev); | |
694fa056 | 669 | return sprintf(buf, "%u\n", IN_FROM_REG(data->in[attr->index], |
f0986bd8 JC |
670 | data->in_vref)); |
671 | } | |
070affa8 UKK |
672 | |
673 | /* | |
674 | * the +11 term below reflects the fact that VLM units 11,12,13 are | |
675 | * used in the chip to measure voltage across the thermistors | |
676 | */ | |
677 | static struct sensor_device_attribute therm_input[] = { | |
678 | SENSOR_ATTR_RO(temp4_input, therm_input, 0 + 11), | |
679 | SENSOR_ATTR_RO(temp5_input, therm_input, 1 + 11), | |
680 | SENSOR_ATTR_RO(temp6_input, therm_input, 2 + 11), | |
681 | }; | |
682 | ||
683 | static ssize_t therm_status_show(struct device *dev, | |
684 | struct device_attribute *devattr, char *buf) | |
f0986bd8 JC |
685 | { |
686 | struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | |
687 | struct pc87360_data *data = pc87360_update_device(dev); | |
070affa8 | 688 | return sprintf(buf, "%u\n", data->in_status[attr->index]); |
f0986bd8 | 689 | } |
070affa8 UKK |
690 | |
691 | static struct sensor_device_attribute therm_status[] = { | |
692 | SENSOR_ATTR_RO(temp4_status, therm_status, 0 + 11), | |
693 | SENSOR_ATTR_RO(temp5_status, therm_status, 1 + 11), | |
694 | SENSOR_ATTR_RO(temp6_status, therm_status, 2 + 11), | |
695 | }; | |
696 | ||
697 | static ssize_t therm_min_show(struct device *dev, | |
449a7a07 | 698 | struct device_attribute *devattr, char *buf) |
f0986bd8 JC |
699 | { |
700 | struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | |
701 | struct pc87360_data *data = pc87360_update_device(dev); | |
070affa8 | 702 | return sprintf(buf, "%u\n", IN_FROM_REG(data->in_min[attr->index], |
f0986bd8 JC |
703 | data->in_vref)); |
704 | } | |
449a7a07 | 705 | |
eba42d30 GR |
706 | static ssize_t therm_min_store(struct device *dev, |
707 | struct device_attribute *devattr, | |
708 | const char *buf, size_t count) | |
f0986bd8 JC |
709 | { |
710 | struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | |
f641b588 | 711 | struct pc87360_data *data = dev_get_drvdata(dev); |
449a7a07 GR |
712 | long val; |
713 | int err; | |
714 | ||
715 | err = kstrtol(buf, 10, &val); | |
716 | if (err) | |
717 | return err; | |
f0986bd8 | 718 | |
9a61bf63 | 719 | mutex_lock(&data->update_lock); |
694fa056 JC |
720 | data->in_min[attr->index] = IN_TO_REG(val, data->in_vref); |
721 | pc87360_write_value(data, LD_IN, attr->index, PC87365_REG_TEMP_MIN, | |
722 | data->in_min[attr->index]); | |
9a61bf63 | 723 | mutex_unlock(&data->update_lock); |
f0986bd8 JC |
724 | return count; |
725 | } | |
449a7a07 | 726 | |
070affa8 UKK |
727 | static struct sensor_device_attribute therm_min[] = { |
728 | SENSOR_ATTR_RW(temp4_min, therm_min, 0 + 11), | |
729 | SENSOR_ATTR_RW(temp5_min, therm_min, 1 + 11), | |
730 | SENSOR_ATTR_RW(temp6_min, therm_min, 2 + 11), | |
731 | }; | |
732 | ||
733 | static ssize_t therm_max_show(struct device *dev, | |
734 | struct device_attribute *devattr, char *buf) | |
735 | { | |
736 | struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | |
737 | struct pc87360_data *data = pc87360_update_device(dev); | |
738 | return sprintf(buf, "%u\n", IN_FROM_REG(data->in_max[attr->index], | |
739 | data->in_vref)); | |
740 | } | |
741 | ||
742 | static ssize_t therm_max_store(struct device *dev, | |
743 | struct device_attribute *devattr, | |
744 | const char *buf, size_t count) | |
745 | { | |
746 | struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | |
747 | struct pc87360_data *data = dev_get_drvdata(dev); | |
748 | long val; | |
749 | int err; | |
449a7a07 GR |
750 | |
751 | err = kstrtol(buf, 10, &val); | |
752 | if (err) | |
753 | return err; | |
f0986bd8 | 754 | |
9a61bf63 | 755 | mutex_lock(&data->update_lock); |
694fa056 JC |
756 | data->in_max[attr->index] = IN_TO_REG(val, data->in_vref); |
757 | pc87360_write_value(data, LD_IN, attr->index, PC87365_REG_TEMP_MAX, | |
758 | data->in_max[attr->index]); | |
9a61bf63 | 759 | mutex_unlock(&data->update_lock); |
f0986bd8 JC |
760 | return count; |
761 | } | |
070affa8 UKK |
762 | |
763 | static struct sensor_device_attribute therm_max[] = { | |
764 | SENSOR_ATTR_RW(temp4_max, therm_max, 0 + 11), | |
765 | SENSOR_ATTR_RW(temp5_max, therm_max, 1 + 11), | |
766 | SENSOR_ATTR_RW(temp6_max, therm_max, 2 + 11), | |
767 | }; | |
768 | ||
769 | static ssize_t therm_crit_show(struct device *dev, | |
770 | struct device_attribute *devattr, char *buf) | |
771 | { | |
772 | struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | |
773 | struct pc87360_data *data = pc87360_update_device(dev); | |
774 | return sprintf(buf, "%u\n", IN_FROM_REG(data->in_crit[attr->index-11], | |
775 | data->in_vref)); | |
776 | } | |
777 | ||
eba42d30 GR |
778 | static ssize_t therm_crit_store(struct device *dev, |
779 | struct device_attribute *devattr, | |
780 | const char *buf, size_t count) | |
f0986bd8 JC |
781 | { |
782 | struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | |
f641b588 | 783 | struct pc87360_data *data = dev_get_drvdata(dev); |
449a7a07 GR |
784 | long val; |
785 | int err; | |
786 | ||
787 | err = kstrtol(buf, 10, &val); | |
788 | if (err) | |
789 | return err; | |
f0986bd8 | 790 | |
9a61bf63 | 791 | mutex_lock(&data->update_lock); |
694fa056 JC |
792 | data->in_crit[attr->index-11] = IN_TO_REG(val, data->in_vref); |
793 | pc87360_write_value(data, LD_IN, attr->index, PC87365_REG_TEMP_CRIT, | |
794 | data->in_crit[attr->index-11]); | |
9a61bf63 | 795 | mutex_unlock(&data->update_lock); |
f0986bd8 JC |
796 | return count; |
797 | } | |
798 | ||
dedc6a78 | 799 | static struct sensor_device_attribute therm_crit[] = { |
eba42d30 GR |
800 | SENSOR_ATTR_RW(temp4_crit, therm_crit, 0 + 11), |
801 | SENSOR_ATTR_RW(temp5_crit, therm_crit, 1 + 11), | |
802 | SENSOR_ATTR_RW(temp6_crit, therm_crit, 2 + 11), | |
dedc6a78 | 803 | }; |
1da177e4 | 804 | |
3af2861e GR |
805 | /* |
806 | * show_therm_min/max_alarm() reads data from the per-channel voltage | |
807 | * status register (sec 11.5.12) | |
808 | */ | |
eba42d30 GR |
809 | static ssize_t therm_min_alarm_show(struct device *dev, |
810 | struct device_attribute *devattr, | |
811 | char *buf) | |
865c2953 JC |
812 | { |
813 | struct pc87360_data *data = pc87360_update_device(dev); | |
814 | unsigned nr = to_sensor_dev_attr(devattr)->index; | |
815 | ||
816 | return sprintf(buf, "%u\n", !!(data->in_status[nr] & CHAN_ALM_MIN)); | |
817 | } | |
070affa8 UKK |
818 | |
819 | static struct sensor_device_attribute therm_min_alarm[] = { | |
820 | SENSOR_ATTR_RO(temp4_min_alarm, therm_min_alarm, 0 + 11), | |
821 | SENSOR_ATTR_RO(temp5_min_alarm, therm_min_alarm, 1 + 11), | |
822 | SENSOR_ATTR_RO(temp6_min_alarm, therm_min_alarm, 2 + 11), | |
823 | }; | |
824 | ||
eba42d30 GR |
825 | static ssize_t therm_max_alarm_show(struct device *dev, |
826 | struct device_attribute *devattr, | |
827 | char *buf) | |
865c2953 JC |
828 | { |
829 | struct pc87360_data *data = pc87360_update_device(dev); | |
830 | unsigned nr = to_sensor_dev_attr(devattr)->index; | |
831 | ||
832 | return sprintf(buf, "%u\n", !!(data->in_status[nr] & CHAN_ALM_MAX)); | |
833 | } | |
070affa8 UKK |
834 | |
835 | static struct sensor_device_attribute therm_max_alarm[] = { | |
836 | SENSOR_ATTR_RO(temp4_max_alarm, therm_max_alarm, 0 + 11), | |
837 | SENSOR_ATTR_RO(temp5_max_alarm, therm_max_alarm, 1 + 11), | |
838 | SENSOR_ATTR_RO(temp6_max_alarm, therm_max_alarm, 2 + 11), | |
839 | }; | |
840 | ||
eba42d30 GR |
841 | static ssize_t therm_crit_alarm_show(struct device *dev, |
842 | struct device_attribute *devattr, | |
843 | char *buf) | |
865c2953 JC |
844 | { |
845 | struct pc87360_data *data = pc87360_update_device(dev); | |
846 | unsigned nr = to_sensor_dev_attr(devattr)->index; | |
847 | ||
848 | return sprintf(buf, "%u\n", !!(data->in_status[nr] & TEMP_ALM_CRIT)); | |
849 | } | |
850 | ||
865c2953 | 851 | static struct sensor_device_attribute therm_crit_alarm[] = { |
eba42d30 GR |
852 | SENSOR_ATTR_RO(temp4_crit_alarm, therm_crit_alarm, 0 + 11), |
853 | SENSOR_ATTR_RO(temp5_crit_alarm, therm_crit_alarm, 1 + 11), | |
854 | SENSOR_ATTR_RO(temp6_crit_alarm, therm_crit_alarm, 2 + 11), | |
865c2953 JC |
855 | }; |
856 | ||
941c5c05 JC |
857 | #define THERM_UNIT_ATTRS(X) \ |
858 | &therm_input[X].dev_attr.attr, \ | |
859 | &therm_status[X].dev_attr.attr, \ | |
860 | &therm_min[X].dev_attr.attr, \ | |
861 | &therm_max[X].dev_attr.attr, \ | |
865c2953 JC |
862 | &therm_crit[X].dev_attr.attr, \ |
863 | &therm_min_alarm[X].dev_attr.attr, \ | |
864 | &therm_max_alarm[X].dev_attr.attr, \ | |
865 | &therm_crit_alarm[X].dev_attr.attr | |
941c5c05 | 866 | |
449a7a07 | 867 | static struct attribute *pc8736x_therm_attr_array[] = { |
941c5c05 JC |
868 | THERM_UNIT_ATTRS(0), |
869 | THERM_UNIT_ATTRS(1), | |
870 | THERM_UNIT_ATTRS(2), | |
871 | NULL | |
872 | }; | |
873 | static const struct attribute_group pc8736x_therm_group = { | |
874 | .attrs = pc8736x_therm_attr_array, | |
875 | }; | |
876 | ||
eba42d30 | 877 | static ssize_t temp_input_show(struct device *dev, |
449a7a07 | 878 | struct device_attribute *devattr, char *buf) |
f0986bd8 JC |
879 | { |
880 | struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | |
881 | struct pc87360_data *data = pc87360_update_device(dev); | |
694fa056 | 882 | return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp[attr->index])); |
f0986bd8 | 883 | } |
449a7a07 | 884 | |
070affa8 UKK |
885 | static struct sensor_device_attribute temp_input[] = { |
886 | SENSOR_ATTR_RO(temp1_input, temp_input, 0), | |
887 | SENSOR_ATTR_RO(temp2_input, temp_input, 1), | |
888 | SENSOR_ATTR_RO(temp3_input, temp_input, 2), | |
889 | }; | |
449a7a07 | 890 | |
070affa8 UKK |
891 | static ssize_t temp_status_show(struct device *dev, |
892 | struct device_attribute *devattr, char *buf) | |
f0986bd8 JC |
893 | { |
894 | struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | |
895 | struct pc87360_data *data = pc87360_update_device(dev); | |
070affa8 | 896 | return sprintf(buf, "%d\n", data->temp_status[attr->index]); |
f0986bd8 | 897 | } |
449a7a07 | 898 | |
070affa8 UKK |
899 | static struct sensor_device_attribute temp_status[] = { |
900 | SENSOR_ATTR_RO(temp1_status, temp_status, 0), | |
901 | SENSOR_ATTR_RO(temp2_status, temp_status, 1), | |
902 | SENSOR_ATTR_RO(temp3_status, temp_status, 2), | |
903 | }; | |
449a7a07 | 904 | |
070affa8 UKK |
905 | static ssize_t temp_min_show(struct device *dev, |
906 | struct device_attribute *devattr, char *buf) | |
f0986bd8 JC |
907 | { |
908 | struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | |
909 | struct pc87360_data *data = pc87360_update_device(dev); | |
070affa8 | 910 | return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_min[attr->index])); |
f0986bd8 | 911 | } |
449a7a07 | 912 | |
eba42d30 GR |
913 | static ssize_t temp_min_store(struct device *dev, |
914 | struct device_attribute *devattr, | |
915 | const char *buf, size_t count) | |
f0986bd8 JC |
916 | { |
917 | struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | |
f641b588 | 918 | struct pc87360_data *data = dev_get_drvdata(dev); |
449a7a07 GR |
919 | long val; |
920 | int err; | |
921 | ||
922 | err = kstrtol(buf, 10, &val); | |
923 | if (err) | |
924 | return err; | |
f0986bd8 | 925 | |
9a61bf63 | 926 | mutex_lock(&data->update_lock); |
694fa056 JC |
927 | data->temp_min[attr->index] = TEMP_TO_REG(val); |
928 | pc87360_write_value(data, LD_TEMP, attr->index, PC87365_REG_TEMP_MIN, | |
929 | data->temp_min[attr->index]); | |
9a61bf63 | 930 | mutex_unlock(&data->update_lock); |
f0986bd8 JC |
931 | return count; |
932 | } | |
449a7a07 | 933 | |
070affa8 UKK |
934 | static struct sensor_device_attribute temp_min[] = { |
935 | SENSOR_ATTR_RW(temp1_min, temp_min, 0), | |
936 | SENSOR_ATTR_RW(temp2_min, temp_min, 1), | |
937 | SENSOR_ATTR_RW(temp3_min, temp_min, 2), | |
938 | }; | |
939 | ||
940 | static ssize_t temp_max_show(struct device *dev, | |
941 | struct device_attribute *devattr, char *buf) | |
942 | { | |
943 | struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | |
944 | struct pc87360_data *data = pc87360_update_device(dev); | |
945 | return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_max[attr->index])); | |
946 | } | |
947 | ||
eba42d30 GR |
948 | static ssize_t temp_max_store(struct device *dev, |
949 | struct device_attribute *devattr, | |
950 | const char *buf, size_t count) | |
f0986bd8 JC |
951 | { |
952 | struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | |
f641b588 | 953 | struct pc87360_data *data = dev_get_drvdata(dev); |
449a7a07 GR |
954 | long val; |
955 | int err; | |
956 | ||
957 | err = kstrtol(buf, 10, &val); | |
958 | if (err) | |
959 | return err; | |
f0986bd8 | 960 | |
9a61bf63 | 961 | mutex_lock(&data->update_lock); |
694fa056 JC |
962 | data->temp_max[attr->index] = TEMP_TO_REG(val); |
963 | pc87360_write_value(data, LD_TEMP, attr->index, PC87365_REG_TEMP_MAX, | |
964 | data->temp_max[attr->index]); | |
9a61bf63 | 965 | mutex_unlock(&data->update_lock); |
f0986bd8 JC |
966 | return count; |
967 | } | |
449a7a07 | 968 | |
070affa8 UKK |
969 | static struct sensor_device_attribute temp_max[] = { |
970 | SENSOR_ATTR_RW(temp1_max, temp_max, 0), | |
971 | SENSOR_ATTR_RW(temp2_max, temp_max, 1), | |
972 | SENSOR_ATTR_RW(temp3_max, temp_max, 2), | |
973 | }; | |
974 | ||
975 | static ssize_t temp_crit_show(struct device *dev, | |
976 | struct device_attribute *devattr, char *buf) | |
977 | { | |
978 | struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | |
979 | struct pc87360_data *data = pc87360_update_device(dev); | |
980 | return sprintf(buf, "%d\n", | |
981 | TEMP_FROM_REG(data->temp_crit[attr->index])); | |
982 | } | |
983 | ||
eba42d30 GR |
984 | static ssize_t temp_crit_store(struct device *dev, |
985 | struct device_attribute *devattr, | |
986 | const char *buf, size_t count) | |
f0986bd8 JC |
987 | { |
988 | struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | |
f641b588 | 989 | struct pc87360_data *data = dev_get_drvdata(dev); |
449a7a07 GR |
990 | long val; |
991 | int err; | |
992 | ||
993 | err = kstrtol(buf, 10, &val); | |
994 | if (err) | |
995 | return err; | |
f0986bd8 | 996 | |
9a61bf63 | 997 | mutex_lock(&data->update_lock); |
694fa056 JC |
998 | data->temp_crit[attr->index] = TEMP_TO_REG(val); |
999 | pc87360_write_value(data, LD_TEMP, attr->index, PC87365_REG_TEMP_CRIT, | |
1000 | data->temp_crit[attr->index]); | |
9a61bf63 | 1001 | mutex_unlock(&data->update_lock); |
f0986bd8 JC |
1002 | return count; |
1003 | } | |
1004 | ||
dedc6a78 | 1005 | static struct sensor_device_attribute temp_crit[] = { |
eba42d30 GR |
1006 | SENSOR_ATTR_RW(temp1_crit, temp_crit, 0), |
1007 | SENSOR_ATTR_RW(temp2_crit, temp_crit, 1), | |
1008 | SENSOR_ATTR_RW(temp3_crit, temp_crit, 2), | |
dedc6a78 | 1009 | }; |
1da177e4 | 1010 | |
3af2861e | 1011 | /* |
070affa8 | 1012 | * temp_min/max_alarm_show() reads data from the per-channel status |
3af2861e GR |
1013 | * register (sec 12.3.7), not the temp event status registers (sec |
1014 | * 12.3.2) that show_temp_alarm() reads (via data->temp_alarms) | |
1015 | */ | |
eba42d30 GR |
1016 | static ssize_t temp_min_alarm_show(struct device *dev, |
1017 | struct device_attribute *devattr, | |
1018 | char *buf) | |
b267e8cd JC |
1019 | { |
1020 | struct pc87360_data *data = pc87360_update_device(dev); | |
1021 | unsigned nr = to_sensor_dev_attr(devattr)->index; | |
1022 | ||
1023 | return sprintf(buf, "%u\n", !!(data->temp_status[nr] & CHAN_ALM_MIN)); | |
1024 | } | |
449a7a07 | 1025 | |
070affa8 UKK |
1026 | static struct sensor_device_attribute temp_min_alarm[] = { |
1027 | SENSOR_ATTR_RO(temp1_min_alarm, temp_min_alarm, 0), | |
1028 | SENSOR_ATTR_RO(temp2_min_alarm, temp_min_alarm, 1), | |
1029 | SENSOR_ATTR_RO(temp3_min_alarm, temp_min_alarm, 2), | |
1030 | }; | |
1031 | ||
eba42d30 GR |
1032 | static ssize_t temp_max_alarm_show(struct device *dev, |
1033 | struct device_attribute *devattr, | |
1034 | char *buf) | |
b267e8cd JC |
1035 | { |
1036 | struct pc87360_data *data = pc87360_update_device(dev); | |
1037 | unsigned nr = to_sensor_dev_attr(devattr)->index; | |
1038 | ||
1039 | return sprintf(buf, "%u\n", !!(data->temp_status[nr] & CHAN_ALM_MAX)); | |
1040 | } | |
449a7a07 | 1041 | |
070affa8 UKK |
1042 | static struct sensor_device_attribute temp_max_alarm[] = { |
1043 | SENSOR_ATTR_RO(temp1_max_alarm, temp_max_alarm, 0), | |
1044 | SENSOR_ATTR_RO(temp2_max_alarm, temp_max_alarm, 1), | |
1045 | SENSOR_ATTR_RO(temp3_max_alarm, temp_max_alarm, 2), | |
1046 | }; | |
1047 | ||
eba42d30 GR |
1048 | static ssize_t temp_crit_alarm_show(struct device *dev, |
1049 | struct device_attribute *devattr, | |
1050 | char *buf) | |
b267e8cd JC |
1051 | { |
1052 | struct pc87360_data *data = pc87360_update_device(dev); | |
1053 | unsigned nr = to_sensor_dev_attr(devattr)->index; | |
1054 | ||
1055 | return sprintf(buf, "%u\n", !!(data->temp_status[nr] & TEMP_ALM_CRIT)); | |
1056 | } | |
1057 | ||
b267e8cd | 1058 | static struct sensor_device_attribute temp_crit_alarm[] = { |
eba42d30 GR |
1059 | SENSOR_ATTR_RO(temp1_crit_alarm, temp_crit_alarm, 0), |
1060 | SENSOR_ATTR_RO(temp2_crit_alarm, temp_crit_alarm, 1), | |
1061 | SENSOR_ATTR_RO(temp3_crit_alarm, temp_crit_alarm, 2), | |
b267e8cd JC |
1062 | }; |
1063 | ||
1064 | #define TEMP_FAULT 0x40 /* open diode */ | |
eba42d30 GR |
1065 | static ssize_t temp_fault_show(struct device *dev, |
1066 | struct device_attribute *devattr, char *buf) | |
b267e8cd JC |
1067 | { |
1068 | struct pc87360_data *data = pc87360_update_device(dev); | |
1069 | unsigned nr = to_sensor_dev_attr(devattr)->index; | |
1070 | ||
1071 | return sprintf(buf, "%u\n", !!(data->temp_status[nr] & TEMP_FAULT)); | |
1072 | } | |
070affa8 | 1073 | |
b267e8cd | 1074 | static struct sensor_device_attribute temp_fault[] = { |
eba42d30 GR |
1075 | SENSOR_ATTR_RO(temp1_fault, temp_fault, 0), |
1076 | SENSOR_ATTR_RO(temp2_fault, temp_fault, 1), | |
1077 | SENSOR_ATTR_RO(temp3_fault, temp_fault, 2), | |
b267e8cd JC |
1078 | }; |
1079 | ||
bce2778d GR |
1080 | #define TEMP_UNIT_ATTRS(X) \ |
1081 | { &temp_input[X].dev_attr.attr, \ | |
1082 | &temp_status[X].dev_attr.attr, \ | |
1083 | &temp_min[X].dev_attr.attr, \ | |
1084 | &temp_max[X].dev_attr.attr, \ | |
1085 | &temp_crit[X].dev_attr.attr, \ | |
1086 | &temp_min_alarm[X].dev_attr.attr, \ | |
1087 | &temp_max_alarm[X].dev_attr.attr, \ | |
1088 | &temp_crit_alarm[X].dev_attr.attr, \ | |
1089 | &temp_fault[X].dev_attr.attr, \ | |
1090 | NULL \ | |
1091 | } | |
1092 | ||
1093 | static struct attribute *pc8736x_temp_attr[][10] = { | |
941c5c05 JC |
1094 | TEMP_UNIT_ATTRS(0), |
1095 | TEMP_UNIT_ATTRS(1), | |
bce2778d | 1096 | TEMP_UNIT_ATTRS(2) |
941c5c05 | 1097 | }; |
449a7a07 | 1098 | |
bce2778d GR |
1099 | static const struct attribute_group pc8736x_temp_attr_group[] = { |
1100 | { .attrs = pc8736x_temp_attr[0] }, | |
1101 | { .attrs = pc8736x_temp_attr[1] }, | |
1102 | { .attrs = pc8736x_temp_attr[2] } | |
941c5c05 JC |
1103 | }; |
1104 | ||
070affa8 UKK |
1105 | static ssize_t alarms_temp_show(struct device *dev, |
1106 | struct device_attribute *attr, char *buf) | |
f641b588 | 1107 | { |
070affa8 UKK |
1108 | struct pc87360_data *data = pc87360_update_device(dev); |
1109 | return sprintf(buf, "%u\n", data->temp_alarms); | |
f641b588 | 1110 | } |
449a7a07 | 1111 | |
070affa8 | 1112 | static DEVICE_ATTR_RO(alarms_temp); |
f641b588 | 1113 | |
070affa8 UKK |
1114 | static ssize_t fan_input_show(struct device *dev, |
1115 | struct device_attribute *devattr, char *buf) | |
1116 | { | |
1117 | struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | |
1118 | struct pc87360_data *data = pc87360_update_device(dev); | |
1119 | return sprintf(buf, "%u\n", FAN_FROM_REG(data->fan[attr->index], | |
1120 | FAN_DIV_FROM_REG(data->fan_status[attr->index]))); | |
1121 | } | |
1da177e4 | 1122 | |
070affa8 UKK |
1123 | static struct sensor_device_attribute fan_input[] = { |
1124 | SENSOR_ATTR_RO(fan1_input, fan_input, 0), | |
1125 | SENSOR_ATTR_RO(fan2_input, fan_input, 1), | |
1126 | SENSOR_ATTR_RO(fan3_input, fan_input, 2), | |
1127 | }; | |
1128 | ||
1129 | static ssize_t fan_status_show(struct device *dev, | |
1130 | struct device_attribute *devattr, char *buf) | |
1da177e4 | 1131 | { |
070affa8 UKK |
1132 | struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); |
1133 | struct pc87360_data *data = pc87360_update_device(dev); | |
1134 | return sprintf(buf, "%u\n", | |
1135 | FAN_STATUS_FROM_REG(data->fan_status[attr->index])); | |
1136 | } | |
1da177e4 | 1137 | |
070affa8 UKK |
1138 | static struct sensor_device_attribute fan_status[] = { |
1139 | SENSOR_ATTR_RO(fan1_status, fan_status, 0), | |
1140 | SENSOR_ATTR_RO(fan2_status, fan_status, 1), | |
1141 | SENSOR_ATTR_RO(fan3_status, fan_status, 2), | |
1142 | }; | |
1da177e4 | 1143 | |
070affa8 UKK |
1144 | static ssize_t fan_div_show(struct device *dev, |
1145 | struct device_attribute *devattr, char *buf) | |
1146 | { | |
1147 | struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | |
1148 | struct pc87360_data *data = pc87360_update_device(dev); | |
1149 | return sprintf(buf, "%u\n", | |
1150 | FAN_DIV_FROM_REG(data->fan_status[attr->index])); | |
1da177e4 LT |
1151 | } |
1152 | ||
070affa8 UKK |
1153 | static struct sensor_device_attribute fan_div[] = { |
1154 | SENSOR_ATTR_RO(fan1_div, fan_div, 0), | |
1155 | SENSOR_ATTR_RO(fan2_div, fan_div, 1), | |
1156 | SENSOR_ATTR_RO(fan3_div, fan_div, 2), | |
1157 | }; | |
bce2778d | 1158 | |
070affa8 UKK |
1159 | static ssize_t fan_min_show(struct device *dev, |
1160 | struct device_attribute *devattr, char *buf) | |
1161 | { | |
1162 | struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | |
1163 | struct pc87360_data *data = pc87360_update_device(dev); | |
1164 | return sprintf(buf, "%u\n", FAN_FROM_REG(data->fan_min[attr->index], | |
1165 | FAN_DIV_FROM_REG(data->fan_status[attr->index]))); | |
bce2778d GR |
1166 | } |
1167 | ||
070affa8 UKK |
1168 | static ssize_t fan_min_store(struct device *dev, |
1169 | struct device_attribute *devattr, | |
1170 | const char *buf, size_t count) | |
1da177e4 | 1171 | { |
070affa8 UKK |
1172 | struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); |
1173 | struct pc87360_data *data = dev_get_drvdata(dev); | |
1174 | long fan_min; | |
1175 | int err; | |
943b0830 | 1176 | |
070affa8 UKK |
1177 | err = kstrtol(buf, 10, &fan_min); |
1178 | if (err) | |
1179 | return err; | |
f3722d5b | 1180 | |
070affa8 UKK |
1181 | mutex_lock(&data->update_lock); |
1182 | fan_min = FAN_TO_REG(fan_min, | |
1183 | FAN_DIV_FROM_REG(data->fan_status[attr->index])); | |
1da177e4 | 1184 | |
070affa8 UKK |
1185 | /* If it wouldn't fit, change clock divisor */ |
1186 | while (fan_min > 255 | |
1187 | && (data->fan_status[attr->index] & 0x60) != 0x60) { | |
1188 | fan_min >>= 1; | |
1189 | data->fan[attr->index] >>= 1; | |
1190 | data->fan_status[attr->index] += 0x20; | |
1da177e4 | 1191 | } |
070affa8 UKK |
1192 | data->fan_min[attr->index] = fan_min > 255 ? 255 : fan_min; |
1193 | pc87360_write_value(data, LD_FAN, NO_BANK, | |
1194 | PC87360_REG_FAN_MIN(attr->index), | |
1195 | data->fan_min[attr->index]); | |
1da177e4 | 1196 | |
070affa8 UKK |
1197 | /* Write new divider, preserve alarm bits */ |
1198 | pc87360_write_value(data, LD_FAN, NO_BANK, | |
1199 | PC87360_REG_FAN_STATUS(attr->index), | |
1200 | data->fan_status[attr->index] & 0xF9); | |
1201 | mutex_unlock(&data->update_lock); | |
1da177e4 | 1202 | |
070affa8 UKK |
1203 | return count; |
1204 | } | |
f641b588 | 1205 | |
070affa8 UKK |
1206 | static struct sensor_device_attribute fan_min[] = { |
1207 | SENSOR_ATTR_RW(fan1_min, fan_min, 0), | |
1208 | SENSOR_ATTR_RW(fan2_min, fan_min, 1), | |
1209 | SENSOR_ATTR_RW(fan3_min, fan_min, 2), | |
1210 | }; | |
1da177e4 | 1211 | |
070affa8 UKK |
1212 | #define FAN_UNIT_ATTRS(X) \ |
1213 | { &fan_input[X].dev_attr.attr, \ | |
1214 | &fan_status[X].dev_attr.attr, \ | |
1215 | &fan_div[X].dev_attr.attr, \ | |
1216 | &fan_min[X].dev_attr.attr, \ | |
1217 | NULL \ | |
1da177e4 LT |
1218 | } |
1219 | ||
070affa8 UKK |
1220 | static struct attribute *pc8736x_fan_attr[][5] = { |
1221 | FAN_UNIT_ATTRS(0), | |
1222 | FAN_UNIT_ATTRS(1), | |
1223 | FAN_UNIT_ATTRS(2) | |
1224 | }; | |
1da177e4 | 1225 | |
070affa8 UKK |
1226 | static const struct attribute_group pc8736x_fan_attr_group[] = { |
1227 | { .attrs = pc8736x_fan_attr[0], }, | |
1228 | { .attrs = pc8736x_fan_attr[1], }, | |
1229 | { .attrs = pc8736x_fan_attr[2], }, | |
1230 | }; | |
1da177e4 | 1231 | |
070affa8 UKK |
1232 | static ssize_t pwm_show(struct device *dev, struct device_attribute *devattr, |
1233 | char *buf) | |
1234 | { | |
1235 | struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | |
1236 | struct pc87360_data *data = pc87360_update_device(dev); | |
1237 | return sprintf(buf, "%u\n", | |
1238 | PWM_FROM_REG(data->pwm[attr->index], | |
1239 | FAN_CONFIG_INVERT(data->fan_conf, | |
1240 | attr->index))); | |
1da177e4 LT |
1241 | } |
1242 | ||
070affa8 UKK |
1243 | static ssize_t pwm_store(struct device *dev, struct device_attribute *devattr, |
1244 | const char *buf, size_t count) | |
1da177e4 | 1245 | { |
070affa8 UKK |
1246 | struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); |
1247 | struct pc87360_data *data = dev_get_drvdata(dev); | |
1248 | long val; | |
1249 | int err; | |
1da177e4 | 1250 | |
070affa8 UKK |
1251 | err = kstrtol(buf, 10, &val); |
1252 | if (err) | |
1253 | return err; | |
1da177e4 | 1254 | |
070affa8 UKK |
1255 | mutex_lock(&data->update_lock); |
1256 | data->pwm[attr->index] = PWM_TO_REG(val, | |
1257 | FAN_CONFIG_INVERT(data->fan_conf, attr->index)); | |
1258 | pc87360_write_value(data, LD_FAN, NO_BANK, PC87360_REG_PWM(attr->index), | |
1259 | data->pwm[attr->index]); | |
1260 | mutex_unlock(&data->update_lock); | |
1261 | return count; | |
1da177e4 LT |
1262 | } |
1263 | ||
070affa8 UKK |
1264 | static struct sensor_device_attribute pwm[] = { |
1265 | SENSOR_ATTR_RW(pwm1, pwm, 0), | |
1266 | SENSOR_ATTR_RW(pwm2, pwm, 1), | |
1267 | SENSOR_ATTR_RW(pwm3, pwm, 2), | |
1268 | }; | |
1269 | ||
1270 | static ssize_t name_show(struct device *dev, | |
1271 | struct device_attribute *devattr, char *buf) | |
1da177e4 | 1272 | { |
070affa8 UKK |
1273 | struct pc87360_data *data = dev_get_drvdata(dev); |
1274 | return sprintf(buf, "%s\n", data->name); | |
1da177e4 LT |
1275 | } |
1276 | ||
070affa8 | 1277 | static DEVICE_ATTR_RO(name); |
28f74e71 | 1278 | |
070affa8 UKK |
1279 | static void pc87360_remove_files(struct device *dev) |
1280 | { | |
1281 | int i; | |
1282 | ||
1283 | device_remove_file(dev, &dev_attr_name); | |
1284 | device_remove_file(dev, &dev_attr_alarms_temp); | |
1285 | for (i = 0; i < ARRAY_SIZE(pc8736x_temp_attr_group); i++) | |
1286 | sysfs_remove_group(&dev->kobj, &pc8736x_temp_attr_group[i]); | |
1287 | for (i = 0; i < ARRAY_SIZE(pc8736x_fan_attr_group); i++) { | |
1288 | sysfs_remove_group(&pdev->dev.kobj, &pc8736x_fan_attr_group[i]); | |
1289 | device_remove_file(dev, &pwm[i].dev_attr); | |
1290 | } | |
1291 | sysfs_remove_group(&dev->kobj, &pc8736x_therm_group); | |
1292 | sysfs_remove_group(&dev->kobj, &pc8736x_vin_group); | |
1293 | } | |
b267e8cd | 1294 | |
f641b588 JD |
1295 | static void pc87360_init_device(struct platform_device *pdev, |
1296 | int use_thermistors) | |
1da177e4 | 1297 | { |
f641b588 | 1298 | struct pc87360_data *data = platform_get_drvdata(pdev); |
1da177e4 LT |
1299 | int i, nr; |
1300 | const u8 init_in[14] = { 2, 2, 2, 2, 2, 2, 2, 1, 1, 3, 1, 2, 2, 2 }; | |
1301 | const u8 init_temp[3] = { 2, 2, 1 }; | |
1302 | u8 reg; | |
1303 | ||
1304 | if (init >= 2 && data->innr) { | |
1305 | reg = pc87360_read_value(data, LD_IN, NO_BANK, | |
1306 | PC87365_REG_IN_CONVRATE); | |
b55f3757 GR |
1307 | dev_info(&pdev->dev, |
1308 | "VLM conversion set to 1s period, 160us delay\n"); | |
1da177e4 LT |
1309 | pc87360_write_value(data, LD_IN, NO_BANK, |
1310 | PC87365_REG_IN_CONVRATE, | |
1311 | (reg & 0xC0) | 0x11); | |
1312 | } | |
1313 | ||
1314 | nr = data->innr < 11 ? data->innr : 11; | |
dedc6a78 | 1315 | for (i = 0; i < nr; i++) { |
8ca13674 JC |
1316 | reg = pc87360_read_value(data, LD_IN, i, |
1317 | PC87365_REG_IN_STATUS); | |
1318 | dev_dbg(&pdev->dev, "bios in%d status:0x%02x\n", i, reg); | |
1da177e4 LT |
1319 | if (init >= init_in[i]) { |
1320 | /* Forcibly enable voltage channel */ | |
28f74e71 | 1321 | if (!(reg & CHAN_ENA)) { |
b55f3757 GR |
1322 | dev_dbg(&pdev->dev, "Forcibly enabling in%d\n", |
1323 | i); | |
1da177e4 LT |
1324 | pc87360_write_value(data, LD_IN, i, |
1325 | PC87365_REG_IN_STATUS, | |
1326 | (reg & 0x68) | 0x87); | |
1327 | } | |
1328 | } | |
1329 | } | |
1330 | ||
3af2861e GR |
1331 | /* |
1332 | * We can't blindly trust the Super-I/O space configuration bit, | |
1333 | * most BIOS won't set it properly | |
1334 | */ | |
8ca13674 | 1335 | dev_dbg(&pdev->dev, "bios thermistors:%d\n", use_thermistors); |
dedc6a78 | 1336 | for (i = 11; i < data->innr; i++) { |
1da177e4 LT |
1337 | reg = pc87360_read_value(data, LD_IN, i, |
1338 | PC87365_REG_TEMP_STATUS); | |
28f74e71 | 1339 | use_thermistors = use_thermistors || (reg & CHAN_ENA); |
8ca13674 JC |
1340 | /* thermistors are temp[4-6], measured on vin[11-14] */ |
1341 | dev_dbg(&pdev->dev, "bios temp%d_status:0x%02x\n", i-7, reg); | |
1da177e4 | 1342 | } |
8ca13674 | 1343 | dev_dbg(&pdev->dev, "using thermistors:%d\n", use_thermistors); |
1da177e4 LT |
1344 | |
1345 | i = use_thermistors ? 2 : 0; | |
dedc6a78 | 1346 | for (; i < data->tempnr; i++) { |
8ca13674 JC |
1347 | reg = pc87360_read_value(data, LD_TEMP, i, |
1348 | PC87365_REG_TEMP_STATUS); | |
449a7a07 | 1349 | dev_dbg(&pdev->dev, "bios temp%d_status:0x%02x\n", i + 1, reg); |
1da177e4 LT |
1350 | if (init >= init_temp[i]) { |
1351 | /* Forcibly enable temperature channel */ | |
28f74e71 | 1352 | if (!(reg & CHAN_ENA)) { |
449a7a07 GR |
1353 | dev_dbg(&pdev->dev, |
1354 | "Forcibly enabling temp%d\n", i + 1); | |
1da177e4 LT |
1355 | pc87360_write_value(data, LD_TEMP, i, |
1356 | PC87365_REG_TEMP_STATUS, | |
1357 | 0xCF); | |
1358 | } | |
1359 | } | |
1360 | } | |
1361 | ||
1362 | if (use_thermistors) { | |
dedc6a78 | 1363 | for (i = 11; i < data->innr; i++) { |
1da177e4 | 1364 | if (init >= init_in[i]) { |
3af2861e GR |
1365 | /* |
1366 | * The pin may already be used by thermal | |
1367 | * diodes | |
1368 | */ | |
1da177e4 | 1369 | reg = pc87360_read_value(data, LD_TEMP, |
449a7a07 | 1370 | (i - 11) / 2, PC87365_REG_TEMP_STATUS); |
28f74e71 | 1371 | if (reg & CHAN_ENA) { |
449a7a07 GR |
1372 | dev_dbg(&pdev->dev, |
1373 | "Skipping temp%d, pin already in use by temp%d\n", | |
1374 | i - 7, (i - 11) / 2); | |
1da177e4 LT |
1375 | continue; |
1376 | } | |
1377 | ||
1378 | /* Forcibly enable thermistor channel */ | |
1379 | reg = pc87360_read_value(data, LD_IN, i, | |
1380 | PC87365_REG_IN_STATUS); | |
28f74e71 | 1381 | if (!(reg & CHAN_ENA)) { |
449a7a07 GR |
1382 | dev_dbg(&pdev->dev, |
1383 | "Forcibly enabling temp%d\n", | |
1384 | i - 7); | |
1da177e4 LT |
1385 | pc87360_write_value(data, LD_IN, i, |
1386 | PC87365_REG_TEMP_STATUS, | |
1387 | (reg & 0x60) | 0x8F); | |
1388 | } | |
1389 | } | |
1390 | } | |
1391 | } | |
1392 | ||
1393 | if (data->innr) { | |
1394 | reg = pc87360_read_value(data, LD_IN, NO_BANK, | |
1395 | PC87365_REG_IN_CONFIG); | |
8ca13674 | 1396 | dev_dbg(&pdev->dev, "bios vin-cfg:0x%02x\n", reg); |
28f74e71 | 1397 | if (reg & CHAN_ENA) { |
449a7a07 GR |
1398 | dev_dbg(&pdev->dev, |
1399 | "Forcibly enabling monitoring (VLM)\n"); | |
1da177e4 LT |
1400 | pc87360_write_value(data, LD_IN, NO_BANK, |
1401 | PC87365_REG_IN_CONFIG, | |
1402 | reg & 0xFE); | |
1403 | } | |
1404 | } | |
1405 | ||
1406 | if (data->tempnr) { | |
1407 | reg = pc87360_read_value(data, LD_TEMP, NO_BANK, | |
1408 | PC87365_REG_TEMP_CONFIG); | |
8ca13674 | 1409 | dev_dbg(&pdev->dev, "bios temp-cfg:0x%02x\n", reg); |
28f74e71 | 1410 | if (reg & CHAN_ENA) { |
449a7a07 GR |
1411 | dev_dbg(&pdev->dev, |
1412 | "Forcibly enabling monitoring (TMS)\n"); | |
1da177e4 LT |
1413 | pc87360_write_value(data, LD_TEMP, NO_BANK, |
1414 | PC87365_REG_TEMP_CONFIG, | |
1415 | reg & 0xFE); | |
1416 | } | |
1417 | ||
1418 | if (init >= 2) { | |
1419 | /* Chip config as documented by National Semi. */ | |
1420 | pc87360_write_value(data, LD_TEMP, 0xF, 0xA, 0x08); | |
3af2861e GR |
1421 | /* |
1422 | * We voluntarily omit the bank here, in case the | |
1423 | * sequence itself matters. It shouldn't be a problem, | |
1424 | * since nobody else is supposed to access the | |
1425 | * device at that point. | |
1426 | */ | |
1da177e4 LT |
1427 | pc87360_write_value(data, LD_TEMP, NO_BANK, 0xB, 0x04); |
1428 | pc87360_write_value(data, LD_TEMP, NO_BANK, 0xC, 0x35); | |
1429 | pc87360_write_value(data, LD_TEMP, NO_BANK, 0xD, 0x05); | |
1430 | pc87360_write_value(data, LD_TEMP, NO_BANK, 0xE, 0x05); | |
1431 | } | |
1432 | } | |
070affa8 UKK |
1433 | } |
1434 | ||
1435 | static int pc87360_probe(struct platform_device *pdev) | |
1436 | { | |
1437 | int i; | |
1438 | struct pc87360_data *data; | |
1439 | int err = 0; | |
1440 | const char *name; | |
1441 | int use_thermistors = 0; | |
1442 | struct device *dev = &pdev->dev; | |
1443 | ||
1444 | data = devm_kzalloc(dev, sizeof(struct pc87360_data), GFP_KERNEL); | |
1445 | if (!data) | |
1446 | return -ENOMEM; | |
1447 | ||
1448 | switch (devid) { | |
1449 | default: | |
1450 | name = "pc87360"; | |
1451 | data->fannr = 2; | |
1452 | break; | |
1453 | case 0xe8: | |
1454 | name = "pc87363"; | |
1455 | data->fannr = 2; | |
1456 | break; | |
1457 | case 0xe4: | |
1458 | name = "pc87364"; | |
1459 | data->fannr = 3; | |
1460 | break; | |
1461 | case 0xe5: | |
1462 | name = "pc87365"; | |
1463 | data->fannr = extra_isa[0] ? 3 : 0; | |
1464 | data->innr = extra_isa[1] ? 11 : 0; | |
1465 | data->tempnr = extra_isa[2] ? 2 : 0; | |
1466 | break; | |
1467 | case 0xe9: | |
1468 | name = "pc87366"; | |
1469 | data->fannr = extra_isa[0] ? 3 : 0; | |
1470 | data->innr = extra_isa[1] ? 14 : 0; | |
1471 | data->tempnr = extra_isa[2] ? 3 : 0; | |
1472 | break; | |
1473 | } | |
1474 | ||
1475 | data->name = name; | |
1476 | mutex_init(&data->lock); | |
1477 | mutex_init(&data->update_lock); | |
1478 | platform_set_drvdata(pdev, data); | |
1479 | ||
1480 | for (i = 0; i < LDNI_MAX; i++) { | |
1481 | data->address[i] = extra_isa[i]; | |
1482 | if (data->address[i] | |
1483 | && !devm_request_region(dev, extra_isa[i], PC87360_EXTENT, | |
1484 | DRIVER_NAME)) { | |
1485 | dev_err(dev, | |
1486 | "Region 0x%x-0x%x already in use!\n", | |
1487 | extra_isa[i], extra_isa[i]+PC87360_EXTENT-1); | |
1488 | return -EBUSY; | |
1489 | } | |
1490 | } | |
1491 | ||
1492 | /* Retrieve the fans configuration from Super-I/O space */ | |
1493 | if (data->fannr) | |
1494 | data->fan_conf = confreg[0] | (confreg[1] << 8); | |
1495 | ||
1496 | /* | |
1497 | * Use the correct reference voltage | |
1498 | * Unless both the VLM and the TMS logical devices agree to | |
1499 | * use an external Vref, the internal one is used. | |
1500 | */ | |
1501 | if (data->innr) { | |
1502 | i = pc87360_read_value(data, LD_IN, NO_BANK, | |
1503 | PC87365_REG_IN_CONFIG); | |
1504 | if (data->tempnr) { | |
1505 | i &= pc87360_read_value(data, LD_TEMP, NO_BANK, | |
1506 | PC87365_REG_TEMP_CONFIG); | |
1507 | } | |
1508 | data->in_vref = (i&0x02) ? 3025 : 2966; | |
1509 | dev_dbg(dev, "Using %s reference voltage\n", | |
1510 | (i&0x02) ? "external" : "internal"); | |
1511 | ||
1512 | data->vid_conf = confreg[3]; | |
1513 | data->vrm = vid_which_vrm(); | |
1514 | } | |
1515 | ||
1516 | /* Fan clock dividers may be needed before any data is read */ | |
1517 | for (i = 0; i < data->fannr; i++) { | |
1518 | if (FAN_CONFIG_MONITOR(data->fan_conf, i)) | |
1519 | data->fan_status[i] = pc87360_read_value(data, | |
1520 | LD_FAN, NO_BANK, | |
1521 | PC87360_REG_FAN_STATUS(i)); | |
1522 | } | |
1523 | ||
1524 | if (init > 0) { | |
1525 | if (devid == 0xe9 && data->address[1]) /* PC87366 */ | |
1526 | use_thermistors = confreg[2] & 0x40; | |
1527 | ||
1528 | pc87360_init_device(pdev, use_thermistors); | |
1529 | } | |
1530 | ||
1531 | /* Register all-or-nothing sysfs groups */ | |
1532 | ||
1533 | if (data->innr) { | |
1534 | err = sysfs_create_group(&dev->kobj, &pc8736x_vin_group); | |
1535 | if (err) | |
1536 | goto error; | |
1537 | } | |
1538 | ||
1539 | if (data->innr == 14) { | |
1540 | err = sysfs_create_group(&dev->kobj, &pc8736x_therm_group); | |
1541 | if (err) | |
1542 | goto error; | |
1543 | } | |
1544 | ||
1545 | /* create device attr-files for varying sysfs groups */ | |
1546 | ||
1547 | if (data->tempnr) { | |
1548 | for (i = 0; i < data->tempnr; i++) { | |
1549 | err = sysfs_create_group(&dev->kobj, | |
1550 | &pc8736x_temp_attr_group[i]); | |
1551 | if (err) | |
1552 | goto error; | |
1553 | } | |
1554 | err = device_create_file(dev, &dev_attr_alarms_temp); | |
1555 | if (err) | |
1556 | goto error; | |
1557 | } | |
1da177e4 | 1558 | |
070affa8 UKK |
1559 | for (i = 0; i < data->fannr; i++) { |
1560 | if (FAN_CONFIG_MONITOR(data->fan_conf, i)) { | |
1561 | err = sysfs_create_group(&dev->kobj, | |
1562 | &pc8736x_fan_attr_group[i]); | |
1563 | if (err) | |
1564 | goto error; | |
1da177e4 | 1565 | } |
070affa8 UKK |
1566 | if (FAN_CONFIG_CONTROL(data->fan_conf, i)) { |
1567 | err = device_create_file(dev, &pwm[i].dev_attr); | |
1568 | if (err) | |
1569 | goto error; | |
1da177e4 LT |
1570 | } |
1571 | } | |
1572 | ||
070affa8 UKK |
1573 | err = device_create_file(dev, &dev_attr_name); |
1574 | if (err) | |
1575 | goto error; | |
1576 | ||
1577 | data->hwmon_dev = hwmon_device_register(dev); | |
1578 | if (IS_ERR(data->hwmon_dev)) { | |
1579 | err = PTR_ERR(data->hwmon_dev); | |
1580 | goto error; | |
1da177e4 | 1581 | } |
070affa8 UKK |
1582 | return 0; |
1583 | ||
1584 | error: | |
1585 | pc87360_remove_files(dev); | |
1586 | return err; | |
1da177e4 LT |
1587 | } |
1588 | ||
070affa8 | 1589 | static int pc87360_remove(struct platform_device *pdev) |
1da177e4 | 1590 | { |
070affa8 | 1591 | struct pc87360_data *data = platform_get_drvdata(pdev); |
1da177e4 | 1592 | |
070affa8 UKK |
1593 | hwmon_device_unregister(data->hwmon_dev); |
1594 | pc87360_remove_files(&pdev->dev); | |
1da177e4 | 1595 | |
070affa8 UKK |
1596 | return 0; |
1597 | } | |
1da177e4 | 1598 | |
070affa8 UKK |
1599 | /* |
1600 | * Driver data | |
1601 | */ | |
1602 | static struct platform_driver pc87360_driver = { | |
1603 | .driver = { | |
1604 | .name = DRIVER_NAME, | |
1605 | }, | |
1606 | .probe = pc87360_probe, | |
1607 | .remove = pc87360_remove, | |
1608 | }; | |
1da177e4 | 1609 | |
070affa8 UKK |
1610 | /* |
1611 | * Device detection, registration and update | |
1612 | */ | |
1613 | ||
1614 | static int __init pc87360_find(int sioaddr, u8 *devid, | |
1615 | unsigned short *addresses) | |
1616 | { | |
1617 | u16 val; | |
1618 | int i; | |
1619 | int nrdev; /* logical device count */ | |
1620 | ||
1621 | /* No superio_enter */ | |
1622 | ||
1623 | /* Identify device */ | |
1624 | val = force_id ? force_id : superio_inb(sioaddr, DEVID); | |
1625 | switch (val) { | |
1626 | case 0xE1: /* PC87360 */ | |
1627 | case 0xE8: /* PC87363 */ | |
1628 | case 0xE4: /* PC87364 */ | |
1629 | nrdev = 1; | |
1630 | break; | |
1631 | case 0xE5: /* PC87365 */ | |
1632 | case 0xE9: /* PC87366 */ | |
1633 | nrdev = 3; | |
1634 | break; | |
1635 | default: | |
1636 | superio_exit(sioaddr); | |
1637 | return -ENODEV; | |
1638 | } | |
1639 | /* Remember the device id */ | |
1640 | *devid = val; | |
1641 | ||
1642 | for (i = 0; i < nrdev; i++) { | |
1643 | /* select logical device */ | |
1644 | superio_outb(sioaddr, DEV, logdev[i]); | |
1645 | ||
1646 | val = superio_inb(sioaddr, ACT); | |
1647 | if (!(val & 0x01)) { | |
1648 | pr_info("Device 0x%02x not activated\n", logdev[i]); | |
1649 | continue; | |
1da177e4 | 1650 | } |
070affa8 UKK |
1651 | |
1652 | val = (superio_inb(sioaddr, BASE) << 8) | |
1653 | | superio_inb(sioaddr, BASE + 1); | |
1654 | if (!val) { | |
1655 | pr_info("Base address not set for device 0x%02x\n", | |
1656 | logdev[i]); | |
1657 | continue; | |
1da177e4 LT |
1658 | } |
1659 | ||
070affa8 UKK |
1660 | addresses[i] = val; |
1661 | ||
1662 | if (i == 0) { /* Fans */ | |
1663 | confreg[0] = superio_inb(sioaddr, 0xF0); | |
1664 | confreg[1] = superio_inb(sioaddr, 0xF1); | |
1665 | ||
1666 | pr_debug("Fan %d: mon=%d ctrl=%d inv=%d\n", 1, | |
1667 | (confreg[0] >> 2) & 1, (confreg[0] >> 3) & 1, | |
1668 | (confreg[0] >> 4) & 1); | |
1669 | pr_debug("Fan %d: mon=%d ctrl=%d inv=%d\n", 2, | |
1670 | (confreg[0] >> 5) & 1, (confreg[0] >> 6) & 1, | |
1671 | (confreg[0] >> 7) & 1); | |
1672 | pr_debug("Fan %d: mon=%d ctrl=%d inv=%d\n", 3, | |
1673 | confreg[1] & 1, (confreg[1] >> 1) & 1, | |
1674 | (confreg[1] >> 2) & 1); | |
1675 | } else if (i == 1) { /* Voltages */ | |
1676 | /* Are we using thermistors? */ | |
1677 | if (*devid == 0xE9) { /* PC87366 */ | |
1678 | /* | |
1679 | * These registers are not logical-device | |
1680 | * specific, just that we won't need them if | |
1681 | * we don't use the VLM device | |
1682 | */ | |
1683 | confreg[2] = superio_inb(sioaddr, 0x2B); | |
1684 | confreg[3] = superio_inb(sioaddr, 0x25); | |
1685 | ||
1686 | if (confreg[2] & 0x40) { | |
1687 | pr_info("Using thermistors for temperature monitoring\n"); | |
1688 | } | |
1689 | if (confreg[3] & 0xE0) { | |
1690 | pr_info("VID inputs routed (mode %u)\n", | |
1691 | confreg[3] >> 5); | |
1692 | } | |
1da177e4 LT |
1693 | } |
1694 | } | |
1da177e4 LT |
1695 | } |
1696 | ||
070affa8 UKK |
1697 | superio_exit(sioaddr); |
1698 | return 0; | |
1da177e4 LT |
1699 | } |
1700 | ||
f641b588 JD |
1701 | static int __init pc87360_device_add(unsigned short address) |
1702 | { | |
b9783dce JD |
1703 | struct resource res[3]; |
1704 | int err, i, res_count; | |
f641b588 JD |
1705 | |
1706 | pdev = platform_device_alloc("pc87360", address); | |
1707 | if (!pdev) { | |
1708 | err = -ENOMEM; | |
9e991c6f | 1709 | pr_err("Device allocation failed\n"); |
f641b588 JD |
1710 | goto exit; |
1711 | } | |
1712 | ||
b9783dce JD |
1713 | memset(res, 0, 3 * sizeof(struct resource)); |
1714 | res_count = 0; | |
f641b588 JD |
1715 | for (i = 0; i < 3; i++) { |
1716 | if (!extra_isa[i]) | |
1717 | continue; | |
b9783dce JD |
1718 | res[res_count].start = extra_isa[i]; |
1719 | res[res_count].end = extra_isa[i] + PC87360_EXTENT - 1; | |
94c08e06 ZY |
1720 | res[res_count].name = "pc87360"; |
1721 | res[res_count].flags = IORESOURCE_IO; | |
b9acb64a | 1722 | |
b9783dce | 1723 | err = acpi_check_resource_conflict(&res[res_count]); |
b9acb64a JD |
1724 | if (err) |
1725 | goto exit_device_put; | |
1726 | ||
b9783dce JD |
1727 | res_count++; |
1728 | } | |
1729 | ||
1730 | err = platform_device_add_resources(pdev, res, res_count); | |
1731 | if (err) { | |
9e991c6f | 1732 | pr_err("Device resources addition failed (%d)\n", err); |
b9783dce | 1733 | goto exit_device_put; |
f641b588 JD |
1734 | } |
1735 | ||
1736 | err = platform_device_add(pdev); | |
1737 | if (err) { | |
9e991c6f | 1738 | pr_err("Device addition failed (%d)\n", err); |
f641b588 JD |
1739 | goto exit_device_put; |
1740 | } | |
1741 | ||
1742 | return 0; | |
1743 | ||
1744 | exit_device_put: | |
1745 | platform_device_put(pdev); | |
1746 | exit: | |
1747 | return err; | |
1748 | } | |
1749 | ||
1da177e4 LT |
1750 | static int __init pc87360_init(void) |
1751 | { | |
f641b588 JD |
1752 | int err, i; |
1753 | unsigned short address = 0; | |
1da177e4 LT |
1754 | |
1755 | if (pc87360_find(0x2e, &devid, extra_isa) | |
1756 | && pc87360_find(0x4e, &devid, extra_isa)) { | |
9e991c6f | 1757 | pr_warn("PC8736x not detected, module not inserted\n"); |
1da177e4 LT |
1758 | return -ENODEV; |
1759 | } | |
1760 | ||
1761 | /* Arbitrarily pick one of the addresses */ | |
1762 | for (i = 0; i < 3; i++) { | |
1763 | if (extra_isa[i] != 0x0000) { | |
2d8672c5 | 1764 | address = extra_isa[i]; |
1da177e4 LT |
1765 | break; |
1766 | } | |
1767 | } | |
1768 | ||
2d8672c5 | 1769 | if (address == 0x0000) { |
9e991c6f | 1770 | pr_warn("No active logical device, module not inserted\n"); |
1da177e4 LT |
1771 | return -ENODEV; |
1772 | } | |
1773 | ||
f641b588 JD |
1774 | err = platform_driver_register(&pc87360_driver); |
1775 | if (err) | |
1776 | goto exit; | |
1777 | ||
1778 | /* Sets global pdev as a side effect */ | |
1779 | err = pc87360_device_add(address); | |
1780 | if (err) | |
1781 | goto exit_driver; | |
1782 | ||
1783 | return 0; | |
1784 | ||
1785 | exit_driver: | |
1786 | platform_driver_unregister(&pc87360_driver); | |
1787 | exit: | |
1788 | return err; | |
1da177e4 LT |
1789 | } |
1790 | ||
1791 | static void __exit pc87360_exit(void) | |
1792 | { | |
f641b588 JD |
1793 | platform_device_unregister(pdev); |
1794 | platform_driver_unregister(&pc87360_driver); | |
1da177e4 LT |
1795 | } |
1796 | ||
7c81c60f | 1797 | MODULE_AUTHOR("Jean Delvare <jdelvare@suse.de>"); |
1da177e4 LT |
1798 | MODULE_DESCRIPTION("PC8736x hardware monitor"); |
1799 | MODULE_LICENSE("GPL"); | |
02e05005 | 1800 | MODULE_ALIAS("platform:" DRIVER_NAME); |
1da177e4 LT |
1801 | |
1802 | module_init(pc87360_init); | |
1803 | module_exit(pc87360_exit); |