diff options
author | Lei YU <mine260309@gmail.com> | 2017-11-12 22:27:33 -0500 |
---|---|---|
committer | Guenter Roeck <linux@roeck-us.net> | 2018-01-02 18:05:34 -0500 |
commit | ee249f271524d111aed8d6e7c61e220aa6b4d714 (patch) | |
tree | c63c697758f065320abdb1f75c207216de70c189 | |
parent | 30a7acd573899fd8b8ac39236eff6468b195ac7d (diff) |
hwmon: Add W83773G driver
Nuvoton W83773G is a hardware monitor IC providing one local
temperature and two remote temperature sensors.
Signed-off-by: Lei YU <mine260309@gmail.com>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
-rw-r--r-- | drivers/hwmon/Kconfig | 10 | ||||
-rw-r--r-- | drivers/hwmon/Makefile | 1 | ||||
-rw-r--r-- | drivers/hwmon/w83773g.c | 329 |
3 files changed, 340 insertions, 0 deletions
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 7ad017690e3a..530ff7c9234c 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig | |||
@@ -1725,6 +1725,16 @@ config SENSORS_VT8231 | |||
1725 | This driver can also be built as a module. If so, the module | 1725 | This driver can also be built as a module. If so, the module |
1726 | will be called vt8231. | 1726 | will be called vt8231. |
1727 | 1727 | ||
1728 | config SENSORS_W83773G | ||
1729 | tristate "Nuvoton W83773G" | ||
1730 | depends on I2C | ||
1731 | help | ||
1732 | If you say yes here you get support for the Nuvoton W83773G hardware | ||
1733 | monitoring chip. | ||
1734 | |||
1735 | This driver can also be built as a module. If so, the module | ||
1736 | will be called w83773g. | ||
1737 | |||
1728 | config SENSORS_W83781D | 1738 | config SENSORS_W83781D |
1729 | tristate "Winbond W83781D, W83782D, W83783S, Asus AS99127F" | 1739 | tristate "Winbond W83781D, W83782D, W83783S, Asus AS99127F" |
1730 | depends on I2C | 1740 | depends on I2C |
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 0fe489fab663..f814b4ace138 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile | |||
@@ -14,6 +14,7 @@ obj-$(CONFIG_SENSORS_ATK0110) += asus_atk0110.o | |||
14 | # asb100, then w83781d go first, as they can override other drivers' addresses. | 14 | # asb100, then w83781d go first, as they can override other drivers' addresses. |
15 | obj-$(CONFIG_SENSORS_ASB100) += asb100.o | 15 | obj-$(CONFIG_SENSORS_ASB100) += asb100.o |
16 | obj-$(CONFIG_SENSORS_W83627HF) += w83627hf.o | 16 | obj-$(CONFIG_SENSORS_W83627HF) += w83627hf.o |
17 | obj-$(CONFIG_SENSORS_W83773G) += w83773g.o | ||
17 | obj-$(CONFIG_SENSORS_W83792D) += w83792d.o | 18 | obj-$(CONFIG_SENSORS_W83792D) += w83792d.o |
18 | obj-$(CONFIG_SENSORS_W83793) += w83793.o | 19 | obj-$(CONFIG_SENSORS_W83793) += w83793.o |
19 | obj-$(CONFIG_SENSORS_W83795) += w83795.o | 20 | obj-$(CONFIG_SENSORS_W83795) += w83795.o |
diff --git a/drivers/hwmon/w83773g.c b/drivers/hwmon/w83773g.c new file mode 100644 index 000000000000..0b97c285b049 --- /dev/null +++ b/drivers/hwmon/w83773g.c | |||
@@ -0,0 +1,329 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2017 IBM Corp. | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License as published by | ||
6 | * the Free Software Foundation; either version 2 of the License, or | ||
7 | * (at your option) any later version. | ||
8 | * | ||
9 | * Driver for the Nuvoton W83773G SMBus temperature sensor IC. | ||
10 | * Supported models: W83773G | ||
11 | */ | ||
12 | |||
13 | #include <linux/module.h> | ||
14 | #include <linux/init.h> | ||
15 | #include <linux/i2c.h> | ||
16 | #include <linux/hwmon.h> | ||
17 | #include <linux/hwmon-sysfs.h> | ||
18 | #include <linux/err.h> | ||
19 | #include <linux/of_device.h> | ||
20 | #include <linux/regmap.h> | ||
21 | |||
22 | /* W83773 has 3 channels */ | ||
23 | #define W83773_CHANNELS 3 | ||
24 | |||
25 | /* The W83773 registers */ | ||
26 | #define W83773_CONVERSION_RATE_REG_READ 0x04 | ||
27 | #define W83773_CONVERSION_RATE_REG_WRITE 0x0A | ||
28 | #define W83773_MANUFACTURER_ID_REG 0xFE | ||
29 | #define W83773_LOCAL_TEMP 0x00 | ||
30 | |||
31 | static const u8 W83773_STATUS[2] = { 0x02, 0x17 }; | ||
32 | |||
33 | static const u8 W83773_TEMP_LSB[2] = { 0x10, 0x25 }; | ||
34 | static const u8 W83773_TEMP_MSB[2] = { 0x01, 0x24 }; | ||
35 | |||
36 | static const u8 W83773_OFFSET_LSB[2] = { 0x12, 0x16 }; | ||
37 | static const u8 W83773_OFFSET_MSB[2] = { 0x11, 0x15 }; | ||
38 | |||
39 | /* this is the number of sensors in the device */ | ||
40 | static const struct i2c_device_id w83773_id[] = { | ||
41 | { "w83773g" }, | ||
42 | { } | ||
43 | }; | ||
44 | |||
45 | MODULE_DEVICE_TABLE(i2c, w83773_id); | ||
46 | |||
47 | static const struct of_device_id w83773_of_match[] = { | ||
48 | { | ||
49 | .compatible = "nuvoton,w83773g" | ||
50 | }, | ||
51 | { }, | ||
52 | }; | ||
53 | MODULE_DEVICE_TABLE(of, w83773_of_match); | ||
54 | |||
55 | static inline long temp_of_local(s8 reg) | ||
56 | { | ||
57 | return reg * 1000; | ||
58 | } | ||
59 | |||
60 | static inline long temp_of_remote(s8 hb, u8 lb) | ||
61 | { | ||
62 | return (hb << 3 | lb >> 5) * 125; | ||
63 | } | ||
64 | |||
65 | static int get_local_temp(struct regmap *regmap, long *val) | ||
66 | { | ||
67 | unsigned int regval; | ||
68 | int ret; | ||
69 | |||
70 | ret = regmap_read(regmap, W83773_LOCAL_TEMP, ®val); | ||
71 | if (ret < 0) | ||
72 | return ret; | ||
73 | |||
74 | *val = temp_of_local(regval); | ||
75 | return 0; | ||
76 | } | ||
77 | |||
78 | static int get_remote_temp(struct regmap *regmap, int index, long *val) | ||
79 | { | ||
80 | unsigned int regval_high; | ||
81 | unsigned int regval_low; | ||
82 | int ret; | ||
83 | |||
84 | ret = regmap_read(regmap, W83773_TEMP_MSB[index], ®val_high); | ||
85 | if (ret < 0) | ||
86 | return ret; | ||
87 | |||
88 | ret = regmap_read(regmap, W83773_TEMP_LSB[index], ®val_low); | ||
89 | if (ret < 0) | ||
90 | return ret; | ||
91 | |||
92 | *val = temp_of_remote(regval_high, regval_low); | ||
93 | return 0; | ||
94 | } | ||
95 | |||
96 | static int get_fault(struct regmap *regmap, int index, long *val) | ||
97 | { | ||
98 | unsigned int regval; | ||
99 | int ret; | ||
100 | |||
101 | ret = regmap_read(regmap, W83773_STATUS[index], ®val); | ||
102 | if (ret < 0) | ||
103 | return ret; | ||
104 | |||
105 | *val = (u8)regval & 0x04 >> 2; | ||
106 | return 0; | ||
107 | } | ||
108 | |||
109 | static int get_offset(struct regmap *regmap, int index, long *val) | ||
110 | { | ||
111 | unsigned int regval_high; | ||
112 | unsigned int regval_low; | ||
113 | int ret; | ||
114 | |||
115 | ret = regmap_read(regmap, W83773_OFFSET_MSB[index], ®val_high); | ||
116 | if (ret < 0) | ||
117 | return ret; | ||
118 | |||
119 | ret = regmap_read(regmap, W83773_OFFSET_LSB[index], ®val_low); | ||
120 | if (ret < 0) | ||
121 | return ret; | ||
122 | |||
123 | *val = temp_of_remote(regval_high, regval_low); | ||
124 | return 0; | ||
125 | } | ||
126 | |||
127 | static int set_offset(struct regmap *regmap, int index, long val) | ||
128 | { | ||
129 | int ret; | ||
130 | u8 high_byte; | ||
131 | u8 low_byte; | ||
132 | |||
133 | val = clamp_val(val, -127825, 127825); | ||
134 | /* offset value equals to (high_byte << 3 | low_byte >> 5) * 125 */ | ||
135 | val /= 125; | ||
136 | high_byte = val >> 3; | ||
137 | low_byte = (val & 0x07) << 5; | ||
138 | |||
139 | ret = regmap_write(regmap, W83773_OFFSET_MSB[index], high_byte); | ||
140 | if (ret < 0) | ||
141 | return ret; | ||
142 | |||
143 | return regmap_write(regmap, W83773_OFFSET_LSB[index], low_byte); | ||
144 | } | ||
145 | |||
146 | static int get_update_interval(struct regmap *regmap, long *val) | ||
147 | { | ||
148 | unsigned int regval; | ||
149 | int ret; | ||
150 | |||
151 | ret = regmap_read(regmap, W83773_CONVERSION_RATE_REG_READ, ®val); | ||
152 | if (ret < 0) | ||
153 | return ret; | ||
154 | |||
155 | *val = 16000 >> regval; | ||
156 | return 0; | ||
157 | } | ||
158 | |||
159 | static int set_update_interval(struct regmap *regmap, long val) | ||
160 | { | ||
161 | int rate; | ||
162 | |||
163 | /* | ||
164 | * For valid rates, interval can be calculated as | ||
165 | * interval = (1 << (8 - rate)) * 62.5; | ||
166 | * Rounded rate is therefore | ||
167 | * rate = 8 - __fls(interval * 8 / (62.5 * 7)); | ||
168 | * Use clamp_val() to avoid overflows, and to ensure valid input | ||
169 | * for __fls. | ||
170 | */ | ||
171 | val = clamp_val(val, 62, 16000) * 10; | ||
172 | rate = 8 - __fls((val * 8 / (625 * 7))); | ||
173 | return regmap_write(regmap, W83773_CONVERSION_RATE_REG_WRITE, rate); | ||
174 | } | ||
175 | |||
176 | static int w83773_read(struct device *dev, enum hwmon_sensor_types type, | ||
177 | u32 attr, int channel, long *val) | ||
178 | { | ||
179 | struct regmap *regmap = dev_get_drvdata(dev); | ||
180 | |||
181 | if (type == hwmon_chip) { | ||
182 | if (attr == hwmon_chip_update_interval) | ||
183 | return get_update_interval(regmap, val); | ||
184 | return -EOPNOTSUPP; | ||
185 | } | ||
186 | |||
187 | switch (attr) { | ||
188 | case hwmon_temp_input: | ||
189 | if (channel == 0) | ||
190 | return get_local_temp(regmap, val); | ||
191 | return get_remote_temp(regmap, channel - 1, val); | ||
192 | case hwmon_temp_fault: | ||
193 | return get_fault(regmap, channel - 1, val); | ||
194 | case hwmon_temp_offset: | ||
195 | return get_offset(regmap, channel - 1, val); | ||
196 | default: | ||
197 | return -EOPNOTSUPP; | ||
198 | } | ||
199 | } | ||
200 | |||
201 | static int w83773_write(struct device *dev, enum hwmon_sensor_types type, | ||
202 | u32 attr, int channel, long val) | ||
203 | { | ||
204 | struct regmap *regmap = dev_get_drvdata(dev); | ||
205 | |||
206 | if (type == hwmon_chip && attr == hwmon_chip_update_interval) | ||
207 | return set_update_interval(regmap, val); | ||
208 | |||
209 | if (type == hwmon_temp && attr == hwmon_temp_offset) | ||
210 | return set_offset(regmap, channel - 1, val); | ||
211 | |||
212 | return -EOPNOTSUPP; | ||
213 | } | ||
214 | |||
215 | static umode_t w83773_is_visible(const void *data, enum hwmon_sensor_types type, | ||
216 | u32 attr, int channel) | ||
217 | { | ||
218 | switch (type) { | ||
219 | case hwmon_chip: | ||
220 | switch (attr) { | ||
221 | case hwmon_chip_update_interval: | ||
222 | return 0644; | ||
223 | } | ||
224 | break; | ||
225 | case hwmon_temp: | ||
226 | switch (attr) { | ||
227 | case hwmon_temp_input: | ||
228 | case hwmon_temp_fault: | ||
229 | return 0444; | ||
230 | case hwmon_temp_offset: | ||
231 | return 0644; | ||
232 | } | ||
233 | break; | ||
234 | default: | ||
235 | break; | ||
236 | } | ||
237 | return 0; | ||
238 | } | ||
239 | |||
240 | static const u32 w83773_chip_config[] = { | ||
241 | HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL, | ||
242 | 0 | ||
243 | }; | ||
244 | |||
245 | static const struct hwmon_channel_info w83773_chip = { | ||
246 | .type = hwmon_chip, | ||
247 | .config = w83773_chip_config, | ||
248 | }; | ||
249 | |||
250 | static const u32 w83773_temp_config[] = { | ||
251 | HWMON_T_INPUT, | ||
252 | HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_OFFSET, | ||
253 | HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_OFFSET, | ||
254 | 0 | ||
255 | }; | ||
256 | |||
257 | static const struct hwmon_channel_info w83773_temp = { | ||
258 | .type = hwmon_temp, | ||
259 | .config = w83773_temp_config, | ||
260 | }; | ||
261 | |||
262 | static const struct hwmon_channel_info *w83773_info[] = { | ||
263 | &w83773_chip, | ||
264 | &w83773_temp, | ||
265 | NULL | ||
266 | }; | ||
267 | |||
268 | static const struct hwmon_ops w83773_ops = { | ||
269 | .is_visible = w83773_is_visible, | ||
270 | .read = w83773_read, | ||
271 | .write = w83773_write, | ||
272 | }; | ||
273 | |||
274 | static const struct hwmon_chip_info w83773_chip_info = { | ||
275 | .ops = &w83773_ops, | ||
276 | .info = w83773_info, | ||
277 | }; | ||
278 | |||
279 | static const struct regmap_config w83773_regmap_config = { | ||
280 | .reg_bits = 8, | ||
281 | .val_bits = 8, | ||
282 | }; | ||
283 | |||
284 | static int w83773_probe(struct i2c_client *client, | ||
285 | const struct i2c_device_id *id) | ||
286 | { | ||
287 | struct device *dev = &client->dev; | ||
288 | struct device *hwmon_dev; | ||
289 | struct regmap *regmap; | ||
290 | int ret; | ||
291 | |||
292 | regmap = devm_regmap_init_i2c(client, &w83773_regmap_config); | ||
293 | if (IS_ERR(regmap)) { | ||
294 | dev_err(dev, "failed to allocate register map\n"); | ||
295 | return PTR_ERR(regmap); | ||
296 | } | ||
297 | |||
298 | /* Set the conversion rate to 2 Hz */ | ||
299 | ret = regmap_write(regmap, W83773_CONVERSION_RATE_REG_WRITE, 0x05); | ||
300 | if (ret < 0) { | ||
301 | dev_err(&client->dev, "error writing config rate register\n"); | ||
302 | return ret; | ||
303 | } | ||
304 | |||
305 | i2c_set_clientdata(client, regmap); | ||
306 | |||
307 | hwmon_dev = devm_hwmon_device_register_with_info(dev, | ||
308 | client->name, | ||
309 | regmap, | ||
310 | &w83773_chip_info, | ||
311 | NULL); | ||
312 | return PTR_ERR_OR_ZERO(hwmon_dev); | ||
313 | } | ||
314 | |||
315 | static struct i2c_driver w83773_driver = { | ||
316 | .class = I2C_CLASS_HWMON, | ||
317 | .driver = { | ||
318 | .name = "w83773g", | ||
319 | .of_match_table = of_match_ptr(w83773_of_match), | ||
320 | }, | ||
321 | .probe = w83773_probe, | ||
322 | .id_table = w83773_id, | ||
323 | }; | ||
324 | |||
325 | module_i2c_driver(w83773_driver); | ||
326 | |||
327 | MODULE_AUTHOR("Lei YU <mine260309@gmail.com>"); | ||
328 | MODULE_DESCRIPTION("W83773G temperature sensor driver"); | ||
329 | MODULE_LICENSE("GPL"); | ||