^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 1) // SPDX-License-Identifier: GPL-2.0-only
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 2) /*
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 3) * drivers/hwmon/nsa320-hwmon.c
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 4) *
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 5) * ZyXEL NSA320 Media Servers
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 6) * hardware monitoring
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 7) *
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 8) * Copyright (C) 2016 Adam Baker <linux@baker-net.org.uk>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 9) * based on a board file driver
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 10) * Copyright (C) 2012 Peter Schildmann <linux@schildmann.info>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 11) */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 12)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 13) #include <linux/bitops.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 14) #include <linux/delay.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 15) #include <linux/err.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 16) #include <linux/gpio/consumer.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 17) #include <linux/hwmon.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 18) #include <linux/hwmon-sysfs.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 19) #include <linux/jiffies.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 20) #include <linux/module.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 21) #include <linux/mutex.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 22) #include <linux/of.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 23) #include <linux/of_device.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 24) #include <linux/of_platform.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 25) #include <linux/platform_device.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 26)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 27) /* Tests for error return values rely upon this value being < 0x80 */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 28) #define MAGIC_NUMBER 0x55
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 29)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 30) /*
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 31) * The Zyxel hwmon MCU is a Holtek HT46R065 that is factory programmed
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 32) * to perform temperature and fan speed monitoring. It is read by taking
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 33) * the active pin low. The 32 bit output word is then clocked onto the
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 34) * data line. The MSB of the data word is a magic nuber to indicate it
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 35) * has been read correctly, the next byte is the fan speed (in hundreds
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 36) * of RPM) and the last two bytes are the temperature (in tenths of a
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 37) * degree)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 38) */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 39)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 40) struct nsa320_hwmon {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 41) struct mutex update_lock; /* lock GPIO operations */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 42) unsigned long last_updated; /* jiffies */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 43) unsigned long mcu_data;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 44) struct gpio_desc *act;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 45) struct gpio_desc *clk;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 46) struct gpio_desc *data;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 47) };
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 48)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 49) enum nsa320_inputs {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 50) NSA320_TEMP = 0,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 51) NSA320_FAN = 1,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 52) };
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 53)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 54) static const char * const nsa320_input_names[] = {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 55) [NSA320_TEMP] = "System Temperature",
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 56) [NSA320_FAN] = "Chassis Fan",
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 57) };
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 58)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 59) /*
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 60) * Although this protocol looks similar to SPI the long delay
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 61) * between the active (aka chip select) signal and the shorter
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 62) * delay between clock pulses are needed for reliable operation.
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 63) * The delays provided are taken from the manufacturer kernel,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 64) * testing suggest they probably incorporate a reasonable safety
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 65) * margin. (The single device tested became unreliable if the
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 66) * delay was reduced to 1/10th of this value.)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 67) */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 68) static s32 nsa320_hwmon_update(struct device *dev)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 69) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 70) u32 mcu_data;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 71) u32 mask;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 72) struct nsa320_hwmon *hwmon = dev_get_drvdata(dev);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 73)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 74) mutex_lock(&hwmon->update_lock);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 75)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 76) mcu_data = hwmon->mcu_data;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 77)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 78) if (time_after(jiffies, hwmon->last_updated + HZ) || mcu_data == 0) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 79) gpiod_set_value(hwmon->act, 1);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 80) msleep(100);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 81)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 82) mcu_data = 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 83) for (mask = BIT(31); mask; mask >>= 1) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 84) gpiod_set_value(hwmon->clk, 0);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 85) usleep_range(100, 200);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 86) gpiod_set_value(hwmon->clk, 1);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 87) usleep_range(100, 200);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 88) if (gpiod_get_value(hwmon->data))
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 89) mcu_data |= mask;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 90) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 91)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 92) gpiod_set_value(hwmon->act, 0);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 93) dev_dbg(dev, "Read raw MCU data %08x\n", mcu_data);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 94)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 95) if ((mcu_data >> 24) != MAGIC_NUMBER) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 96) dev_dbg(dev, "Read invalid MCU data %08x\n", mcu_data);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 97) mcu_data = -EIO;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 98) } else {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 99) hwmon->mcu_data = mcu_data;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 100) hwmon->last_updated = jiffies;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 101) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 102) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 103)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 104) mutex_unlock(&hwmon->update_lock);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 105)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 106) return mcu_data;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 107) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 108)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 109) static ssize_t label_show(struct device *dev, struct device_attribute *attr,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 110) char *buf)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 111) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 112) int channel = to_sensor_dev_attr(attr)->index;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 113)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 114) return sprintf(buf, "%s\n", nsa320_input_names[channel]);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 115) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 116)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 117) static ssize_t temp1_input_show(struct device *dev,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 118) struct device_attribute *attr, char *buf)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 119) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 120) s32 mcu_data = nsa320_hwmon_update(dev);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 121)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 122) if (mcu_data < 0)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 123) return mcu_data;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 124)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 125) return sprintf(buf, "%d\n", (mcu_data & 0xffff) * 100);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 126) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 127)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 128) static ssize_t fan1_input_show(struct device *dev,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 129) struct device_attribute *attr, char *buf)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 130) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 131) s32 mcu_data = nsa320_hwmon_update(dev);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 132)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 133) if (mcu_data < 0)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 134) return mcu_data;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 135)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 136) return sprintf(buf, "%d\n", ((mcu_data & 0xff0000) >> 16) * 100);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 137) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 138)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 139) static SENSOR_DEVICE_ATTR_RO(temp1_label, label, NSA320_TEMP);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 140) static DEVICE_ATTR_RO(temp1_input);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 141) static SENSOR_DEVICE_ATTR_RO(fan1_label, label, NSA320_FAN);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 142) static DEVICE_ATTR_RO(fan1_input);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 143)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 144) static struct attribute *nsa320_attrs[] = {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 145) &sensor_dev_attr_temp1_label.dev_attr.attr,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 146) &dev_attr_temp1_input.attr,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 147) &sensor_dev_attr_fan1_label.dev_attr.attr,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 148) &dev_attr_fan1_input.attr,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 149) NULL
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 150) };
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 151)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 152) ATTRIBUTE_GROUPS(nsa320);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 153)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 154) static const struct of_device_id of_nsa320_hwmon_match[] = {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 155) { .compatible = "zyxel,nsa320-mcu", },
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 156) { },
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 157) };
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 158)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 159) static int nsa320_hwmon_probe(struct platform_device *pdev)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 160) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 161) struct nsa320_hwmon *hwmon;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 162) struct device *classdev;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 163)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 164) hwmon = devm_kzalloc(&pdev->dev, sizeof(*hwmon), GFP_KERNEL);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 165) if (!hwmon)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 166) return -ENOMEM;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 167)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 168) /* Look up the GPIO pins to use */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 169) hwmon->act = devm_gpiod_get(&pdev->dev, "act", GPIOD_OUT_LOW);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 170) if (IS_ERR(hwmon->act))
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 171) return PTR_ERR(hwmon->act);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 172)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 173) hwmon->clk = devm_gpiod_get(&pdev->dev, "clk", GPIOD_OUT_HIGH);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 174) if (IS_ERR(hwmon->clk))
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 175) return PTR_ERR(hwmon->clk);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 176)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 177) hwmon->data = devm_gpiod_get(&pdev->dev, "data", GPIOD_IN);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 178) if (IS_ERR(hwmon->data))
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 179) return PTR_ERR(hwmon->data);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 180)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 181) mutex_init(&hwmon->update_lock);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 182)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 183) classdev = devm_hwmon_device_register_with_groups(&pdev->dev,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 184) "nsa320", hwmon, nsa320_groups);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 185)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 186) return PTR_ERR_OR_ZERO(classdev);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 187)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 188) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 189)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 190) /* All allocations use devres so remove() is not needed. */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 191)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 192) static struct platform_driver nsa320_hwmon_driver = {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 193) .probe = nsa320_hwmon_probe,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 194) .driver = {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 195) .name = "nsa320-hwmon",
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 196) .of_match_table = of_match_ptr(of_nsa320_hwmon_match),
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 197) },
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 198) };
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 199)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 200) module_platform_driver(nsa320_hwmon_driver);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 201)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 202) MODULE_DEVICE_TABLE(of, of_nsa320_hwmon_match);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 203) MODULE_AUTHOR("Peter Schildmann <linux@schildmann.info>");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 204) MODULE_AUTHOR("Adam Baker <linux@baker-net.org.uk>");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 205) MODULE_DESCRIPTION("NSA320 Hardware Monitoring");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 206) MODULE_LICENSE("GPL v2");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 207) MODULE_ALIAS("platform:nsa320-hwmon");