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

Configure Feed

Select the types of activity you want to include in your feed.

hwmon: add driver for the hwmon parts of qnap-mcu devices

The MCU can be found on network-attached-storage devices made by QNAP
and provides access to fan control including reading back its RPM as
well as reading the temperature of the NAS case.

Signed-off-by: Heiko Stuebner <heiko@sntech.de>
Acked-by: Guenter Roeck <linux@roeck-us.net>
Link: https://lore.kernel.org/r/20241107114712.538976-8-heiko@sntech.de
Signed-off-by: Lee Jones <lee@kernel.org>

authored by

Heiko Stuebner and committed by
Lee Jones
9855caf5 bb7e3611

+406
+1
Documentation/hwmon/index.rst
··· 201 201 pxe1610 202 202 pwm-fan 203 203 q54sj108a2 204 + qnap-mcu-hwmon 204 205 raspberrypi-hwmon 205 206 sbrmi 206 207 sbtsi_temp
+27
Documentation/hwmon/qnap-mcu-hwmon.rst
··· 1 + .. SPDX-License-Identifier: GPL-2.0-or-later 2 + 3 + Kernel driver qnap-mcu-hwmon 4 + ============================ 5 + 6 + This driver enables the use of the hardware monitoring and fan control 7 + of the MCU used on some QNAP network attached storage devices. 8 + 9 + Author: Heiko Stuebner <heiko@sntech.de> 10 + 11 + Description 12 + ----------- 13 + 14 + The driver implements a simple interface for driving the fan controlled by 15 + setting its PWM output value and exposes the fan rpm and case-temperature 16 + to user space through hwmon's sysfs interface. 17 + 18 + The fan rotation speed returned via the optional 'fan1_input' is calculated 19 + inside the MCU device. 20 + 21 + The driver provides the following sensor accesses in sysfs: 22 + 23 + =============== ======= ======================================================= 24 + fan1_input ro fan tachometer speed in RPM 25 + pwm1 rw relative speed (0-255), 255=max. speed. 26 + temp1_input ro Measured temperature in millicelsius 27 + =============== ======= =======================================================
+1
MAINTAINERS
··· 19108 19108 QNAP MCU DRIVER 19109 19109 M: Heiko Stuebner <heiko@sntech.de> 19110 19110 S: Maintained 19111 + F: drivers/hwmon/qnap-mcu-hwmon.c 19111 19112 F: drivers/input/misc/qnap-mcu-input.c 19112 19113 F: drivers/leds/leds-qnap-mcu.c 19113 19114 F: drivers/mfd/qnap-mcu.c
+12
drivers/hwmon/Kconfig
··· 1822 1822 This driver can also be built as a module. If so, the module 1823 1823 will be called pwm-fan. 1824 1824 1825 + config SENSORS_QNAP_MCU_HWMON 1826 + tristate "QNAP MCU hardware monitoring" 1827 + depends on MFD_QNAP_MCU 1828 + depends on THERMAL || THERMAL=n 1829 + help 1830 + Say yes here to enable support for fan and temperature sensor 1831 + connected to a QNAP MCU, as found in a number of QNAP network 1832 + attached storage devices. 1833 + 1834 + This driver can also be built as a module. If so, the module 1835 + will be called qnap-mcu-hwmon. 1836 + 1825 1837 config SENSORS_RASPBERRYPI_HWMON 1826 1838 tristate "Raspberry Pi voltage monitor" 1827 1839 depends on RASPBERRYPI_FIRMWARE || (COMPILE_TEST && !RASPBERRYPI_FIRMWARE)
+1
drivers/hwmon/Makefile
··· 189 189 obj-$(CONFIG_SENSORS_POWR1220) += powr1220.o 190 190 obj-$(CONFIG_SENSORS_PT5161L) += pt5161l.o 191 191 obj-$(CONFIG_SENSORS_PWM_FAN) += pwm-fan.o 192 + obj-$(CONFIG_SENSORS_QNAP_MCU_HWMON) += qnap-mcu-hwmon.o 192 193 obj-$(CONFIG_SENSORS_RASPBERRYPI_HWMON) += raspberrypi-hwmon.o 193 194 obj-$(CONFIG_SENSORS_SBTSI) += sbtsi_temp.o 194 195 obj-$(CONFIG_SENSORS_SBRMI) += sbrmi.o
+364
drivers/hwmon/qnap-mcu-hwmon.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + 3 + /* 4 + * Driver for hwmon elements found on QNAP-MCU devices 5 + * 6 + * Copyright (C) 2024 Heiko Stuebner <heiko@sntech.de> 7 + */ 8 + 9 + #include <linux/fwnode.h> 10 + #include <linux/hwmon.h> 11 + #include <linux/mfd/qnap-mcu.h> 12 + #include <linux/module.h> 13 + #include <linux/platform_device.h> 14 + #include <linux/property.h> 15 + #include <linux/thermal.h> 16 + 17 + struct qnap_mcu_hwmon { 18 + struct qnap_mcu *mcu; 19 + struct device *dev; 20 + 21 + unsigned int pwm_min; 22 + unsigned int pwm_max; 23 + 24 + struct fwnode_handle *fan_node; 25 + unsigned int fan_state; 26 + unsigned int fan_max_state; 27 + unsigned int *fan_cooling_levels; 28 + 29 + struct thermal_cooling_device *cdev; 30 + struct hwmon_chip_info info; 31 + }; 32 + 33 + static int qnap_mcu_hwmon_get_rpm(struct qnap_mcu_hwmon *hwm) 34 + { 35 + static const u8 cmd[] = { '@', 'F', 'A' }; 36 + u8 reply[6]; 37 + int ret; 38 + 39 + /* poll the fan rpm */ 40 + ret = qnap_mcu_exec(hwm->mcu, cmd, sizeof(cmd), reply, sizeof(reply)); 41 + if (ret) 42 + return ret; 43 + 44 + /* First 2 bytes must mirror the sent command */ 45 + if (memcmp(cmd, reply, 2)) 46 + return -EIO; 47 + 48 + return reply[4] * 30; 49 + } 50 + 51 + static int qnap_mcu_hwmon_get_pwm(struct qnap_mcu_hwmon *hwm) 52 + { 53 + static const u8 cmd[] = { '@', 'F', 'Z', '0' }; /* 0 = fan-id? */ 54 + u8 reply[4]; 55 + int ret; 56 + 57 + /* poll the fan pwm */ 58 + ret = qnap_mcu_exec(hwm->mcu, cmd, sizeof(cmd), reply, sizeof(reply)); 59 + if (ret) 60 + return ret; 61 + 62 + /* First 3 bytes must mirror the sent command */ 63 + if (memcmp(cmd, reply, 3)) 64 + return -EIO; 65 + 66 + return reply[3]; 67 + } 68 + 69 + static int qnap_mcu_hwmon_set_pwm(struct qnap_mcu_hwmon *hwm, u8 pwm) 70 + { 71 + const u8 cmd[] = { '@', 'F', 'W', '0', pwm }; /* 0 = fan-id?, pwm 0-255 */ 72 + 73 + /* set the fan pwm */ 74 + return qnap_mcu_exec_with_ack(hwm->mcu, cmd, sizeof(cmd)); 75 + } 76 + 77 + static int qnap_mcu_hwmon_get_temp(struct qnap_mcu_hwmon *hwm) 78 + { 79 + static const u8 cmd[] = { '@', 'T', '3' }; 80 + u8 reply[4]; 81 + int ret; 82 + 83 + /* poll the fan rpm */ 84 + ret = qnap_mcu_exec(hwm->mcu, cmd, sizeof(cmd), reply, sizeof(reply)); 85 + if (ret) 86 + return ret; 87 + 88 + /* First bytes must mirror the sent command */ 89 + if (memcmp(cmd, reply, sizeof(cmd))) 90 + return -EIO; 91 + 92 + /* 93 + * There is an unknown bit set in bit7. 94 + * Bits [6:0] report the actual temperature as returned by the 95 + * original qnap firmware-tools, so just drop bit7 for now. 96 + */ 97 + return (reply[3] & 0x7f) * 1000; 98 + } 99 + 100 + static int qnap_mcu_hwmon_write(struct device *dev, enum hwmon_sensor_types type, 101 + u32 attr, int channel, long val) 102 + { 103 + struct qnap_mcu_hwmon *hwm = dev_get_drvdata(dev); 104 + 105 + switch (attr) { 106 + case hwmon_pwm_input: 107 + if (val < 0 || val > 255) 108 + return -EINVAL; 109 + 110 + if (val != 0) 111 + val = clamp_val(val, hwm->pwm_min, hwm->pwm_max); 112 + 113 + return qnap_mcu_hwmon_set_pwm(hwm, val); 114 + default: 115 + return -EOPNOTSUPP; 116 + } 117 + 118 + return 0; 119 + } 120 + 121 + static int qnap_mcu_hwmon_read(struct device *dev, enum hwmon_sensor_types type, 122 + u32 attr, int channel, long *val) 123 + { 124 + struct qnap_mcu_hwmon *hwm = dev_get_drvdata(dev); 125 + int ret; 126 + 127 + switch (type) { 128 + case hwmon_pwm: 129 + switch (attr) { 130 + case hwmon_pwm_input: 131 + ret = qnap_mcu_hwmon_get_pwm(hwm); 132 + if (ret < 0) 133 + return ret; 134 + 135 + *val = ret; 136 + return 0; 137 + default: 138 + return -EOPNOTSUPP; 139 + } 140 + case hwmon_fan: 141 + ret = qnap_mcu_hwmon_get_rpm(hwm); 142 + if (ret < 0) 143 + return ret; 144 + 145 + *val = ret; 146 + return 0; 147 + case hwmon_temp: 148 + ret = qnap_mcu_hwmon_get_temp(hwm); 149 + if (ret < 0) 150 + return ret; 151 + 152 + *val = ret; 153 + return 0; 154 + default: 155 + return -EOPNOTSUPP; 156 + } 157 + } 158 + 159 + static umode_t qnap_mcu_hwmon_is_visible(const void *data, 160 + enum hwmon_sensor_types type, 161 + u32 attr, int channel) 162 + { 163 + switch (type) { 164 + case hwmon_temp: 165 + return 0444; 166 + 167 + case hwmon_pwm: 168 + return 0644; 169 + 170 + case hwmon_fan: 171 + return 0444; 172 + 173 + default: 174 + return 0; 175 + } 176 + } 177 + 178 + static const struct hwmon_ops qnap_mcu_hwmon_hwmon_ops = { 179 + .is_visible = qnap_mcu_hwmon_is_visible, 180 + .read = qnap_mcu_hwmon_read, 181 + .write = qnap_mcu_hwmon_write, 182 + }; 183 + 184 + /* thermal cooling device callbacks */ 185 + static int qnap_mcu_hwmon_get_max_state(struct thermal_cooling_device *cdev, 186 + unsigned long *state) 187 + { 188 + struct qnap_mcu_hwmon *hwm = cdev->devdata; 189 + 190 + if (!hwm) 191 + return -EINVAL; 192 + 193 + *state = hwm->fan_max_state; 194 + 195 + return 0; 196 + } 197 + 198 + static int qnap_mcu_hwmon_get_cur_state(struct thermal_cooling_device *cdev, 199 + unsigned long *state) 200 + { 201 + struct qnap_mcu_hwmon *hwm = cdev->devdata; 202 + 203 + if (!hwm) 204 + return -EINVAL; 205 + 206 + *state = hwm->fan_state; 207 + 208 + return 0; 209 + } 210 + 211 + static int qnap_mcu_hwmon_set_cur_state(struct thermal_cooling_device *cdev, 212 + unsigned long state) 213 + { 214 + struct qnap_mcu_hwmon *hwm = cdev->devdata; 215 + int ret; 216 + 217 + if (!hwm || state > hwm->fan_max_state) 218 + return -EINVAL; 219 + 220 + if (state == hwm->fan_state) 221 + return 0; 222 + 223 + ret = qnap_mcu_hwmon_set_pwm(hwm, hwm->fan_cooling_levels[state]); 224 + if (ret) 225 + return ret; 226 + 227 + hwm->fan_state = state; 228 + 229 + return ret; 230 + } 231 + 232 + static const struct thermal_cooling_device_ops qnap_mcu_hwmon_cooling_ops = { 233 + .get_max_state = qnap_mcu_hwmon_get_max_state, 234 + .get_cur_state = qnap_mcu_hwmon_get_cur_state, 235 + .set_cur_state = qnap_mcu_hwmon_set_cur_state, 236 + }; 237 + 238 + static void devm_fan_node_release(void *data) 239 + { 240 + struct qnap_mcu_hwmon *hwm = data; 241 + 242 + fwnode_handle_put(hwm->fan_node); 243 + } 244 + 245 + static int qnap_mcu_hwmon_get_cooling_data(struct device *dev, struct qnap_mcu_hwmon *hwm) 246 + { 247 + struct fwnode_handle *fwnode; 248 + int num, i, ret; 249 + 250 + fwnode = device_get_named_child_node(dev->parent, "fan-0"); 251 + if (!fwnode) 252 + return 0; 253 + 254 + /* if we found the fan-node, we're keeping it until device-unbind */ 255 + hwm->fan_node = fwnode; 256 + ret = devm_add_action_or_reset(dev, devm_fan_node_release, hwm); 257 + if (ret) 258 + return ret; 259 + 260 + num = fwnode_property_count_u32(fwnode, "cooling-levels"); 261 + if (num <= 0) 262 + return dev_err_probe(dev, num ? : -EINVAL, 263 + "Failed to count elements in 'cooling-levels'\n"); 264 + 265 + hwm->fan_cooling_levels = devm_kcalloc(dev, num, sizeof(u32), 266 + GFP_KERNEL); 267 + if (!hwm->fan_cooling_levels) 268 + return -ENOMEM; 269 + 270 + ret = fwnode_property_read_u32_array(fwnode, "cooling-levels", 271 + hwm->fan_cooling_levels, num); 272 + if (ret) 273 + return dev_err_probe(dev, ret, "Failed to read 'cooling-levels'\n"); 274 + 275 + for (i = 0; i < num; i++) { 276 + if (hwm->fan_cooling_levels[i] > hwm->pwm_max) 277 + return dev_err_probe(dev, -EINVAL, "fan state[%d]:%d > %d\n", i, 278 + hwm->fan_cooling_levels[i], hwm->pwm_max); 279 + } 280 + 281 + hwm->fan_max_state = num - 1; 282 + 283 + return 0; 284 + } 285 + 286 + static const struct hwmon_channel_info * const qnap_mcu_hwmon_channels[] = { 287 + HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT), 288 + HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT), 289 + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), 290 + NULL 291 + }; 292 + 293 + static int qnap_mcu_hwmon_probe(struct platform_device *pdev) 294 + { 295 + struct qnap_mcu *mcu = dev_get_drvdata(pdev->dev.parent); 296 + const struct qnap_mcu_variant *variant = pdev->dev.platform_data; 297 + struct qnap_mcu_hwmon *hwm; 298 + struct thermal_cooling_device *cdev; 299 + struct device *dev = &pdev->dev; 300 + struct device *hwmon; 301 + int ret; 302 + 303 + hwm = devm_kzalloc(dev, sizeof(*hwm), GFP_KERNEL); 304 + if (!hwm) 305 + return -ENOMEM; 306 + 307 + hwm->mcu = mcu; 308 + hwm->dev = &pdev->dev; 309 + hwm->pwm_min = variant->fan_pwm_min; 310 + hwm->pwm_max = variant->fan_pwm_max; 311 + 312 + platform_set_drvdata(pdev, hwm); 313 + 314 + /* 315 + * Set duty cycle to maximum allowed. 316 + */ 317 + ret = qnap_mcu_hwmon_set_pwm(hwm, hwm->pwm_max); 318 + if (ret) 319 + return ret; 320 + 321 + hwm->info.ops = &qnap_mcu_hwmon_hwmon_ops; 322 + hwm->info.info = qnap_mcu_hwmon_channels; 323 + 324 + ret = qnap_mcu_hwmon_get_cooling_data(dev, hwm); 325 + if (ret) 326 + return ret; 327 + 328 + hwm->fan_state = hwm->fan_max_state; 329 + 330 + hwmon = devm_hwmon_device_register_with_info(dev, "qnapmcu", 331 + hwm, &hwm->info, NULL); 332 + if (IS_ERR(hwmon)) 333 + return dev_err_probe(dev, PTR_ERR(hwmon), "Failed to register hwmon device\n"); 334 + 335 + /* 336 + * Only register cooling device when we found cooling-levels. 337 + * qnap_mcu_hwmon_get_cooling_data() will fail when reading malformed 338 + * levels and only succeed with either no or correct cooling levels. 339 + */ 340 + if (IS_ENABLED(CONFIG_THERMAL) && hwm->fan_cooling_levels) { 341 + cdev = devm_thermal_of_cooling_device_register(dev, 342 + to_of_node(hwm->fan_node), "qnap-mcu-hwmon", 343 + hwm, &qnap_mcu_hwmon_cooling_ops); 344 + if (IS_ERR(cdev)) 345 + return dev_err_probe(dev, PTR_ERR(cdev), 346 + "Failed to register qnap-mcu-hwmon as cooling device\n"); 347 + hwm->cdev = cdev; 348 + } 349 + 350 + return 0; 351 + } 352 + 353 + static struct platform_driver qnap_mcu_hwmon_driver = { 354 + .probe = qnap_mcu_hwmon_probe, 355 + .driver = { 356 + .name = "qnap-mcu-hwmon", 357 + }, 358 + }; 359 + module_platform_driver(qnap_mcu_hwmon_driver); 360 + 361 + MODULE_ALIAS("platform:qnap-mcu-hwmon"); 362 + MODULE_AUTHOR("Heiko Stuebner <heiko@sntech.de>"); 363 + MODULE_DESCRIPTION("QNAP MCU hwmon driver"); 364 + MODULE_LICENSE("GPL");