Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

hwmon: Add Nuvoton NCT6694 HWMON support

This driver supports Hardware monitor functionality for NCT6694 MFD
device based on USB interface.

Reviewed-by: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Ming Yu <a0282524688@gmail.com>
Link: https://lore.kernel.org/r/20250912091952.1169369-7-a0282524688@gmail.com
Signed-off-by: Lee Jones <lee@kernel.org>

authored by

Ming Yu and committed by
Lee Jones
197e779d f9d737a7

+961
+1
MAINTAINERS
··· 18086 18086 M: Ming Yu <tmyu0@nuvoton.com> 18087 18087 S: Supported 18088 18088 F: drivers/gpio/gpio-nct6694.c 18089 + F: drivers/hwmon/nct6694-hwmon.c 18089 18090 F: drivers/i2c/busses/i2c-nct6694.c 18090 18091 F: drivers/mfd/nct6694.c 18091 18092 F: drivers/net/can/usb/nct6694_canfd.c
+10
drivers/hwmon/Kconfig
··· 1698 1698 This driver can also be built as a module. If so, the module 1699 1699 will be called nct6683. 1700 1700 1701 + config SENSORS_NCT6694 1702 + tristate "Nuvoton NCT6694 Hardware Monitor support" 1703 + depends on MFD_NCT6694 1704 + help 1705 + Say Y here to support Nuvoton NCT6694 hardware monitoring 1706 + functionality. 1707 + 1708 + This driver can also be built as a module. If so, the module 1709 + will be called nct6694-hwmon. 1710 + 1701 1711 config SENSORS_NCT6775_CORE 1702 1712 tristate 1703 1713 select REGMAP
+1
drivers/hwmon/Makefile
··· 174 174 obj-$(CONFIG_SENSORS_MENF21BMC_HWMON) += menf21bmc_hwmon.o 175 175 obj-$(CONFIG_SENSORS_MR75203) += mr75203.o 176 176 obj-$(CONFIG_SENSORS_NCT6683) += nct6683.o 177 + obj-$(CONFIG_SENSORS_NCT6694) += nct6694-hwmon.o 177 178 obj-$(CONFIG_SENSORS_NCT6775_CORE) += nct6775-core.o 178 179 nct6775-objs := nct6775-platform.o 179 180 obj-$(CONFIG_SENSORS_NCT6775) += nct6775.o
+949
drivers/hwmon/nct6694-hwmon.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* 3 + * Nuvoton NCT6694 HWMON driver based on USB interface. 4 + * 5 + * Copyright (C) 2025 Nuvoton Technology Corp. 6 + */ 7 + 8 + #include <linux/bits.h> 9 + #include <linux/bitfield.h> 10 + #include <linux/hwmon.h> 11 + #include <linux/kernel.h> 12 + #include <linux/mfd/core.h> 13 + #include <linux/mfd/nct6694.h> 14 + #include <linux/module.h> 15 + #include <linux/platform_device.h> 16 + #include <linux/slab.h> 17 + 18 + /* 19 + * USB command module type for NCT6694 report channel 20 + * This defines the module type used for communication with the NCT6694 21 + * report channel over the USB interface. 22 + */ 23 + #define NCT6694_RPT_MOD 0xFF 24 + 25 + /* Report channel */ 26 + /* 27 + * The report channel is used to report the status of the hardware monitor 28 + * devices, such as voltage, temperature, fan speed, and PWM. 29 + */ 30 + #define NCT6694_VIN_IDX(x) (0x00 + (x)) 31 + #define NCT6694_TIN_IDX(x) \ 32 + ({ typeof(x) (_x) = (x); \ 33 + ((_x) < 10) ? (0x10 + ((_x) * 2)) : \ 34 + (0x30 + (((_x) - 10) * 2)); }) 35 + #define NCT6694_FIN_IDX(x) (0x50 + ((x) * 2)) 36 + #define NCT6694_PWM_IDX(x) (0x70 + (x)) 37 + #define NCT6694_VIN_STS(x) (0x68 + (x)) 38 + #define NCT6694_TIN_STS(x) (0x6A + (x)) 39 + #define NCT6694_FIN_STS(x) (0x6E + (x)) 40 + 41 + /* 42 + * USB command module type for NCT6694 HWMON controller. 43 + * This defines the module type used for communication with the NCT6694 44 + * HWMON controller over the USB interface. 45 + */ 46 + #define NCT6694_HWMON_MOD 0x00 47 + 48 + /* Command 00h - Hardware Monitor Control */ 49 + #define NCT6694_HWMON_CONTROL 0x00 50 + #define NCT6694_HWMON_CONTROL_SEL 0x00 51 + 52 + /* Command 02h - Alarm Control */ 53 + #define NCT6694_HWMON_ALARM 0x02 54 + #define NCT6694_HWMON_ALARM_SEL 0x00 55 + 56 + /* 57 + * USB command module type for NCT6694 PWM controller. 58 + * This defines the module type used for communication with the NCT6694 59 + * PWM controller over the USB interface. 60 + */ 61 + #define NCT6694_PWM_MOD 0x01 62 + 63 + /* PWM Command - Manual Control */ 64 + #define NCT6694_PWM_CONTROL 0x01 65 + #define NCT6694_PWM_CONTROL_SEL 0x00 66 + 67 + #define NCT6694_FREQ_FROM_REG(reg) ((reg) * 25000 / 255) 68 + #define NCT6694_FREQ_TO_REG(val) \ 69 + (DIV_ROUND_CLOSEST(clamp_val((val), 100, 25000) * 255, 25000)) 70 + 71 + #define NCT6694_LSB_REG_MASK GENMASK(7, 5) 72 + #define NCT6694_TIN_HYST_MASK GENMASK(7, 5) 73 + 74 + enum nct6694_hwmon_temp_mode { 75 + NCT6694_HWMON_TWOTIME_IRQ = 0, 76 + NCT6694_HWMON_ONETIME_IRQ, 77 + NCT6694_HWMON_REALTIME_IRQ, 78 + NCT6694_HWMON_COMPARE_IRQ, 79 + }; 80 + 81 + struct __packed nct6694_hwmon_control { 82 + u8 vin_en[2]; 83 + u8 tin_en[2]; 84 + u8 fin_en[2]; 85 + u8 pwm_en[2]; 86 + u8 reserved1[40]; 87 + u8 pwm_freq[10]; 88 + u8 reserved2[6]; 89 + }; 90 + 91 + struct __packed nct6694_hwmon_alarm { 92 + u8 smi_ctrl; 93 + u8 reserved1[15]; 94 + struct { 95 + u8 hl; 96 + u8 ll; 97 + } vin_limit[16]; 98 + struct { 99 + u8 hyst; 100 + s8 hl; 101 + } tin_cfg[32]; 102 + __be16 fin_ll[10]; 103 + u8 reserved2[4]; 104 + }; 105 + 106 + struct __packed nct6694_pwm_control { 107 + u8 mal_en[2]; 108 + u8 mal_val[10]; 109 + u8 reserved[12]; 110 + }; 111 + 112 + union __packed nct6694_hwmon_rpt { 113 + u8 vin; 114 + struct { 115 + u8 msb; 116 + u8 lsb; 117 + } tin; 118 + __be16 fin; 119 + u8 pwm; 120 + u8 status; 121 + }; 122 + 123 + union __packed nct6694_hwmon_msg { 124 + struct nct6694_hwmon_alarm hwmon_alarm; 125 + struct nct6694_pwm_control pwm_ctrl; 126 + }; 127 + 128 + struct nct6694_hwmon_data { 129 + struct nct6694 *nct6694; 130 + struct mutex lock; 131 + struct nct6694_hwmon_control hwmon_en; 132 + union nct6694_hwmon_rpt *rpt; 133 + union nct6694_hwmon_msg *msg; 134 + }; 135 + 136 + static inline long in_from_reg(u8 reg) 137 + { 138 + return reg * 16; 139 + } 140 + 141 + static inline u8 in_to_reg(long val) 142 + { 143 + return DIV_ROUND_CLOSEST(val, 16); 144 + } 145 + 146 + static inline long temp_from_reg(s8 reg) 147 + { 148 + return reg * 1000; 149 + } 150 + 151 + static inline s8 temp_to_reg(long val) 152 + { 153 + return DIV_ROUND_CLOSEST(val, 1000); 154 + } 155 + 156 + #define NCT6694_HWMON_IN_CONFIG (HWMON_I_INPUT | HWMON_I_ENABLE | \ 157 + HWMON_I_MAX | HWMON_I_MIN | \ 158 + HWMON_I_ALARM) 159 + #define NCT6694_HWMON_TEMP_CONFIG (HWMON_T_INPUT | HWMON_T_ENABLE | \ 160 + HWMON_T_MAX | HWMON_T_MAX_HYST | \ 161 + HWMON_T_MAX_ALARM) 162 + #define NCT6694_HWMON_FAN_CONFIG (HWMON_F_INPUT | HWMON_F_ENABLE | \ 163 + HWMON_F_MIN | HWMON_F_MIN_ALARM) 164 + #define NCT6694_HWMON_PWM_CONFIG (HWMON_PWM_INPUT | HWMON_PWM_ENABLE | \ 165 + HWMON_PWM_FREQ) 166 + static const struct hwmon_channel_info *nct6694_info[] = { 167 + HWMON_CHANNEL_INFO(in, 168 + NCT6694_HWMON_IN_CONFIG, /* VIN0 */ 169 + NCT6694_HWMON_IN_CONFIG, /* VIN1 */ 170 + NCT6694_HWMON_IN_CONFIG, /* VIN2 */ 171 + NCT6694_HWMON_IN_CONFIG, /* VIN3 */ 172 + NCT6694_HWMON_IN_CONFIG, /* VIN5 */ 173 + NCT6694_HWMON_IN_CONFIG, /* VIN6 */ 174 + NCT6694_HWMON_IN_CONFIG, /* VIN7 */ 175 + NCT6694_HWMON_IN_CONFIG, /* VIN14 */ 176 + NCT6694_HWMON_IN_CONFIG, /* VIN15 */ 177 + NCT6694_HWMON_IN_CONFIG, /* VIN16 */ 178 + NCT6694_HWMON_IN_CONFIG, /* VBAT */ 179 + NCT6694_HWMON_IN_CONFIG, /* VSB */ 180 + NCT6694_HWMON_IN_CONFIG, /* AVSB */ 181 + NCT6694_HWMON_IN_CONFIG, /* VCC */ 182 + NCT6694_HWMON_IN_CONFIG, /* VHIF */ 183 + NCT6694_HWMON_IN_CONFIG), /* VTT */ 184 + 185 + HWMON_CHANNEL_INFO(temp, 186 + NCT6694_HWMON_TEMP_CONFIG, /* THR1 */ 187 + NCT6694_HWMON_TEMP_CONFIG, /* THR2 */ 188 + NCT6694_HWMON_TEMP_CONFIG, /* THR14 */ 189 + NCT6694_HWMON_TEMP_CONFIG, /* THR15 */ 190 + NCT6694_HWMON_TEMP_CONFIG, /* THR16 */ 191 + NCT6694_HWMON_TEMP_CONFIG, /* TDP0 */ 192 + NCT6694_HWMON_TEMP_CONFIG, /* TDP1 */ 193 + NCT6694_HWMON_TEMP_CONFIG, /* TDP2 */ 194 + NCT6694_HWMON_TEMP_CONFIG, /* TDP3 */ 195 + NCT6694_HWMON_TEMP_CONFIG, /* TDP4 */ 196 + NCT6694_HWMON_TEMP_CONFIG, /* DTIN0 */ 197 + NCT6694_HWMON_TEMP_CONFIG, /* DTIN1 */ 198 + NCT6694_HWMON_TEMP_CONFIG, /* DTIN2 */ 199 + NCT6694_HWMON_TEMP_CONFIG, /* DTIN3 */ 200 + NCT6694_HWMON_TEMP_CONFIG, /* DTIN4 */ 201 + NCT6694_HWMON_TEMP_CONFIG, /* DTIN5 */ 202 + NCT6694_HWMON_TEMP_CONFIG, /* DTIN6 */ 203 + NCT6694_HWMON_TEMP_CONFIG, /* DTIN7 */ 204 + NCT6694_HWMON_TEMP_CONFIG, /* DTIN8 */ 205 + NCT6694_HWMON_TEMP_CONFIG, /* DTIN9 */ 206 + NCT6694_HWMON_TEMP_CONFIG, /* DTIN10 */ 207 + NCT6694_HWMON_TEMP_CONFIG, /* DTIN11 */ 208 + NCT6694_HWMON_TEMP_CONFIG, /* DTIN12 */ 209 + NCT6694_HWMON_TEMP_CONFIG, /* DTIN13 */ 210 + NCT6694_HWMON_TEMP_CONFIG, /* DTIN14 */ 211 + NCT6694_HWMON_TEMP_CONFIG), /* DTIN15 */ 212 + 213 + HWMON_CHANNEL_INFO(fan, 214 + NCT6694_HWMON_FAN_CONFIG, /* FIN0 */ 215 + NCT6694_HWMON_FAN_CONFIG, /* FIN1 */ 216 + NCT6694_HWMON_FAN_CONFIG, /* FIN2 */ 217 + NCT6694_HWMON_FAN_CONFIG, /* FIN3 */ 218 + NCT6694_HWMON_FAN_CONFIG, /* FIN4 */ 219 + NCT6694_HWMON_FAN_CONFIG, /* FIN5 */ 220 + NCT6694_HWMON_FAN_CONFIG, /* FIN6 */ 221 + NCT6694_HWMON_FAN_CONFIG, /* FIN7 */ 222 + NCT6694_HWMON_FAN_CONFIG, /* FIN8 */ 223 + NCT6694_HWMON_FAN_CONFIG), /* FIN9 */ 224 + 225 + HWMON_CHANNEL_INFO(pwm, 226 + NCT6694_HWMON_PWM_CONFIG, /* PWM0 */ 227 + NCT6694_HWMON_PWM_CONFIG, /* PWM1 */ 228 + NCT6694_HWMON_PWM_CONFIG, /* PWM2 */ 229 + NCT6694_HWMON_PWM_CONFIG, /* PWM3 */ 230 + NCT6694_HWMON_PWM_CONFIG, /* PWM4 */ 231 + NCT6694_HWMON_PWM_CONFIG, /* PWM5 */ 232 + NCT6694_HWMON_PWM_CONFIG, /* PWM6 */ 233 + NCT6694_HWMON_PWM_CONFIG, /* PWM7 */ 234 + NCT6694_HWMON_PWM_CONFIG, /* PWM8 */ 235 + NCT6694_HWMON_PWM_CONFIG), /* PWM9 */ 236 + NULL 237 + }; 238 + 239 + static int nct6694_in_read(struct device *dev, u32 attr, int channel, 240 + long *val) 241 + { 242 + struct nct6694_hwmon_data *data = dev_get_drvdata(dev); 243 + struct nct6694_cmd_header cmd_hd; 244 + unsigned char vin_en; 245 + int ret; 246 + 247 + guard(mutex)(&data->lock); 248 + 249 + switch (attr) { 250 + case hwmon_in_enable: 251 + vin_en = data->hwmon_en.vin_en[(channel / 8)]; 252 + *val = !!(vin_en & BIT(channel % 8)); 253 + 254 + return 0; 255 + case hwmon_in_input: 256 + cmd_hd = (struct nct6694_cmd_header) { 257 + .mod = NCT6694_RPT_MOD, 258 + .offset = cpu_to_le16(NCT6694_VIN_IDX(channel)), 259 + .len = cpu_to_le16(sizeof(data->rpt->vin)) 260 + }; 261 + ret = nct6694_read_msg(data->nct6694, &cmd_hd, 262 + &data->rpt->vin); 263 + if (ret) 264 + return ret; 265 + 266 + *val = in_from_reg(data->rpt->vin); 267 + 268 + return 0; 269 + case hwmon_in_max: 270 + cmd_hd = (struct nct6694_cmd_header) { 271 + .mod = NCT6694_HWMON_MOD, 272 + .cmd = NCT6694_HWMON_ALARM, 273 + .sel = NCT6694_HWMON_ALARM_SEL, 274 + .len = cpu_to_le16(sizeof(data->msg->hwmon_alarm)) 275 + }; 276 + ret = nct6694_read_msg(data->nct6694, &cmd_hd, 277 + &data->msg->hwmon_alarm); 278 + if (ret) 279 + return ret; 280 + 281 + *val = in_from_reg(data->msg->hwmon_alarm.vin_limit[channel].hl); 282 + 283 + return 0; 284 + case hwmon_in_min: 285 + cmd_hd = (struct nct6694_cmd_header) { 286 + .mod = NCT6694_HWMON_MOD, 287 + .cmd = NCT6694_HWMON_ALARM, 288 + .sel = NCT6694_HWMON_ALARM_SEL, 289 + .len = cpu_to_le16(sizeof(data->msg->hwmon_alarm)) 290 + }; 291 + ret = nct6694_read_msg(data->nct6694, &cmd_hd, 292 + &data->msg->hwmon_alarm); 293 + if (ret) 294 + return ret; 295 + 296 + *val = in_from_reg(data->msg->hwmon_alarm.vin_limit[channel].ll); 297 + 298 + return 0; 299 + case hwmon_in_alarm: 300 + cmd_hd = (struct nct6694_cmd_header) { 301 + .mod = NCT6694_RPT_MOD, 302 + .offset = cpu_to_le16(NCT6694_VIN_STS(channel / 8)), 303 + .len = cpu_to_le16(sizeof(data->rpt->status)) 304 + }; 305 + ret = nct6694_read_msg(data->nct6694, &cmd_hd, 306 + &data->rpt->status); 307 + if (ret) 308 + return ret; 309 + 310 + *val = !!(data->rpt->status & BIT(channel % 8)); 311 + 312 + return 0; 313 + default: 314 + return -EOPNOTSUPP; 315 + } 316 + } 317 + 318 + static int nct6694_temp_read(struct device *dev, u32 attr, int channel, 319 + long *val) 320 + { 321 + struct nct6694_hwmon_data *data = dev_get_drvdata(dev); 322 + struct nct6694_cmd_header cmd_hd; 323 + unsigned char temp_en, temp_hyst; 324 + signed char temp_max; 325 + int ret, temp_raw; 326 + 327 + guard(mutex)(&data->lock); 328 + 329 + switch (attr) { 330 + case hwmon_temp_enable: 331 + temp_en = data->hwmon_en.tin_en[channel / 8]; 332 + *val = !!(temp_en & BIT(channel % 8)); 333 + 334 + return 0; 335 + case hwmon_temp_input: 336 + cmd_hd = (struct nct6694_cmd_header) { 337 + .mod = NCT6694_RPT_MOD, 338 + .offset = cpu_to_le16(NCT6694_TIN_IDX(channel)), 339 + .len = cpu_to_le16(sizeof(data->rpt->tin)) 340 + }; 341 + ret = nct6694_read_msg(data->nct6694, &cmd_hd, 342 + &data->rpt->tin); 343 + if (ret) 344 + return ret; 345 + 346 + temp_raw = data->rpt->tin.msb << 3; 347 + temp_raw |= FIELD_GET(NCT6694_LSB_REG_MASK, data->rpt->tin.lsb); 348 + 349 + /* Real temperature(milli degrees Celsius) = temp_raw * 1000 * 0.125 */ 350 + *val = sign_extend32(temp_raw, 10) * 125; 351 + 352 + return 0; 353 + case hwmon_temp_max: 354 + cmd_hd = (struct nct6694_cmd_header) { 355 + .mod = NCT6694_HWMON_MOD, 356 + .cmd = NCT6694_HWMON_ALARM, 357 + .sel = NCT6694_HWMON_ALARM_SEL, 358 + .len = cpu_to_le16(sizeof(data->msg->hwmon_alarm)) 359 + }; 360 + ret = nct6694_read_msg(data->nct6694, &cmd_hd, 361 + &data->msg->hwmon_alarm); 362 + if (ret) 363 + return ret; 364 + 365 + *val = temp_from_reg(data->msg->hwmon_alarm.tin_cfg[channel].hl); 366 + 367 + return 0; 368 + case hwmon_temp_max_hyst: 369 + cmd_hd = (struct nct6694_cmd_header) { 370 + .mod = NCT6694_HWMON_MOD, 371 + .cmd = NCT6694_HWMON_ALARM, 372 + .sel = NCT6694_HWMON_ALARM_SEL, 373 + .len = cpu_to_le16(sizeof(data->msg->hwmon_alarm)) 374 + }; 375 + ret = nct6694_read_msg(data->nct6694, &cmd_hd, 376 + &data->msg->hwmon_alarm); 377 + if (ret) 378 + return ret; 379 + 380 + temp_max = data->msg->hwmon_alarm.tin_cfg[channel].hl; 381 + temp_hyst = FIELD_GET(NCT6694_TIN_HYST_MASK, 382 + data->msg->hwmon_alarm.tin_cfg[channel].hyst); 383 + *val = temp_from_reg(temp_max - temp_hyst); 384 + 385 + return 0; 386 + case hwmon_temp_max_alarm: 387 + cmd_hd = (struct nct6694_cmd_header) { 388 + .mod = NCT6694_RPT_MOD, 389 + .offset = cpu_to_le16(NCT6694_TIN_STS(channel / 8)), 390 + .len = cpu_to_le16(sizeof(data->rpt->status)) 391 + }; 392 + ret = nct6694_read_msg(data->nct6694, &cmd_hd, 393 + &data->rpt->status); 394 + if (ret) 395 + return ret; 396 + 397 + *val = !!(data->rpt->status & BIT(channel % 8)); 398 + 399 + return 0; 400 + default: 401 + return -EOPNOTSUPP; 402 + } 403 + } 404 + 405 + static int nct6694_fan_read(struct device *dev, u32 attr, int channel, 406 + long *val) 407 + { 408 + struct nct6694_hwmon_data *data = dev_get_drvdata(dev); 409 + struct nct6694_cmd_header cmd_hd; 410 + unsigned char fanin_en; 411 + int ret; 412 + 413 + guard(mutex)(&data->lock); 414 + 415 + switch (attr) { 416 + case hwmon_fan_enable: 417 + fanin_en = data->hwmon_en.fin_en[channel / 8]; 418 + *val = !!(fanin_en & BIT(channel % 8)); 419 + 420 + return 0; 421 + case hwmon_fan_input: 422 + cmd_hd = (struct nct6694_cmd_header) { 423 + .mod = NCT6694_RPT_MOD, 424 + .offset = cpu_to_le16(NCT6694_FIN_IDX(channel)), 425 + .len = cpu_to_le16(sizeof(data->rpt->fin)) 426 + }; 427 + ret = nct6694_read_msg(data->nct6694, &cmd_hd, 428 + &data->rpt->fin); 429 + if (ret) 430 + return ret; 431 + 432 + *val = be16_to_cpu(data->rpt->fin); 433 + 434 + return 0; 435 + case hwmon_fan_min: 436 + cmd_hd = (struct nct6694_cmd_header) { 437 + .mod = NCT6694_HWMON_MOD, 438 + .cmd = NCT6694_HWMON_ALARM, 439 + .sel = NCT6694_HWMON_ALARM_SEL, 440 + .len = cpu_to_le16(sizeof(data->msg->hwmon_alarm)) 441 + }; 442 + ret = nct6694_read_msg(data->nct6694, &cmd_hd, 443 + &data->msg->hwmon_alarm); 444 + if (ret) 445 + return ret; 446 + 447 + *val = be16_to_cpu(data->msg->hwmon_alarm.fin_ll[channel]); 448 + 449 + return 0; 450 + case hwmon_fan_min_alarm: 451 + cmd_hd = (struct nct6694_cmd_header) { 452 + .mod = NCT6694_RPT_MOD, 453 + .offset = cpu_to_le16(NCT6694_FIN_STS(channel / 8)), 454 + .len = cpu_to_le16(sizeof(data->rpt->status)) 455 + }; 456 + ret = nct6694_read_msg(data->nct6694, &cmd_hd, 457 + &data->rpt->status); 458 + if (ret) 459 + return ret; 460 + 461 + *val = !!(data->rpt->status & BIT(channel % 8)); 462 + 463 + return 0; 464 + default: 465 + return -EOPNOTSUPP; 466 + } 467 + } 468 + 469 + static int nct6694_pwm_read(struct device *dev, u32 attr, int channel, 470 + long *val) 471 + { 472 + struct nct6694_hwmon_data *data = dev_get_drvdata(dev); 473 + struct nct6694_cmd_header cmd_hd; 474 + unsigned char pwm_en; 475 + int ret; 476 + 477 + guard(mutex)(&data->lock); 478 + 479 + switch (attr) { 480 + case hwmon_pwm_enable: 481 + pwm_en = data->hwmon_en.pwm_en[channel / 8]; 482 + *val = !!(pwm_en & BIT(channel % 8)); 483 + 484 + return 0; 485 + case hwmon_pwm_input: 486 + cmd_hd = (struct nct6694_cmd_header) { 487 + .mod = NCT6694_RPT_MOD, 488 + .offset = cpu_to_le16(NCT6694_PWM_IDX(channel)), 489 + .len = cpu_to_le16(sizeof(data->rpt->pwm)) 490 + }; 491 + ret = nct6694_read_msg(data->nct6694, &cmd_hd, 492 + &data->rpt->pwm); 493 + if (ret) 494 + return ret; 495 + 496 + *val = data->rpt->pwm; 497 + 498 + return 0; 499 + case hwmon_pwm_freq: 500 + *val = NCT6694_FREQ_FROM_REG(data->hwmon_en.pwm_freq[channel]); 501 + 502 + return 0; 503 + default: 504 + return -EOPNOTSUPP; 505 + } 506 + } 507 + 508 + static int nct6694_in_write(struct device *dev, u32 attr, int channel, 509 + long val) 510 + { 511 + struct nct6694_hwmon_data *data = dev_get_drvdata(dev); 512 + struct nct6694_cmd_header cmd_hd; 513 + int ret; 514 + 515 + guard(mutex)(&data->lock); 516 + 517 + switch (attr) { 518 + case hwmon_in_enable: 519 + if (val == 0) 520 + data->hwmon_en.vin_en[channel / 8] &= ~BIT(channel % 8); 521 + else if (val == 1) 522 + data->hwmon_en.vin_en[channel / 8] |= BIT(channel % 8); 523 + else 524 + return -EINVAL; 525 + 526 + cmd_hd = (struct nct6694_cmd_header) { 527 + .mod = NCT6694_HWMON_MOD, 528 + .cmd = NCT6694_HWMON_CONTROL, 529 + .sel = NCT6694_HWMON_CONTROL_SEL, 530 + .len = cpu_to_le16(sizeof(data->hwmon_en)) 531 + }; 532 + 533 + return nct6694_write_msg(data->nct6694, &cmd_hd, 534 + &data->hwmon_en); 535 + case hwmon_in_max: 536 + cmd_hd = (struct nct6694_cmd_header) { 537 + .mod = NCT6694_HWMON_MOD, 538 + .cmd = NCT6694_HWMON_ALARM, 539 + .sel = NCT6694_HWMON_ALARM_SEL, 540 + .len = cpu_to_le16(sizeof(data->msg->hwmon_alarm)) 541 + }; 542 + ret = nct6694_read_msg(data->nct6694, &cmd_hd, 543 + &data->msg->hwmon_alarm); 544 + if (ret) 545 + return ret; 546 + 547 + val = clamp_val(val, 0, 2032); 548 + data->msg->hwmon_alarm.vin_limit[channel].hl = in_to_reg(val); 549 + 550 + return nct6694_write_msg(data->nct6694, &cmd_hd, 551 + &data->msg->hwmon_alarm); 552 + case hwmon_in_min: 553 + cmd_hd = (struct nct6694_cmd_header) { 554 + .mod = NCT6694_HWMON_MOD, 555 + .cmd = NCT6694_HWMON_ALARM, 556 + .sel = NCT6694_HWMON_ALARM_SEL, 557 + .len = cpu_to_le16(sizeof(data->msg->hwmon_alarm)) 558 + }; 559 + ret = nct6694_read_msg(data->nct6694, &cmd_hd, 560 + &data->msg->hwmon_alarm); 561 + if (ret) 562 + return ret; 563 + 564 + val = clamp_val(val, 0, 2032); 565 + data->msg->hwmon_alarm.vin_limit[channel].ll = in_to_reg(val); 566 + 567 + return nct6694_write_msg(data->nct6694, &cmd_hd, 568 + &data->msg->hwmon_alarm); 569 + default: 570 + return -EOPNOTSUPP; 571 + } 572 + } 573 + 574 + static int nct6694_temp_write(struct device *dev, u32 attr, int channel, 575 + long val) 576 + { 577 + struct nct6694_hwmon_data *data = dev_get_drvdata(dev); 578 + struct nct6694_cmd_header cmd_hd; 579 + unsigned char temp_hyst; 580 + signed char temp_max; 581 + int ret; 582 + 583 + guard(mutex)(&data->lock); 584 + 585 + switch (attr) { 586 + case hwmon_temp_enable: 587 + if (val == 0) 588 + data->hwmon_en.tin_en[channel / 8] &= ~BIT(channel % 8); 589 + else if (val == 1) 590 + data->hwmon_en.tin_en[channel / 8] |= BIT(channel % 8); 591 + else 592 + return -EINVAL; 593 + 594 + cmd_hd = (struct nct6694_cmd_header) { 595 + .mod = NCT6694_HWMON_MOD, 596 + .cmd = NCT6694_HWMON_CONTROL, 597 + .sel = NCT6694_HWMON_CONTROL_SEL, 598 + .len = cpu_to_le16(sizeof(data->hwmon_en)) 599 + }; 600 + 601 + return nct6694_write_msg(data->nct6694, &cmd_hd, 602 + &data->hwmon_en); 603 + case hwmon_temp_max: 604 + cmd_hd = (struct nct6694_cmd_header) { 605 + .mod = NCT6694_HWMON_MOD, 606 + .cmd = NCT6694_HWMON_ALARM, 607 + .sel = NCT6694_HWMON_ALARM_SEL, 608 + .len = cpu_to_le16(sizeof(data->msg->hwmon_alarm)) 609 + }; 610 + ret = nct6694_read_msg(data->nct6694, &cmd_hd, 611 + &data->msg->hwmon_alarm); 612 + if (ret) 613 + return ret; 614 + 615 + val = clamp_val(val, -127000, 127000); 616 + data->msg->hwmon_alarm.tin_cfg[channel].hl = temp_to_reg(val); 617 + 618 + return nct6694_write_msg(data->nct6694, &cmd_hd, 619 + &data->msg->hwmon_alarm); 620 + case hwmon_temp_max_hyst: 621 + cmd_hd = (struct nct6694_cmd_header) { 622 + .mod = NCT6694_HWMON_MOD, 623 + .cmd = NCT6694_HWMON_ALARM, 624 + .sel = NCT6694_HWMON_ALARM_SEL, 625 + .len = cpu_to_le16(sizeof(data->msg->hwmon_alarm)) 626 + }; 627 + ret = nct6694_read_msg(data->nct6694, &cmd_hd, 628 + &data->msg->hwmon_alarm); 629 + 630 + val = clamp_val(val, -127000, 127000); 631 + temp_max = data->msg->hwmon_alarm.tin_cfg[channel].hl; 632 + temp_hyst = temp_max - temp_to_reg(val); 633 + temp_hyst = clamp_val(temp_hyst, 0, 7); 634 + data->msg->hwmon_alarm.tin_cfg[channel].hyst = 635 + (data->msg->hwmon_alarm.tin_cfg[channel].hyst & ~NCT6694_TIN_HYST_MASK) | 636 + FIELD_PREP(NCT6694_TIN_HYST_MASK, temp_hyst); 637 + 638 + return nct6694_write_msg(data->nct6694, &cmd_hd, 639 + &data->msg->hwmon_alarm); 640 + default: 641 + return -EOPNOTSUPP; 642 + } 643 + } 644 + 645 + static int nct6694_fan_write(struct device *dev, u32 attr, int channel, 646 + long val) 647 + { 648 + struct nct6694_hwmon_data *data = dev_get_drvdata(dev); 649 + struct nct6694_cmd_header cmd_hd; 650 + int ret; 651 + 652 + guard(mutex)(&data->lock); 653 + 654 + switch (attr) { 655 + case hwmon_fan_enable: 656 + if (val == 0) 657 + data->hwmon_en.fin_en[channel / 8] &= ~BIT(channel % 8); 658 + else if (val == 1) 659 + data->hwmon_en.fin_en[channel / 8] |= BIT(channel % 8); 660 + else 661 + return -EINVAL; 662 + 663 + cmd_hd = (struct nct6694_cmd_header) { 664 + .mod = NCT6694_HWMON_MOD, 665 + .cmd = NCT6694_HWMON_CONTROL, 666 + .sel = NCT6694_HWMON_CONTROL_SEL, 667 + .len = cpu_to_le16(sizeof(data->hwmon_en)) 668 + }; 669 + 670 + return nct6694_write_msg(data->nct6694, &cmd_hd, 671 + &data->hwmon_en); 672 + case hwmon_fan_min: 673 + cmd_hd = (struct nct6694_cmd_header) { 674 + .mod = NCT6694_HWMON_MOD, 675 + .cmd = NCT6694_HWMON_ALARM, 676 + .sel = NCT6694_HWMON_ALARM_SEL, 677 + .len = cpu_to_le16(sizeof(data->msg->hwmon_alarm)) 678 + }; 679 + ret = nct6694_read_msg(data->nct6694, &cmd_hd, 680 + &data->msg->hwmon_alarm); 681 + if (ret) 682 + return ret; 683 + 684 + val = clamp_val(val, 1, 65535); 685 + data->msg->hwmon_alarm.fin_ll[channel] = cpu_to_be16(val); 686 + 687 + return nct6694_write_msg(data->nct6694, &cmd_hd, 688 + &data->msg->hwmon_alarm); 689 + default: 690 + return -EOPNOTSUPP; 691 + } 692 + } 693 + 694 + static int nct6694_pwm_write(struct device *dev, u32 attr, int channel, 695 + long val) 696 + { 697 + struct nct6694_hwmon_data *data = dev_get_drvdata(dev); 698 + struct nct6694_cmd_header cmd_hd; 699 + int ret; 700 + 701 + guard(mutex)(&data->lock); 702 + 703 + switch (attr) { 704 + case hwmon_pwm_enable: 705 + if (val == 0) 706 + data->hwmon_en.pwm_en[channel / 8] &= ~BIT(channel % 8); 707 + else if (val == 1) 708 + data->hwmon_en.pwm_en[channel / 8] |= BIT(channel % 8); 709 + else 710 + return -EINVAL; 711 + 712 + cmd_hd = (struct nct6694_cmd_header) { 713 + .mod = NCT6694_HWMON_MOD, 714 + .cmd = NCT6694_HWMON_CONTROL, 715 + .sel = NCT6694_HWMON_CONTROL_SEL, 716 + .len = cpu_to_le16(sizeof(data->hwmon_en)) 717 + }; 718 + 719 + return nct6694_write_msg(data->nct6694, &cmd_hd, 720 + &data->hwmon_en); 721 + case hwmon_pwm_input: 722 + if (val < 0 || val > 255) 723 + return -EINVAL; 724 + 725 + cmd_hd = (struct nct6694_cmd_header) { 726 + .mod = NCT6694_PWM_MOD, 727 + .cmd = NCT6694_PWM_CONTROL, 728 + .sel = NCT6694_PWM_CONTROL_SEL, 729 + .len = cpu_to_le16(sizeof(data->msg->pwm_ctrl)) 730 + }; 731 + 732 + ret = nct6694_read_msg(data->nct6694, &cmd_hd, 733 + &data->msg->pwm_ctrl); 734 + if (ret) 735 + return ret; 736 + 737 + data->msg->pwm_ctrl.mal_val[channel] = val; 738 + 739 + return nct6694_write_msg(data->nct6694, &cmd_hd, 740 + &data->msg->pwm_ctrl); 741 + case hwmon_pwm_freq: 742 + cmd_hd = (struct nct6694_cmd_header) { 743 + .mod = NCT6694_HWMON_MOD, 744 + .cmd = NCT6694_HWMON_CONTROL, 745 + .sel = NCT6694_HWMON_CONTROL_SEL, 746 + .len = cpu_to_le16(sizeof(data->hwmon_en)) 747 + }; 748 + 749 + data->hwmon_en.pwm_freq[channel] = NCT6694_FREQ_TO_REG(val); 750 + 751 + return nct6694_write_msg(data->nct6694, &cmd_hd, 752 + &data->hwmon_en); 753 + default: 754 + return -EOPNOTSUPP; 755 + } 756 + } 757 + 758 + static int nct6694_read(struct device *dev, enum hwmon_sensor_types type, 759 + u32 attr, int channel, long *val) 760 + { 761 + switch (type) { 762 + case hwmon_in: 763 + /* in mV */ 764 + return nct6694_in_read(dev, attr, channel, val); 765 + case hwmon_temp: 766 + /* in mC */ 767 + return nct6694_temp_read(dev, attr, channel, val); 768 + case hwmon_fan: 769 + /* in RPM */ 770 + return nct6694_fan_read(dev, attr, channel, val); 771 + case hwmon_pwm: 772 + /* in value 0~255 */ 773 + return nct6694_pwm_read(dev, attr, channel, val); 774 + default: 775 + return -EOPNOTSUPP; 776 + } 777 + } 778 + 779 + static int nct6694_write(struct device *dev, enum hwmon_sensor_types type, 780 + u32 attr, int channel, long val) 781 + { 782 + switch (type) { 783 + case hwmon_in: 784 + return nct6694_in_write(dev, attr, channel, val); 785 + case hwmon_temp: 786 + return nct6694_temp_write(dev, attr, channel, val); 787 + case hwmon_fan: 788 + return nct6694_fan_write(dev, attr, channel, val); 789 + case hwmon_pwm: 790 + return nct6694_pwm_write(dev, attr, channel, val); 791 + default: 792 + return -EOPNOTSUPP; 793 + } 794 + } 795 + 796 + static umode_t nct6694_is_visible(const void *data, 797 + enum hwmon_sensor_types type, 798 + u32 attr, int channel) 799 + { 800 + switch (type) { 801 + case hwmon_in: 802 + switch (attr) { 803 + case hwmon_in_enable: 804 + case hwmon_in_max: 805 + case hwmon_in_min: 806 + return 0644; 807 + case hwmon_in_alarm: 808 + case hwmon_in_input: 809 + return 0444; 810 + default: 811 + return 0; 812 + } 813 + case hwmon_temp: 814 + switch (attr) { 815 + case hwmon_temp_enable: 816 + case hwmon_temp_max: 817 + case hwmon_temp_max_hyst: 818 + return 0644; 819 + case hwmon_temp_input: 820 + case hwmon_temp_max_alarm: 821 + return 0444; 822 + default: 823 + return 0; 824 + } 825 + case hwmon_fan: 826 + switch (attr) { 827 + case hwmon_fan_enable: 828 + case hwmon_fan_min: 829 + return 0644; 830 + case hwmon_fan_input: 831 + case hwmon_fan_min_alarm: 832 + return 0444; 833 + default: 834 + return 0; 835 + } 836 + case hwmon_pwm: 837 + switch (attr) { 838 + case hwmon_pwm_enable: 839 + case hwmon_pwm_freq: 840 + case hwmon_pwm_input: 841 + return 0644; 842 + default: 843 + return 0; 844 + } 845 + default: 846 + return 0; 847 + } 848 + } 849 + 850 + static const struct hwmon_ops nct6694_hwmon_ops = { 851 + .is_visible = nct6694_is_visible, 852 + .read = nct6694_read, 853 + .write = nct6694_write, 854 + }; 855 + 856 + static const struct hwmon_chip_info nct6694_chip_info = { 857 + .ops = &nct6694_hwmon_ops, 858 + .info = nct6694_info, 859 + }; 860 + 861 + static int nct6694_hwmon_init(struct nct6694_hwmon_data *data) 862 + { 863 + struct nct6694_cmd_header cmd_hd = { 864 + .mod = NCT6694_HWMON_MOD, 865 + .cmd = NCT6694_HWMON_CONTROL, 866 + .sel = NCT6694_HWMON_CONTROL_SEL, 867 + .len = cpu_to_le16(sizeof(data->hwmon_en)) 868 + }; 869 + int ret; 870 + 871 + /* 872 + * Record each Hardware Monitor Channel enable status 873 + * and PWM frequency register 874 + */ 875 + ret = nct6694_read_msg(data->nct6694, &cmd_hd, 876 + &data->hwmon_en); 877 + if (ret) 878 + return ret; 879 + 880 + cmd_hd = (struct nct6694_cmd_header) { 881 + .mod = NCT6694_HWMON_MOD, 882 + .cmd = NCT6694_HWMON_ALARM, 883 + .sel = NCT6694_HWMON_ALARM_SEL, 884 + .len = cpu_to_le16(sizeof(data->msg->hwmon_alarm)) 885 + }; 886 + 887 + /* Select hwmon device alarm mode */ 888 + ret = nct6694_read_msg(data->nct6694, &cmd_hd, 889 + &data->msg->hwmon_alarm); 890 + if (ret) 891 + return ret; 892 + 893 + data->msg->hwmon_alarm.smi_ctrl = NCT6694_HWMON_REALTIME_IRQ; 894 + 895 + return nct6694_write_msg(data->nct6694, &cmd_hd, 896 + &data->msg->hwmon_alarm); 897 + } 898 + 899 + static int nct6694_hwmon_probe(struct platform_device *pdev) 900 + { 901 + struct nct6694_hwmon_data *data; 902 + struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent); 903 + struct device *hwmon_dev; 904 + int ret; 905 + 906 + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); 907 + if (!data) 908 + return -ENOMEM; 909 + 910 + data->rpt = devm_kzalloc(&pdev->dev, sizeof(union nct6694_hwmon_rpt), 911 + GFP_KERNEL); 912 + if (!data->rpt) 913 + return -ENOMEM; 914 + 915 + data->msg = devm_kzalloc(&pdev->dev, sizeof(union nct6694_hwmon_msg), 916 + GFP_KERNEL); 917 + if (!data->msg) 918 + return -ENOMEM; 919 + 920 + data->nct6694 = nct6694; 921 + ret = devm_mutex_init(&pdev->dev, &data->lock); 922 + if (ret) 923 + return ret; 924 + 925 + ret = nct6694_hwmon_init(data); 926 + if (ret) 927 + return ret; 928 + 929 + /* Register hwmon device to HWMON framework */ 930 + hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, 931 + "nct6694", data, 932 + &nct6694_chip_info, 933 + NULL); 934 + return PTR_ERR_OR_ZERO(hwmon_dev); 935 + } 936 + 937 + static struct platform_driver nct6694_hwmon_driver = { 938 + .driver = { 939 + .name = "nct6694-hwmon", 940 + }, 941 + .probe = nct6694_hwmon_probe, 942 + }; 943 + 944 + module_platform_driver(nct6694_hwmon_driver); 945 + 946 + MODULE_DESCRIPTION("USB-HWMON driver for NCT6694"); 947 + MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>"); 948 + MODULE_LICENSE("GPL"); 949 + MODULE_ALIAS("platform:nct6694-hwmon");