diff options
author | Vadim V. Vlasov <vvlasov@dev.rtsoft.ru> | 2015-02-27 08:16:00 -0500 |
---|---|---|
committer | Guenter Roeck <linux@roeck-us.net> | 2015-03-09 12:59:36 -0400 |
commit | 9c947d25c96ec93485d60f7b783403d518c1418d (patch) | |
tree | 8d87bbe5f0d1a327f23f37b5e08316d50360a8ff | |
parent | e8433b42b60e799d55eb2476dc6cb3668c740063 (diff) |
hwmon: Add Nuvoton NCT7904 hwmon driver
The NCT7904D is a hardware monitor supporting up to 20 voltage sensors,
internal temperature sensor, Intel PECI and AMD SB-TSI CPU temperature
interface, up to 12 fan tachometer inputs, up to 4 fan control channels
with SmartFan.
Signed-off-by: Vadim V. Vlasov <vvlasov@dev.rtsoft.ru>
[Guenter Roeck: Fixed whitespace errors, dropped redundant comment]
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
-rw-r--r-- | Documentation/hwmon/nct7904 | 60 | ||||
-rw-r--r-- | drivers/hwmon/Kconfig | 10 | ||||
-rw-r--r-- | drivers/hwmon/Makefile | 1 | ||||
-rw-r--r-- | drivers/hwmon/nct7904.c | 592 |
4 files changed, 663 insertions, 0 deletions
diff --git a/Documentation/hwmon/nct7904 b/Documentation/hwmon/nct7904 new file mode 100644 index 000000000000..014f112e2a14 --- /dev/null +++ b/Documentation/hwmon/nct7904 | |||
@@ -0,0 +1,60 @@ | |||
1 | Kernel driver nct7904 | ||
2 | ==================== | ||
3 | |||
4 | Supported chip: | ||
5 | * Nuvoton NCT7904D | ||
6 | Prefix: nct7904 | ||
7 | Addresses: I2C 0x2d, 0x2e | ||
8 | Datasheet: Publicly available at Nuvoton website | ||
9 | http://www.nuvoton.com/ | ||
10 | |||
11 | Author: Vadim V. Vlasov <vvlasov@dev.rtsoft.ru> | ||
12 | |||
13 | |||
14 | Description | ||
15 | ----------- | ||
16 | |||
17 | The NCT7904D is a hardware monitor supporting up to 20 voltage sensors, | ||
18 | internal temperature sensor, Intel PECI and AMD SB-TSI CPU temperature | ||
19 | interface, up to 12 fan tachometer inputs, up to 4 fan control channels | ||
20 | with SmartFan. | ||
21 | |||
22 | |||
23 | Sysfs entries | ||
24 | ------------- | ||
25 | |||
26 | Currently, the driver supports only the following features: | ||
27 | |||
28 | in[1-20]_input Input voltage measurements (mV) | ||
29 | |||
30 | fan[1-12]_input Fan tachometer measurements (rpm) | ||
31 | |||
32 | temp1_input Local temperature (1/1000 degree, | ||
33 | 0.125 degree resolution) | ||
34 | |||
35 | temp[2-9]_input CPU temperatures (1/1000 degree, | ||
36 | 0.125 degree resolution) | ||
37 | |||
38 | fan[1-4]_mode R/W, 0/1 for manual or SmartFan mode | ||
39 | Setting SmartFan mode is supported only if it has been | ||
40 | previously configured by BIOS (or configuration EEPROM) | ||
41 | |||
42 | fan[1-4]_pwm R/O in SmartFan mode, R/W in manual control mode | ||
43 | |||
44 | The driver checks sensor control registers and does not export the sensors | ||
45 | that are not enabled. Anyway, a sensor that is enabled may actually be not | ||
46 | connected and thus provide zero readings. | ||
47 | |||
48 | |||
49 | Limitations | ||
50 | ----------- | ||
51 | |||
52 | The following features are not supported in current version: | ||
53 | |||
54 | - SmartFan control | ||
55 | - Watchdog | ||
56 | - GPIO | ||
57 | - external temperature sensors | ||
58 | - SMI | ||
59 | - min/max values | ||
60 | - many other... | ||
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 4b40ea79b20f..2a5dd697c142 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig | |||
@@ -1145,6 +1145,16 @@ config SENSORS_NCT7802 | |||
1145 | This driver can also be built as a module. If so, the module | 1145 | This driver can also be built as a module. If so, the module |
1146 | will be called nct7802. | 1146 | will be called nct7802. |
1147 | 1147 | ||
1148 | config SENSORS_NCT7904 | ||
1149 | tristate "Nuvoton NCT7904" | ||
1150 | depends on I2C | ||
1151 | help | ||
1152 | If you say yes here you get support for the Nuvoton NCT7904 | ||
1153 | hardware monitoring chip, including manual fan speed control. | ||
1154 | |||
1155 | This driver can also be built as a module. If so, the module | ||
1156 | will be called nct7904. | ||
1157 | |||
1148 | config SENSORS_PCF8591 | 1158 | config SENSORS_PCF8591 |
1149 | tristate "Philips PCF8591 ADC/DAC" | 1159 | tristate "Philips PCF8591 ADC/DAC" |
1150 | depends on I2C | 1160 | depends on I2C |
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 6c941472e707..b4a40f17e2aa 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile | |||
@@ -120,6 +120,7 @@ obj-$(CONFIG_SENSORS_MENF21BMC_HWMON) += menf21bmc_hwmon.o | |||
120 | obj-$(CONFIG_SENSORS_NCT6683) += nct6683.o | 120 | obj-$(CONFIG_SENSORS_NCT6683) += nct6683.o |
121 | obj-$(CONFIG_SENSORS_NCT6775) += nct6775.o | 121 | obj-$(CONFIG_SENSORS_NCT6775) += nct6775.o |
122 | obj-$(CONFIG_SENSORS_NCT7802) += nct7802.o | 122 | obj-$(CONFIG_SENSORS_NCT7802) += nct7802.o |
123 | obj-$(CONFIG_SENSORS_NCT7904) += nct7904.o | ||
123 | obj-$(CONFIG_SENSORS_NTC_THERMISTOR) += ntc_thermistor.o | 124 | obj-$(CONFIG_SENSORS_NTC_THERMISTOR) += ntc_thermistor.o |
124 | obj-$(CONFIG_SENSORS_PC87360) += pc87360.o | 125 | obj-$(CONFIG_SENSORS_PC87360) += pc87360.o |
125 | obj-$(CONFIG_SENSORS_PC87427) += pc87427.o | 126 | obj-$(CONFIG_SENSORS_PC87427) += pc87427.o |
diff --git a/drivers/hwmon/nct7904.c b/drivers/hwmon/nct7904.c new file mode 100644 index 000000000000..eaa8234e21d0 --- /dev/null +++ b/drivers/hwmon/nct7904.c | |||
@@ -0,0 +1,592 @@ | |||
1 | /* | ||
2 | * nct7904.c - driver for Nuvoton NCT7904D. | ||
3 | * | ||
4 | * Copyright (c) 2015 Kontron | ||
5 | * Author: Vadim V. Vlasov <vvlasov@dev.rtsoft.ru> | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or modify | ||
8 | * it under the terms of the GNU General Public License as published by | ||
9 | * the Free Software Foundation; either version 2 of the License, or | ||
10 | * (at your option) any later version. | ||
11 | * | ||
12 | * This program is distributed in the hope that it will be useful, | ||
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
15 | * GNU General Public License for more details. | ||
16 | */ | ||
17 | |||
18 | #include <linux/module.h> | ||
19 | #include <linux/device.h> | ||
20 | #include <linux/init.h> | ||
21 | #include <linux/i2c.h> | ||
22 | #include <linux/mutex.h> | ||
23 | #include <linux/hwmon.h> | ||
24 | #include <linux/hwmon-sysfs.h> | ||
25 | |||
26 | #define VENDOR_ID_REG 0x7A /* Any bank */ | ||
27 | #define NUVOTON_ID 0x50 | ||
28 | #define CHIP_ID_REG 0x7B /* Any bank */ | ||
29 | #define NCT7904_ID 0xC5 | ||
30 | #define DEVICE_ID_REG 0x7C /* Any bank */ | ||
31 | |||
32 | #define BANK_SEL_REG 0xFF | ||
33 | #define BANK_0 0x00 | ||
34 | #define BANK_1 0x01 | ||
35 | #define BANK_2 0x02 | ||
36 | #define BANK_3 0x03 | ||
37 | #define BANK_4 0x04 | ||
38 | #define BANK_MAX 0x04 | ||
39 | |||
40 | #define FANIN_MAX 12 /* Counted from 1 */ | ||
41 | #define VSEN_MAX 21 /* VSEN1..14, 3VDD, VBAT, V3VSB, | ||
42 | LTD (not a voltage), VSEN17..19 */ | ||
43 | #define FANCTL_MAX 4 /* Counted from 1 */ | ||
44 | #define TCPU_MAX 8 /* Counted from 1 */ | ||
45 | #define TEMP_MAX 4 /* Counted from 1 */ | ||
46 | |||
47 | #define VT_ADC_CTRL0_REG 0x20 /* Bank 0 */ | ||
48 | #define VT_ADC_CTRL1_REG 0x21 /* Bank 0 */ | ||
49 | #define VT_ADC_CTRL2_REG 0x22 /* Bank 0 */ | ||
50 | #define FANIN_CTRL0_REG 0x24 | ||
51 | #define FANIN_CTRL1_REG 0x25 | ||
52 | #define DTS_T_CTRL0_REG 0x26 | ||
53 | #define DTS_T_CTRL1_REG 0x27 | ||
54 | #define VT_ADC_MD_REG 0x2E | ||
55 | |||
56 | #define VSEN1_HV_REG 0x40 /* Bank 0; 2 regs (HV/LV) per sensor */ | ||
57 | #define TEMP_CH1_HV_REG 0x42 /* Bank 0; same as VSEN2_HV */ | ||
58 | #define LTD_HV_REG 0x62 /* Bank 0; 2 regs in VSEN range */ | ||
59 | #define FANIN1_HV_REG 0x80 /* Bank 0; 2 regs (HV/LV) per sensor */ | ||
60 | #define T_CPU1_HV_REG 0xA0 /* Bank 0; 2 regs (HV/LV) per sensor */ | ||
61 | |||
62 | #define PRTS_REG 0x03 /* Bank 2 */ | ||
63 | #define FANCTL1_FMR_REG 0x00 /* Bank 3; 1 reg per channel */ | ||
64 | #define FANCTL1_OUT_REG 0x10 /* Bank 3; 1 reg per channel */ | ||
65 | |||
66 | static const unsigned short normal_i2c[] = { | ||
67 | 0x2d, 0x2e, I2C_CLIENT_END | ||
68 | }; | ||
69 | |||
70 | struct nct7904_data { | ||
71 | struct i2c_client *client; | ||
72 | struct mutex bank_lock; | ||
73 | int bank_sel; | ||
74 | u32 fanin_mask; | ||
75 | u32 vsen_mask; | ||
76 | u32 tcpu_mask; | ||
77 | u8 fan_mode[FANCTL_MAX]; | ||
78 | }; | ||
79 | |||
80 | /* Access functions */ | ||
81 | static int nct7904_bank_lock(struct nct7904_data *data, unsigned bank) | ||
82 | { | ||
83 | int ret; | ||
84 | |||
85 | mutex_lock(&data->bank_lock); | ||
86 | if (data->bank_sel == bank) | ||
87 | return 0; | ||
88 | ret = i2c_smbus_write_byte_data(data->client, BANK_SEL_REG, bank); | ||
89 | if (ret == 0) | ||
90 | data->bank_sel = bank; | ||
91 | else | ||
92 | data->bank_sel = -1; | ||
93 | return ret; | ||
94 | } | ||
95 | |||
96 | static inline void nct7904_bank_release(struct nct7904_data *data) | ||
97 | { | ||
98 | mutex_unlock(&data->bank_lock); | ||
99 | } | ||
100 | |||
101 | /* Read 1-byte register. Returns unsigned reg or -ERRNO on error. */ | ||
102 | static int nct7904_read_reg(struct nct7904_data *data, | ||
103 | unsigned bank, unsigned reg) | ||
104 | { | ||
105 | struct i2c_client *client = data->client; | ||
106 | int ret; | ||
107 | |||
108 | ret = nct7904_bank_lock(data, bank); | ||
109 | if (ret == 0) | ||
110 | ret = i2c_smbus_read_byte_data(client, reg); | ||
111 | |||
112 | nct7904_bank_release(data); | ||
113 | return ret; | ||
114 | } | ||
115 | |||
116 | /* | ||
117 | * Read 2-byte register. Returns register in big-endian format or | ||
118 | * -ERRNO on error. | ||
119 | */ | ||
120 | static int nct7904_read_reg16(struct nct7904_data *data, | ||
121 | unsigned bank, unsigned reg) | ||
122 | { | ||
123 | struct i2c_client *client = data->client; | ||
124 | int ret, hi; | ||
125 | |||
126 | ret = nct7904_bank_lock(data, bank); | ||
127 | if (ret == 0) { | ||
128 | ret = i2c_smbus_read_byte_data(client, reg); | ||
129 | if (ret >= 0) { | ||
130 | hi = ret; | ||
131 | ret = i2c_smbus_read_byte_data(client, reg + 1); | ||
132 | if (ret >= 0) | ||
133 | ret |= hi << 8; | ||
134 | } | ||
135 | } | ||
136 | |||
137 | nct7904_bank_release(data); | ||
138 | return ret; | ||
139 | } | ||
140 | |||
141 | /* Write 1-byte register. Returns 0 or -ERRNO on error. */ | ||
142 | static int nct7904_write_reg(struct nct7904_data *data, | ||
143 | unsigned bank, unsigned reg, u8 val) | ||
144 | { | ||
145 | struct i2c_client *client = data->client; | ||
146 | int ret; | ||
147 | |||
148 | ret = nct7904_bank_lock(data, bank); | ||
149 | if (ret == 0) | ||
150 | ret = i2c_smbus_write_byte_data(client, reg, val); | ||
151 | |||
152 | nct7904_bank_release(data); | ||
153 | return ret; | ||
154 | } | ||
155 | |||
156 | /* FANIN ATTR */ | ||
157 | static ssize_t show_fan(struct device *dev, | ||
158 | struct device_attribute *devattr, char *buf) | ||
159 | { | ||
160 | int index = to_sensor_dev_attr(devattr)->index; | ||
161 | struct nct7904_data *data = dev_get_drvdata(dev); | ||
162 | int ret; | ||
163 | unsigned cnt, rpm; | ||
164 | |||
165 | ret = nct7904_read_reg16(data, BANK_0, FANIN1_HV_REG + index * 2); | ||
166 | if (ret < 0) | ||
167 | return ret; | ||
168 | cnt = ((ret & 0xff00) >> 3) | (ret & 0x1f); | ||
169 | if (cnt == 0x1fff) | ||
170 | rpm = 0; | ||
171 | else | ||
172 | rpm = 1350000 / cnt; | ||
173 | return sprintf(buf, "%u\n", rpm); | ||
174 | } | ||
175 | |||
176 | static umode_t nct7904_fanin_is_visible(struct kobject *kobj, | ||
177 | struct attribute *a, int n) | ||
178 | { | ||
179 | struct device *dev = container_of(kobj, struct device, kobj); | ||
180 | struct nct7904_data *data = dev_get_drvdata(dev); | ||
181 | |||
182 | if (data->fanin_mask & (1 << n)) | ||
183 | return a->mode; | ||
184 | return 0; | ||
185 | } | ||
186 | |||
187 | static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0); | ||
188 | static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 1); | ||
189 | static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, show_fan, NULL, 2); | ||
190 | static SENSOR_DEVICE_ATTR(fan4_input, S_IRUGO, show_fan, NULL, 3); | ||
191 | static SENSOR_DEVICE_ATTR(fan5_input, S_IRUGO, show_fan, NULL, 4); | ||
192 | static SENSOR_DEVICE_ATTR(fan6_input, S_IRUGO, show_fan, NULL, 5); | ||
193 | static SENSOR_DEVICE_ATTR(fan7_input, S_IRUGO, show_fan, NULL, 6); | ||
194 | static SENSOR_DEVICE_ATTR(fan8_input, S_IRUGO, show_fan, NULL, 7); | ||
195 | static SENSOR_DEVICE_ATTR(fan9_input, S_IRUGO, show_fan, NULL, 8); | ||
196 | static SENSOR_DEVICE_ATTR(fan10_input, S_IRUGO, show_fan, NULL, 9); | ||
197 | static SENSOR_DEVICE_ATTR(fan11_input, S_IRUGO, show_fan, NULL, 10); | ||
198 | static SENSOR_DEVICE_ATTR(fan12_input, S_IRUGO, show_fan, NULL, 11); | ||
199 | |||
200 | static struct attribute *nct7904_fanin_attrs[] = { | ||
201 | &sensor_dev_attr_fan1_input.dev_attr.attr, | ||
202 | &sensor_dev_attr_fan2_input.dev_attr.attr, | ||
203 | &sensor_dev_attr_fan3_input.dev_attr.attr, | ||
204 | &sensor_dev_attr_fan4_input.dev_attr.attr, | ||
205 | &sensor_dev_attr_fan5_input.dev_attr.attr, | ||
206 | &sensor_dev_attr_fan6_input.dev_attr.attr, | ||
207 | &sensor_dev_attr_fan7_input.dev_attr.attr, | ||
208 | &sensor_dev_attr_fan8_input.dev_attr.attr, | ||
209 | &sensor_dev_attr_fan9_input.dev_attr.attr, | ||
210 | &sensor_dev_attr_fan10_input.dev_attr.attr, | ||
211 | &sensor_dev_attr_fan11_input.dev_attr.attr, | ||
212 | &sensor_dev_attr_fan12_input.dev_attr.attr, | ||
213 | NULL | ||
214 | }; | ||
215 | |||
216 | static const struct attribute_group nct7904_fanin_group = { | ||
217 | .attrs = nct7904_fanin_attrs, | ||
218 | .is_visible = nct7904_fanin_is_visible, | ||
219 | }; | ||
220 | |||
221 | /* VSEN ATTR */ | ||
222 | static ssize_t show_voltage(struct device *dev, | ||
223 | struct device_attribute *devattr, char *buf) | ||
224 | { | ||
225 | int index = to_sensor_dev_attr(devattr)->index; | ||
226 | struct nct7904_data *data = dev_get_drvdata(dev); | ||
227 | int ret; | ||
228 | int volt; | ||
229 | |||
230 | ret = nct7904_read_reg16(data, BANK_0, VSEN1_HV_REG + index * 2); | ||
231 | if (ret < 0) | ||
232 | return ret; | ||
233 | volt = ((ret & 0xff00) >> 5) | (ret & 0x7); | ||
234 | if (index < 14) | ||
235 | volt *= 2; /* 0.002V scale */ | ||
236 | else | ||
237 | volt *= 6; /* 0.006V scale */ | ||
238 | |||
239 | return sprintf(buf, "%d\n", volt); | ||
240 | } | ||
241 | |||
242 | static ssize_t show_ltemp(struct device *dev, | ||
243 | struct device_attribute *devattr, char *buf) | ||
244 | { | ||
245 | struct nct7904_data *data = dev_get_drvdata(dev); | ||
246 | int ret; | ||
247 | int temp; | ||
248 | |||
249 | ret = nct7904_read_reg16(data, BANK_0, LTD_HV_REG); | ||
250 | if (ret < 0) | ||
251 | return ret; | ||
252 | temp = ((ret & 0xff00) >> 5) | (ret & 0x7); | ||
253 | temp = sign_extend32(temp, 10) * 125; | ||
254 | |||
255 | return sprintf(buf, "%d\n", temp); | ||
256 | } | ||
257 | |||
258 | static umode_t nct7904_vsen_is_visible(struct kobject *kobj, | ||
259 | struct attribute *a, int n) | ||
260 | { | ||
261 | struct device *dev = container_of(kobj, struct device, kobj); | ||
262 | struct nct7904_data *data = dev_get_drvdata(dev); | ||
263 | |||
264 | if (data->vsen_mask & (1 << n)) | ||
265 | return a->mode; | ||
266 | return 0; | ||
267 | } | ||
268 | |||
269 | static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, show_voltage, NULL, 0); | ||
270 | static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, show_voltage, NULL, 1); | ||
271 | static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO, show_voltage, NULL, 2); | ||
272 | static SENSOR_DEVICE_ATTR(in4_input, S_IRUGO, show_voltage, NULL, 3); | ||
273 | static SENSOR_DEVICE_ATTR(in5_input, S_IRUGO, show_voltage, NULL, 4); | ||
274 | static SENSOR_DEVICE_ATTR(in6_input, S_IRUGO, show_voltage, NULL, 5); | ||
275 | static SENSOR_DEVICE_ATTR(in7_input, S_IRUGO, show_voltage, NULL, 6); | ||
276 | static SENSOR_DEVICE_ATTR(in8_input, S_IRUGO, show_voltage, NULL, 7); | ||
277 | static SENSOR_DEVICE_ATTR(in9_input, S_IRUGO, show_voltage, NULL, 8); | ||
278 | static SENSOR_DEVICE_ATTR(in10_input, S_IRUGO, show_voltage, NULL, 9); | ||
279 | static SENSOR_DEVICE_ATTR(in11_input, S_IRUGO, show_voltage, NULL, 10); | ||
280 | static SENSOR_DEVICE_ATTR(in12_input, S_IRUGO, show_voltage, NULL, 11); | ||
281 | static SENSOR_DEVICE_ATTR(in13_input, S_IRUGO, show_voltage, NULL, 12); | ||
282 | static SENSOR_DEVICE_ATTR(in14_input, S_IRUGO, show_voltage, NULL, 13); | ||
283 | /* | ||
284 | * Next 3 voltage sensors have specific names in the Nuvoton doc | ||
285 | * (3VDD, VBAT, 3VSB) but we use vacant numbers for them. | ||
286 | */ | ||
287 | static SENSOR_DEVICE_ATTR(in15_input, S_IRUGO, show_voltage, NULL, 14); | ||
288 | static SENSOR_DEVICE_ATTR(in16_input, S_IRUGO, show_voltage, NULL, 15); | ||
289 | static SENSOR_DEVICE_ATTR(in20_input, S_IRUGO, show_voltage, NULL, 16); | ||
290 | /* This is not a voltage, but a local temperature sensor. */ | ||
291 | static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_ltemp, NULL, 0); | ||
292 | static SENSOR_DEVICE_ATTR(in17_input, S_IRUGO, show_voltage, NULL, 18); | ||
293 | static SENSOR_DEVICE_ATTR(in18_input, S_IRUGO, show_voltage, NULL, 19); | ||
294 | static SENSOR_DEVICE_ATTR(in19_input, S_IRUGO, show_voltage, NULL, 20); | ||
295 | |||
296 | static struct attribute *nct7904_vsen_attrs[] = { | ||
297 | &sensor_dev_attr_in1_input.dev_attr.attr, | ||
298 | &sensor_dev_attr_in2_input.dev_attr.attr, | ||
299 | &sensor_dev_attr_in3_input.dev_attr.attr, | ||
300 | &sensor_dev_attr_in4_input.dev_attr.attr, | ||
301 | &sensor_dev_attr_in5_input.dev_attr.attr, | ||
302 | &sensor_dev_attr_in6_input.dev_attr.attr, | ||
303 | &sensor_dev_attr_in7_input.dev_attr.attr, | ||
304 | &sensor_dev_attr_in8_input.dev_attr.attr, | ||
305 | &sensor_dev_attr_in9_input.dev_attr.attr, | ||
306 | &sensor_dev_attr_in10_input.dev_attr.attr, | ||
307 | &sensor_dev_attr_in11_input.dev_attr.attr, | ||
308 | &sensor_dev_attr_in12_input.dev_attr.attr, | ||
309 | &sensor_dev_attr_in13_input.dev_attr.attr, | ||
310 | &sensor_dev_attr_in14_input.dev_attr.attr, | ||
311 | &sensor_dev_attr_in15_input.dev_attr.attr, | ||
312 | &sensor_dev_attr_in16_input.dev_attr.attr, | ||
313 | &sensor_dev_attr_in20_input.dev_attr.attr, | ||
314 | &sensor_dev_attr_temp1_input.dev_attr.attr, | ||
315 | &sensor_dev_attr_in17_input.dev_attr.attr, | ||
316 | &sensor_dev_attr_in18_input.dev_attr.attr, | ||
317 | &sensor_dev_attr_in19_input.dev_attr.attr, | ||
318 | NULL | ||
319 | }; | ||
320 | |||
321 | static const struct attribute_group nct7904_vsen_group = { | ||
322 | .attrs = nct7904_vsen_attrs, | ||
323 | .is_visible = nct7904_vsen_is_visible, | ||
324 | }; | ||
325 | |||
326 | /* CPU_TEMP ATTR */ | ||
327 | static ssize_t show_tcpu(struct device *dev, | ||
328 | struct device_attribute *devattr, char *buf) | ||
329 | { | ||
330 | int index = to_sensor_dev_attr(devattr)->index; | ||
331 | struct nct7904_data *data = dev_get_drvdata(dev); | ||
332 | int ret; | ||
333 | int temp; | ||
334 | |||
335 | ret = nct7904_read_reg16(data, BANK_0, T_CPU1_HV_REG + index * 2); | ||
336 | if (ret < 0) | ||
337 | return ret; | ||
338 | |||
339 | temp = ((ret & 0xff00) >> 5) | (ret & 0x7); | ||
340 | temp = sign_extend32(temp, 10) * 125; | ||
341 | return sprintf(buf, "%d\n", temp); | ||
342 | } | ||
343 | |||
344 | static umode_t nct7904_tcpu_is_visible(struct kobject *kobj, | ||
345 | struct attribute *a, int n) | ||
346 | { | ||
347 | struct device *dev = container_of(kobj, struct device, kobj); | ||
348 | struct nct7904_data *data = dev_get_drvdata(dev); | ||
349 | |||
350 | if (data->tcpu_mask & (1 << n)) | ||
351 | return a->mode; | ||
352 | return 0; | ||
353 | } | ||
354 | |||
355 | /* "temp1_input" reserved for local temp */ | ||
356 | static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_tcpu, NULL, 0); | ||
357 | static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_tcpu, NULL, 1); | ||
358 | static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_tcpu, NULL, 2); | ||
359 | static SENSOR_DEVICE_ATTR(temp5_input, S_IRUGO, show_tcpu, NULL, 3); | ||
360 | static SENSOR_DEVICE_ATTR(temp6_input, S_IRUGO, show_tcpu, NULL, 4); | ||
361 | static SENSOR_DEVICE_ATTR(temp7_input, S_IRUGO, show_tcpu, NULL, 5); | ||
362 | static SENSOR_DEVICE_ATTR(temp8_input, S_IRUGO, show_tcpu, NULL, 6); | ||
363 | static SENSOR_DEVICE_ATTR(temp9_input, S_IRUGO, show_tcpu, NULL, 7); | ||
364 | |||
365 | static struct attribute *nct7904_tcpu_attrs[] = { | ||
366 | &sensor_dev_attr_temp2_input.dev_attr.attr, | ||
367 | &sensor_dev_attr_temp3_input.dev_attr.attr, | ||
368 | &sensor_dev_attr_temp4_input.dev_attr.attr, | ||
369 | &sensor_dev_attr_temp5_input.dev_attr.attr, | ||
370 | &sensor_dev_attr_temp6_input.dev_attr.attr, | ||
371 | &sensor_dev_attr_temp7_input.dev_attr.attr, | ||
372 | &sensor_dev_attr_temp8_input.dev_attr.attr, | ||
373 | &sensor_dev_attr_temp9_input.dev_attr.attr, | ||
374 | NULL | ||
375 | }; | ||
376 | |||
377 | static const struct attribute_group nct7904_tcpu_group = { | ||
378 | .attrs = nct7904_tcpu_attrs, | ||
379 | .is_visible = nct7904_tcpu_is_visible, | ||
380 | }; | ||
381 | |||
382 | /* PWM ATTR */ | ||
383 | static ssize_t store_pwm(struct device *dev, struct device_attribute *devattr, | ||
384 | const char *buf, size_t count) | ||
385 | { | ||
386 | int index = to_sensor_dev_attr(devattr)->index; | ||
387 | struct nct7904_data *data = dev_get_drvdata(dev); | ||
388 | unsigned long val; | ||
389 | int ret; | ||
390 | |||
391 | if (kstrtoul(buf, 10, &val) < 0) | ||
392 | return -EINVAL; | ||
393 | if (val > 255) | ||
394 | return -EINVAL; | ||
395 | |||
396 | ret = nct7904_write_reg(data, BANK_3, FANCTL1_OUT_REG + index, val); | ||
397 | |||
398 | return ret ? ret : count; | ||
399 | } | ||
400 | |||
401 | static ssize_t show_pwm(struct device *dev, | ||
402 | struct device_attribute *devattr, char *buf) | ||
403 | { | ||
404 | int index = to_sensor_dev_attr(devattr)->index; | ||
405 | struct nct7904_data *data = dev_get_drvdata(dev); | ||
406 | int val; | ||
407 | |||
408 | val = nct7904_read_reg(data, BANK_3, FANCTL1_OUT_REG + index); | ||
409 | if (val < 0) | ||
410 | return val; | ||
411 | |||
412 | return sprintf(buf, "%d\n", val); | ||
413 | } | ||
414 | |||
415 | static ssize_t store_mode(struct device *dev, struct device_attribute *devattr, | ||
416 | const char *buf, size_t count) | ||
417 | { | ||
418 | int index = to_sensor_dev_attr(devattr)->index; | ||
419 | struct nct7904_data *data = dev_get_drvdata(dev); | ||
420 | unsigned long val; | ||
421 | int ret; | ||
422 | |||
423 | if (kstrtoul(buf, 10, &val) < 0) | ||
424 | return -EINVAL; | ||
425 | if (val > 1 || (val && !data->fan_mode[index])) | ||
426 | return -EINVAL; | ||
427 | |||
428 | ret = nct7904_write_reg(data, BANK_3, FANCTL1_FMR_REG + index, | ||
429 | val ? data->fan_mode[index] : 0); | ||
430 | |||
431 | return ret ? ret : count; | ||
432 | } | ||
433 | |||
434 | /* Return 0 for manual mode or 1 for SmartFan mode */ | ||
435 | static ssize_t show_mode(struct device *dev, | ||
436 | struct device_attribute *devattr, char *buf) | ||
437 | { | ||
438 | int index = to_sensor_dev_attr(devattr)->index; | ||
439 | struct nct7904_data *data = dev_get_drvdata(dev); | ||
440 | int val; | ||
441 | |||
442 | val = nct7904_read_reg(data, BANK_3, FANCTL1_FMR_REG + index); | ||
443 | if (val < 0) | ||
444 | return val; | ||
445 | |||
446 | return sprintf(buf, "%d\n", val ? 1 : 0); | ||
447 | } | ||
448 | |||
449 | /* 2 attributes per channel: pwm and mode */ | ||
450 | static SENSOR_DEVICE_ATTR(fan1_pwm, S_IRUGO | S_IWUSR, | ||
451 | show_pwm, store_pwm, 0); | ||
452 | static SENSOR_DEVICE_ATTR(fan1_mode, S_IRUGO | S_IWUSR, | ||
453 | show_mode, store_mode, 0); | ||
454 | static SENSOR_DEVICE_ATTR(fan2_pwm, S_IRUGO | S_IWUSR, | ||
455 | show_pwm, store_pwm, 1); | ||
456 | static SENSOR_DEVICE_ATTR(fan2_mode, S_IRUGO | S_IWUSR, | ||
457 | show_mode, store_mode, 1); | ||
458 | static SENSOR_DEVICE_ATTR(fan3_pwm, S_IRUGO | S_IWUSR, | ||
459 | show_pwm, store_pwm, 2); | ||
460 | static SENSOR_DEVICE_ATTR(fan3_mode, S_IRUGO | S_IWUSR, | ||
461 | show_mode, store_mode, 2); | ||
462 | static SENSOR_DEVICE_ATTR(fan4_pwm, S_IRUGO | S_IWUSR, | ||
463 | show_pwm, store_pwm, 3); | ||
464 | static SENSOR_DEVICE_ATTR(fan4_mode, S_IRUGO | S_IWUSR, | ||
465 | show_mode, store_mode, 3); | ||
466 | |||
467 | static struct attribute *nct7904_fanctl_attrs[] = { | ||
468 | &sensor_dev_attr_fan1_pwm.dev_attr.attr, | ||
469 | &sensor_dev_attr_fan1_mode.dev_attr.attr, | ||
470 | &sensor_dev_attr_fan2_pwm.dev_attr.attr, | ||
471 | &sensor_dev_attr_fan2_mode.dev_attr.attr, | ||
472 | &sensor_dev_attr_fan3_pwm.dev_attr.attr, | ||
473 | &sensor_dev_attr_fan3_mode.dev_attr.attr, | ||
474 | &sensor_dev_attr_fan4_pwm.dev_attr.attr, | ||
475 | &sensor_dev_attr_fan4_mode.dev_attr.attr, | ||
476 | NULL | ||
477 | }; | ||
478 | |||
479 | static const struct attribute_group nct7904_fanctl_group = { | ||
480 | .attrs = nct7904_fanctl_attrs, | ||
481 | }; | ||
482 | |||
483 | static const struct attribute_group *nct7904_groups[] = { | ||
484 | &nct7904_fanin_group, | ||
485 | &nct7904_vsen_group, | ||
486 | &nct7904_tcpu_group, | ||
487 | &nct7904_fanctl_group, | ||
488 | NULL | ||
489 | }; | ||
490 | |||
491 | /* Return 0 if detection is successful, -ENODEV otherwise */ | ||
492 | static int nct7904_detect(struct i2c_client *client, | ||
493 | struct i2c_board_info *info) | ||
494 | { | ||
495 | struct i2c_adapter *adapter = client->adapter; | ||
496 | |||
497 | if (!i2c_check_functionality(adapter, | ||
498 | I2C_FUNC_SMBUS_READ_BYTE | | ||
499 | I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) | ||
500 | return -ENODEV; | ||
501 | |||
502 | /* Determine the chip type. */ | ||
503 | if (i2c_smbus_read_byte_data(client, VENDOR_ID_REG) != NUVOTON_ID || | ||
504 | i2c_smbus_read_byte_data(client, CHIP_ID_REG) != NCT7904_ID || | ||
505 | (i2c_smbus_read_byte_data(client, DEVICE_ID_REG) & 0xf0) != 0x50) | ||
506 | return -ENODEV; | ||
507 | |||
508 | strlcpy(info->type, "nct7904", I2C_NAME_SIZE); | ||
509 | |||
510 | return 0; | ||
511 | } | ||
512 | |||
513 | static int nct7904_probe(struct i2c_client *client, | ||
514 | const struct i2c_device_id *id) | ||
515 | { | ||
516 | struct nct7904_data *data; | ||
517 | struct device *hwmon_dev; | ||
518 | struct device *dev = &client->dev; | ||
519 | int ret, i; | ||
520 | u32 mask; | ||
521 | |||
522 | data = devm_kzalloc(dev, sizeof(struct nct7904_data), GFP_KERNEL); | ||
523 | if (!data) | ||
524 | return -ENOMEM; | ||
525 | |||
526 | data->client = client; | ||
527 | mutex_init(&data->bank_lock); | ||
528 | data->bank_sel = -1; | ||
529 | |||
530 | /* Setup sensor groups. */ | ||
531 | /* FANIN attributes */ | ||
532 | ret = nct7904_read_reg16(data, BANK_0, FANIN_CTRL0_REG); | ||
533 | if (ret < 0) | ||
534 | return ret; | ||
535 | data->fanin_mask = (ret >> 8) | ((ret & 0xff) << 8); | ||
536 | |||
537 | /* | ||
538 | * VSEN attributes | ||
539 | * | ||
540 | * Note: voltage sensors overlap with external temperature | ||
541 | * sensors. So, if we ever decide to support the latter | ||
542 | * we will have to adjust 'vsen_mask' accordingly. | ||
543 | */ | ||
544 | mask = 0; | ||
545 | ret = nct7904_read_reg16(data, BANK_0, VT_ADC_CTRL0_REG); | ||
546 | if (ret >= 0) | ||
547 | mask = (ret >> 8) | ((ret & 0xff) << 8); | ||
548 | ret = nct7904_read_reg(data, BANK_0, VT_ADC_CTRL2_REG); | ||
549 | if (ret >= 0) | ||
550 | mask |= (ret << 16); | ||
551 | data->vsen_mask = mask; | ||
552 | |||
553 | /* CPU_TEMP attributes */ | ||
554 | ret = nct7904_read_reg16(data, BANK_0, DTS_T_CTRL0_REG); | ||
555 | if (ret < 0) | ||
556 | return ret; | ||
557 | data->tcpu_mask = ((ret >> 8) & 0xf) | ((ret & 0xf) << 4); | ||
558 | |||
559 | for (i = 0; i < FANCTL_MAX; i++) { | ||
560 | ret = nct7904_read_reg(data, BANK_3, FANCTL1_FMR_REG + i); | ||
561 | if (ret < 0) | ||
562 | return ret; | ||
563 | data->fan_mode[i] = ret; | ||
564 | } | ||
565 | |||
566 | hwmon_dev = | ||
567 | devm_hwmon_device_register_with_groups(dev, client->name, data, | ||
568 | nct7904_groups); | ||
569 | return PTR_ERR_OR_ZERO(hwmon_dev); | ||
570 | } | ||
571 | |||
572 | static const struct i2c_device_id nct7904_id[] = { | ||
573 | {"nct7904", 0}, | ||
574 | {} | ||
575 | }; | ||
576 | |||
577 | static struct i2c_driver nct7904_driver = { | ||
578 | .class = I2C_CLASS_HWMON, | ||
579 | .driver = { | ||
580 | .name = "nct7904", | ||
581 | }, | ||
582 | .probe = nct7904_probe, | ||
583 | .id_table = nct7904_id, | ||
584 | .detect = nct7904_detect, | ||
585 | .address_list = normal_i2c, | ||
586 | }; | ||
587 | |||
588 | module_i2c_driver(nct7904_driver); | ||
589 | |||
590 | MODULE_AUTHOR("Vadim V. Vlasov <vvlasov@dev.rtsoft.ru>"); | ||
591 | MODULE_DESCRIPTION("Hwmon driver for NUVOTON NCT7904"); | ||
592 | MODULE_LICENSE("GPL"); | ||