109c434b8SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2a80b10ccSGuenter Roeck /*
3a80b10ccSGuenter Roeck * ultra45_env.c: Driver for Ultra45 PIC16F747 environmental monitor.
4e0418088SDavid S. Miller *
5e0418088SDavid S. Miller * Copyright (C) 2008 David S. Miller <davem@davemloft.net>
6e0418088SDavid S. Miller */
7e0418088SDavid S. Miller
8e0418088SDavid S. Miller #include <linux/kernel.h>
9e0418088SDavid S. Miller #include <linux/types.h>
10e0418088SDavid S. Miller #include <linux/slab.h>
1131a1a152SPaul Gortmaker #include <linux/module.h>
12*39f03438SRob Herring #include <linux/of.h>
13*39f03438SRob Herring #include <linux/platform_device.h>
14e0418088SDavid S. Miller #include <linux/io.h>
15e0418088SDavid S. Miller #include <linux/hwmon.h>
16e0418088SDavid S. Miller #include <linux/hwmon-sysfs.h>
17fa845740SJean Delvare #include <linux/err.h>
18e0418088SDavid S. Miller
19e0418088SDavid S. Miller #define DRV_MODULE_VERSION "0.1"
20e0418088SDavid S. Miller
21e0418088SDavid S. Miller MODULE_AUTHOR("David S. Miller (davem@davemloft.net)");
22e0418088SDavid S. Miller MODULE_DESCRIPTION("Ultra45 environmental monitor driver");
23e0418088SDavid S. Miller MODULE_LICENSE("GPL");
24e0418088SDavid S. Miller MODULE_VERSION(DRV_MODULE_VERSION);
25e0418088SDavid S. Miller
26e0418088SDavid S. Miller /* PIC device registers */
27e0418088SDavid S. Miller #define REG_CMD 0x00UL
28e0418088SDavid S. Miller #define REG_CMD_RESET 0x80
29e0418088SDavid S. Miller #define REG_CMD_ESTAR 0x01
30e0418088SDavid S. Miller #define REG_STAT 0x01UL
31e0418088SDavid S. Miller #define REG_STAT_FWVER 0xf0
32e0418088SDavid S. Miller #define REG_STAT_TGOOD 0x08
33e0418088SDavid S. Miller #define REG_STAT_STALE 0x04
34e0418088SDavid S. Miller #define REG_STAT_BUSY 0x02
35e0418088SDavid S. Miller #define REG_STAT_FAULT 0x01
36e0418088SDavid S. Miller #define REG_DATA 0x40UL
37e0418088SDavid S. Miller #define REG_ADDR 0x41UL
38e0418088SDavid S. Miller #define REG_SIZE 0x42UL
39e0418088SDavid S. Miller
40e0418088SDavid S. Miller /* Registers accessed indirectly via REG_DATA/REG_ADDR */
41e0418088SDavid S. Miller #define IREG_FAN0 0x00
42e0418088SDavid S. Miller #define IREG_FAN1 0x01
43e0418088SDavid S. Miller #define IREG_FAN2 0x02
44e0418088SDavid S. Miller #define IREG_FAN3 0x03
45e0418088SDavid S. Miller #define IREG_FAN4 0x04
46e0418088SDavid S. Miller #define IREG_FAN5 0x05
47e0418088SDavid S. Miller #define IREG_LCL_TEMP 0x06
48e0418088SDavid S. Miller #define IREG_RMT1_TEMP 0x07
49e0418088SDavid S. Miller #define IREG_RMT2_TEMP 0x08
50e0418088SDavid S. Miller #define IREG_RMT3_TEMP 0x09
51e0418088SDavid S. Miller #define IREG_LM95221_TEMP 0x0a
52e0418088SDavid S. Miller #define IREG_FIRE_TEMP 0x0b
53e0418088SDavid S. Miller #define IREG_LSI1064_TEMP 0x0c
54e0418088SDavid S. Miller #define IREG_FRONT_TEMP 0x0d
55e0418088SDavid S. Miller #define IREG_FAN_STAT 0x0e
56e0418088SDavid S. Miller #define IREG_VCORE0 0x0f
57e0418088SDavid S. Miller #define IREG_VCORE1 0x10
58e0418088SDavid S. Miller #define IREG_VMEM0 0x11
59e0418088SDavid S. Miller #define IREG_VMEM1 0x12
60e0418088SDavid S. Miller #define IREG_PSU_TEMP 0x13
61e0418088SDavid S. Miller
62e0418088SDavid S. Miller struct env {
63e0418088SDavid S. Miller void __iomem *regs;
64e0418088SDavid S. Miller spinlock_t lock;
65e0418088SDavid S. Miller
66e0418088SDavid S. Miller struct device *hwmon_dev;
67e0418088SDavid S. Miller };
68e0418088SDavid S. Miller
env_read(struct env * p,u8 ireg)69e0418088SDavid S. Miller static u8 env_read(struct env *p, u8 ireg)
70e0418088SDavid S. Miller {
71e0418088SDavid S. Miller u8 ret;
72e0418088SDavid S. Miller
73e0418088SDavid S. Miller spin_lock(&p->lock);
74e0418088SDavid S. Miller writeb(ireg, p->regs + REG_ADDR);
75e0418088SDavid S. Miller ret = readb(p->regs + REG_DATA);
76e0418088SDavid S. Miller spin_unlock(&p->lock);
77e0418088SDavid S. Miller
78e0418088SDavid S. Miller return ret;
79e0418088SDavid S. Miller }
80e0418088SDavid S. Miller
env_write(struct env * p,u8 ireg,u8 val)81e0418088SDavid S. Miller static void env_write(struct env *p, u8 ireg, u8 val)
82e0418088SDavid S. Miller {
83e0418088SDavid S. Miller spin_lock(&p->lock);
84e0418088SDavid S. Miller writeb(ireg, p->regs + REG_ADDR);
85e0418088SDavid S. Miller writeb(val, p->regs + REG_DATA);
86e0418088SDavid S. Miller spin_unlock(&p->lock);
87e0418088SDavid S. Miller }
88e0418088SDavid S. Miller
89a80b10ccSGuenter Roeck /*
90a80b10ccSGuenter Roeck * There seems to be a adr7462 providing these values, thus a lot
91e0418088SDavid S. Miller * of these calculations are borrowed from the adt7470 driver.
92e0418088SDavid S. Miller */
93e0418088SDavid S. Miller #define FAN_PERIOD_TO_RPM(x) ((90000 * 60) / (x))
94e0418088SDavid S. Miller #define FAN_RPM_TO_PERIOD FAN_PERIOD_TO_RPM
95e0418088SDavid S. Miller #define FAN_PERIOD_INVALID (0xff << 8)
96e0418088SDavid S. Miller #define FAN_DATA_VALID(x) ((x) && (x) != FAN_PERIOD_INVALID)
97e0418088SDavid S. Miller
show_fan_speed(struct device * dev,struct device_attribute * attr,char * buf)98a80b10ccSGuenter Roeck static ssize_t show_fan_speed(struct device *dev, struct device_attribute *attr,
99a80b10ccSGuenter Roeck char *buf)
100e0418088SDavid S. Miller {
101e0418088SDavid S. Miller int fan_nr = to_sensor_dev_attr(attr)->index;
102e0418088SDavid S. Miller struct env *p = dev_get_drvdata(dev);
103e0418088SDavid S. Miller int rpm, period;
104e0418088SDavid S. Miller u8 val;
105e0418088SDavid S. Miller
106e0418088SDavid S. Miller val = env_read(p, IREG_FAN0 + fan_nr);
107e0418088SDavid S. Miller period = (int) val << 8;
108e0418088SDavid S. Miller if (FAN_DATA_VALID(period))
109e0418088SDavid S. Miller rpm = FAN_PERIOD_TO_RPM(period);
110e0418088SDavid S. Miller else
111e0418088SDavid S. Miller rpm = 0;
112e0418088SDavid S. Miller
113e0418088SDavid S. Miller return sprintf(buf, "%d\n", rpm);
114e0418088SDavid S. Miller }
115e0418088SDavid S. Miller
set_fan_speed(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)116e0418088SDavid S. Miller static ssize_t set_fan_speed(struct device *dev, struct device_attribute *attr,
117e0418088SDavid S. Miller const char *buf, size_t count)
118e0418088SDavid S. Miller {
119e0418088SDavid S. Miller int fan_nr = to_sensor_dev_attr(attr)->index;
120a80b10ccSGuenter Roeck unsigned long rpm;
121e0418088SDavid S. Miller struct env *p = dev_get_drvdata(dev);
122e0418088SDavid S. Miller int period;
123e0418088SDavid S. Miller u8 val;
124a80b10ccSGuenter Roeck int err;
125a80b10ccSGuenter Roeck
126a80b10ccSGuenter Roeck err = kstrtoul(buf, 10, &rpm);
127a80b10ccSGuenter Roeck if (err)
128a80b10ccSGuenter Roeck return err;
129e0418088SDavid S. Miller
130e0418088SDavid S. Miller if (!rpm)
131e0418088SDavid S. Miller return -EINVAL;
132e0418088SDavid S. Miller
133e0418088SDavid S. Miller period = FAN_RPM_TO_PERIOD(rpm);
134e0418088SDavid S. Miller val = period >> 8;
135e0418088SDavid S. Miller env_write(p, IREG_FAN0 + fan_nr, val);
136e0418088SDavid S. Miller
137e0418088SDavid S. Miller return count;
138e0418088SDavid S. Miller }
139e0418088SDavid S. Miller
show_fan_fault(struct device * dev,struct device_attribute * attr,char * buf)140a80b10ccSGuenter Roeck static ssize_t show_fan_fault(struct device *dev, struct device_attribute *attr,
141a80b10ccSGuenter Roeck char *buf)
142e0418088SDavid S. Miller {
143e0418088SDavid S. Miller int fan_nr = to_sensor_dev_attr(attr)->index;
144e0418088SDavid S. Miller struct env *p = dev_get_drvdata(dev);
145e0418088SDavid S. Miller u8 val = env_read(p, IREG_FAN_STAT);
146e0418088SDavid S. Miller return sprintf(buf, "%d\n", (val & (1 << fan_nr)) ? 1 : 0);
147e0418088SDavid S. Miller }
148e0418088SDavid S. Miller
149e0418088SDavid S. Miller #define fan(index) \
150e0418088SDavid S. Miller static SENSOR_DEVICE_ATTR(fan##index##_speed, S_IRUGO | S_IWUSR, \
151e0418088SDavid S. Miller show_fan_speed, set_fan_speed, index); \
152e0418088SDavid S. Miller static SENSOR_DEVICE_ATTR(fan##index##_fault, S_IRUGO, \
153e0418088SDavid S. Miller show_fan_fault, NULL, index)
154e0418088SDavid S. Miller
155e0418088SDavid S. Miller fan(0);
156e0418088SDavid S. Miller fan(1);
157e0418088SDavid S. Miller fan(2);
158e0418088SDavid S. Miller fan(3);
159e0418088SDavid S. Miller fan(4);
160e0418088SDavid S. Miller
161e0418088SDavid S. Miller static SENSOR_DEVICE_ATTR(psu_fan_fault, S_IRUGO, show_fan_fault, NULL, 6);
162e0418088SDavid S. Miller
show_temp(struct device * dev,struct device_attribute * attr,char * buf)163a80b10ccSGuenter Roeck static ssize_t show_temp(struct device *dev, struct device_attribute *attr,
164a80b10ccSGuenter Roeck char *buf)
165e0418088SDavid S. Miller {
166e0418088SDavid S. Miller int temp_nr = to_sensor_dev_attr(attr)->index;
167e0418088SDavid S. Miller struct env *p = dev_get_drvdata(dev);
168e0418088SDavid S. Miller s8 val;
169e0418088SDavid S. Miller
170e0418088SDavid S. Miller val = env_read(p, IREG_LCL_TEMP + temp_nr);
171e0418088SDavid S. Miller return sprintf(buf, "%d\n", ((int) val) - 64);
172e0418088SDavid S. Miller }
173e0418088SDavid S. Miller
174e0418088SDavid S. Miller static SENSOR_DEVICE_ATTR(adt7462_local_temp, S_IRUGO, show_temp, NULL, 0);
175e0418088SDavid S. Miller static SENSOR_DEVICE_ATTR(cpu0_temp, S_IRUGO, show_temp, NULL, 1);
176e0418088SDavid S. Miller static SENSOR_DEVICE_ATTR(cpu1_temp, S_IRUGO, show_temp, NULL, 2);
177e0418088SDavid S. Miller static SENSOR_DEVICE_ATTR(motherboard_temp, S_IRUGO, show_temp, NULL, 3);
178e0418088SDavid S. Miller static SENSOR_DEVICE_ATTR(lm95221_local_temp, S_IRUGO, show_temp, NULL, 4);
179e0418088SDavid S. Miller static SENSOR_DEVICE_ATTR(fire_temp, S_IRUGO, show_temp, NULL, 5);
180e0418088SDavid S. Miller static SENSOR_DEVICE_ATTR(lsi1064_local_temp, S_IRUGO, show_temp, NULL, 6);
181e0418088SDavid S. Miller static SENSOR_DEVICE_ATTR(front_panel_temp, S_IRUGO, show_temp, NULL, 7);
182e0418088SDavid S. Miller static SENSOR_DEVICE_ATTR(psu_temp, S_IRUGO, show_temp, NULL, 13);
183e0418088SDavid S. Miller
show_stat_bit(struct device * dev,struct device_attribute * attr,char * buf)184a80b10ccSGuenter Roeck static ssize_t show_stat_bit(struct device *dev, struct device_attribute *attr,
185a80b10ccSGuenter Roeck char *buf)
186e0418088SDavid S. Miller {
187e0418088SDavid S. Miller int index = to_sensor_dev_attr(attr)->index;
188e0418088SDavid S. Miller struct env *p = dev_get_drvdata(dev);
189e0418088SDavid S. Miller u8 val;
190e0418088SDavid S. Miller
191e0418088SDavid S. Miller val = readb(p->regs + REG_STAT);
192e0418088SDavid S. Miller return sprintf(buf, "%d\n", (val & (1 << index)) ? 1 : 0);
193e0418088SDavid S. Miller }
194e0418088SDavid S. Miller
195e0418088SDavid S. Miller static SENSOR_DEVICE_ATTR(fan_failure, S_IRUGO, show_stat_bit, NULL, 0);
196e0418088SDavid S. Miller static SENSOR_DEVICE_ATTR(env_bus_busy, S_IRUGO, show_stat_bit, NULL, 1);
197e0418088SDavid S. Miller static SENSOR_DEVICE_ATTR(env_data_stale, S_IRUGO, show_stat_bit, NULL, 2);
198a80b10ccSGuenter Roeck static SENSOR_DEVICE_ATTR(tpm_self_test_passed, S_IRUGO, show_stat_bit, NULL,
199a80b10ccSGuenter Roeck 3);
200e0418088SDavid S. Miller
show_fwver(struct device * dev,struct device_attribute * attr,char * buf)201a80b10ccSGuenter Roeck static ssize_t show_fwver(struct device *dev, struct device_attribute *attr,
202a80b10ccSGuenter Roeck char *buf)
203e0418088SDavid S. Miller {
204e0418088SDavid S. Miller struct env *p = dev_get_drvdata(dev);
205e0418088SDavid S. Miller u8 val;
206e0418088SDavid S. Miller
207e0418088SDavid S. Miller val = readb(p->regs + REG_STAT);
208e0418088SDavid S. Miller return sprintf(buf, "%d\n", val >> 4);
209e0418088SDavid S. Miller }
210e0418088SDavid S. Miller
211e0418088SDavid S. Miller static SENSOR_DEVICE_ATTR(firmware_version, S_IRUGO, show_fwver, NULL, 0);
212e0418088SDavid S. Miller
show_name(struct device * dev,struct device_attribute * attr,char * buf)213a80b10ccSGuenter Roeck static ssize_t show_name(struct device *dev, struct device_attribute *attr,
214a80b10ccSGuenter Roeck char *buf)
215e0418088SDavid S. Miller {
216e0418088SDavid S. Miller return sprintf(buf, "ultra45\n");
217e0418088SDavid S. Miller }
218e0418088SDavid S. Miller
219e0418088SDavid S. Miller static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, 0);
220e0418088SDavid S. Miller
221e0418088SDavid S. Miller static struct attribute *env_attributes[] = {
222e0418088SDavid S. Miller &sensor_dev_attr_fan0_speed.dev_attr.attr,
223e0418088SDavid S. Miller &sensor_dev_attr_fan0_fault.dev_attr.attr,
224e0418088SDavid S. Miller &sensor_dev_attr_fan1_speed.dev_attr.attr,
225e0418088SDavid S. Miller &sensor_dev_attr_fan1_fault.dev_attr.attr,
226e0418088SDavid S. Miller &sensor_dev_attr_fan2_speed.dev_attr.attr,
227e0418088SDavid S. Miller &sensor_dev_attr_fan2_fault.dev_attr.attr,
228e0418088SDavid S. Miller &sensor_dev_attr_fan3_speed.dev_attr.attr,
229e0418088SDavid S. Miller &sensor_dev_attr_fan3_fault.dev_attr.attr,
230e0418088SDavid S. Miller &sensor_dev_attr_fan4_speed.dev_attr.attr,
231e0418088SDavid S. Miller &sensor_dev_attr_fan4_fault.dev_attr.attr,
232e0418088SDavid S. Miller &sensor_dev_attr_psu_fan_fault.dev_attr.attr,
233e0418088SDavid S. Miller &sensor_dev_attr_adt7462_local_temp.dev_attr.attr,
234e0418088SDavid S. Miller &sensor_dev_attr_cpu0_temp.dev_attr.attr,
235e0418088SDavid S. Miller &sensor_dev_attr_cpu1_temp.dev_attr.attr,
236e0418088SDavid S. Miller &sensor_dev_attr_motherboard_temp.dev_attr.attr,
237e0418088SDavid S. Miller &sensor_dev_attr_lm95221_local_temp.dev_attr.attr,
238e0418088SDavid S. Miller &sensor_dev_attr_fire_temp.dev_attr.attr,
239e0418088SDavid S. Miller &sensor_dev_attr_lsi1064_local_temp.dev_attr.attr,
240e0418088SDavid S. Miller &sensor_dev_attr_front_panel_temp.dev_attr.attr,
241e0418088SDavid S. Miller &sensor_dev_attr_psu_temp.dev_attr.attr,
242e0418088SDavid S. Miller &sensor_dev_attr_fan_failure.dev_attr.attr,
243e0418088SDavid S. Miller &sensor_dev_attr_env_bus_busy.dev_attr.attr,
244e0418088SDavid S. Miller &sensor_dev_attr_env_data_stale.dev_attr.attr,
245e0418088SDavid S. Miller &sensor_dev_attr_tpm_self_test_passed.dev_attr.attr,
246e0418088SDavid S. Miller &sensor_dev_attr_firmware_version.dev_attr.attr,
247e0418088SDavid S. Miller &sensor_dev_attr_name.dev_attr.attr,
248e0418088SDavid S. Miller NULL,
249e0418088SDavid S. Miller };
250e0418088SDavid S. Miller
251e0418088SDavid S. Miller static const struct attribute_group env_group = {
252e0418088SDavid S. Miller .attrs = env_attributes,
253e0418088SDavid S. Miller };
254e0418088SDavid S. Miller
env_probe(struct platform_device * op)2556c931ae1SBill Pemberton static int env_probe(struct platform_device *op)
256e0418088SDavid S. Miller {
257e8cba3cdSHimangi Saraogi struct env *p = devm_kzalloc(&op->dev, sizeof(*p), GFP_KERNEL);
258e0418088SDavid S. Miller int err = -ENOMEM;
259e0418088SDavid S. Miller
260e0418088SDavid S. Miller if (!p)
261e0418088SDavid S. Miller goto out;
262e0418088SDavid S. Miller
263e0418088SDavid S. Miller spin_lock_init(&p->lock);
264e0418088SDavid S. Miller
265e0418088SDavid S. Miller p->regs = of_ioremap(&op->resource[0], 0, REG_SIZE, "pic16f747");
266e0418088SDavid S. Miller if (!p->regs)
267e8cba3cdSHimangi Saraogi goto out;
268e0418088SDavid S. Miller
269e0418088SDavid S. Miller err = sysfs_create_group(&op->dev.kobj, &env_group);
270e0418088SDavid S. Miller if (err)
271e0418088SDavid S. Miller goto out_iounmap;
272e0418088SDavid S. Miller
273e0418088SDavid S. Miller p->hwmon_dev = hwmon_device_register(&op->dev);
274e0418088SDavid S. Miller if (IS_ERR(p->hwmon_dev)) {
275e0418088SDavid S. Miller err = PTR_ERR(p->hwmon_dev);
276e0418088SDavid S. Miller goto out_sysfs_remove_group;
277e0418088SDavid S. Miller }
278e0418088SDavid S. Miller
27995de3b25SJean Delvare platform_set_drvdata(op, p);
280e0418088SDavid S. Miller err = 0;
281e0418088SDavid S. Miller
282e0418088SDavid S. Miller out:
283e0418088SDavid S. Miller return err;
284e0418088SDavid S. Miller
285e0418088SDavid S. Miller out_sysfs_remove_group:
286e0418088SDavid S. Miller sysfs_remove_group(&op->dev.kobj, &env_group);
287e0418088SDavid S. Miller
288e0418088SDavid S. Miller out_iounmap:
289e0418088SDavid S. Miller of_iounmap(&op->resource[0], p->regs, REG_SIZE);
290e0418088SDavid S. Miller
291e0418088SDavid S. Miller goto out;
292e0418088SDavid S. Miller }
293e0418088SDavid S. Miller
env_remove(struct platform_device * op)294281dfd0bSBill Pemberton static int env_remove(struct platform_device *op)
295e0418088SDavid S. Miller {
29695de3b25SJean Delvare struct env *p = platform_get_drvdata(op);
297e0418088SDavid S. Miller
298e0418088SDavid S. Miller if (p) {
299e0418088SDavid S. Miller sysfs_remove_group(&op->dev.kobj, &env_group);
300e0418088SDavid S. Miller hwmon_device_unregister(p->hwmon_dev);
301e0418088SDavid S. Miller of_iounmap(&op->resource[0], p->regs, REG_SIZE);
302e0418088SDavid S. Miller }
303e0418088SDavid S. Miller
304e0418088SDavid S. Miller return 0;
305e0418088SDavid S. Miller }
306e0418088SDavid S. Miller
307fd098316SDavid S. Miller static const struct of_device_id env_match[] = {
308e0418088SDavid S. Miller {
309e0418088SDavid S. Miller .name = "env-monitor",
310e0418088SDavid S. Miller .compatible = "SUNW,ebus-pic16f747-env",
311e0418088SDavid S. Miller },
312e0418088SDavid S. Miller {},
313e0418088SDavid S. Miller };
314e0418088SDavid S. Miller MODULE_DEVICE_TABLE(of, env_match);
315e0418088SDavid S. Miller
3164ebb24f7SGrant Likely static struct platform_driver env_driver = {
3174018294bSGrant Likely .driver = {
318e0418088SDavid S. Miller .name = "ultra45_env",
3194018294bSGrant Likely .of_match_table = env_match,
3204018294bSGrant Likely },
321e0418088SDavid S. Miller .probe = env_probe,
3229e5e9b7aSBill Pemberton .remove = env_remove,
323e0418088SDavid S. Miller };
324e0418088SDavid S. Miller
32525a236a5SAxel Lin module_platform_driver(env_driver);
326