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