1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * drivers/hwmon/wm831x-hwmon.c - Wolfson Microelectronics WM831x PMIC |
4 | * hardware monitoring features. |
5 | * |
6 | * Copyright (C) 2009 Wolfson Microelectronics plc |
7 | */ |
8 | |
9 | #include <linux/kernel.h> |
10 | #include <linux/module.h> |
11 | #include <linux/platform_device.h> |
12 | #include <linux/err.h> |
13 | #include <linux/hwmon.h> |
14 | #include <linux/hwmon-sysfs.h> |
15 | #include <linux/slab.h> |
16 | |
17 | #include <linux/mfd/wm831x/core.h> |
18 | #include <linux/mfd/wm831x/auxadc.h> |
19 | |
20 | static const char * const input_names[] = { |
21 | [WM831X_AUX_SYSVDD] = "SYSVDD" , |
22 | [WM831X_AUX_USB] = "USB" , |
23 | [WM831X_AUX_BKUP_BATT] = "Backup battery" , |
24 | [WM831X_AUX_BATT] = "Battery" , |
25 | [WM831X_AUX_WALL] = "WALL" , |
26 | [WM831X_AUX_CHIP_TEMP] = "PMIC" , |
27 | [WM831X_AUX_BATT_TEMP] = "Battery" , |
28 | }; |
29 | |
30 | static ssize_t show_voltage(struct device *dev, |
31 | struct device_attribute *attr, char *buf) |
32 | { |
33 | struct wm831x *wm831x = dev_get_drvdata(dev); |
34 | int channel = to_sensor_dev_attr(attr)->index; |
35 | int ret; |
36 | |
37 | ret = wm831x_auxadc_read_uv(wm831x, input: channel); |
38 | if (ret < 0) |
39 | return ret; |
40 | |
41 | return sprintf(buf, fmt: "%d\n" , DIV_ROUND_CLOSEST(ret, 1000)); |
42 | } |
43 | |
44 | static ssize_t show_chip_temp(struct device *dev, |
45 | struct device_attribute *attr, char *buf) |
46 | { |
47 | struct wm831x *wm831x = dev_get_drvdata(dev); |
48 | int channel = to_sensor_dev_attr(attr)->index; |
49 | int ret; |
50 | |
51 | ret = wm831x_auxadc_read(wm831x, input: channel); |
52 | if (ret < 0) |
53 | return ret; |
54 | |
55 | /* Degrees celsius = (512.18-ret) / 1.0983 */ |
56 | ret = 512180 - (ret * 1000); |
57 | ret = DIV_ROUND_CLOSEST(ret * 10000, 10983); |
58 | |
59 | return sprintf(buf, fmt: "%d\n" , ret); |
60 | } |
61 | |
62 | static ssize_t show_label(struct device *dev, |
63 | struct device_attribute *attr, char *buf) |
64 | { |
65 | int channel = to_sensor_dev_attr(attr)->index; |
66 | |
67 | return sprintf(buf, fmt: "%s\n" , input_names[channel]); |
68 | } |
69 | |
70 | #define WM831X_VOLTAGE(id, name) \ |
71 | static SENSOR_DEVICE_ATTR(in##id##_input, S_IRUGO, show_voltage, \ |
72 | NULL, name) |
73 | |
74 | #define WM831X_NAMED_VOLTAGE(id, name) \ |
75 | WM831X_VOLTAGE(id, name); \ |
76 | static SENSOR_DEVICE_ATTR(in##id##_label, S_IRUGO, show_label, \ |
77 | NULL, name) |
78 | |
79 | WM831X_VOLTAGE(0, WM831X_AUX_AUX1); |
80 | WM831X_VOLTAGE(1, WM831X_AUX_AUX2); |
81 | WM831X_VOLTAGE(2, WM831X_AUX_AUX3); |
82 | WM831X_VOLTAGE(3, WM831X_AUX_AUX4); |
83 | |
84 | WM831X_NAMED_VOLTAGE(4, WM831X_AUX_SYSVDD); |
85 | WM831X_NAMED_VOLTAGE(5, WM831X_AUX_USB); |
86 | WM831X_NAMED_VOLTAGE(6, WM831X_AUX_BATT); |
87 | WM831X_NAMED_VOLTAGE(7, WM831X_AUX_WALL); |
88 | WM831X_NAMED_VOLTAGE(8, WM831X_AUX_BKUP_BATT); |
89 | |
90 | static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_chip_temp, NULL, |
91 | WM831X_AUX_CHIP_TEMP); |
92 | static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, show_label, NULL, |
93 | WM831X_AUX_CHIP_TEMP); |
94 | /* |
95 | * Report as a voltage since conversion depends on external components |
96 | * and that's what the ABI wants. |
97 | */ |
98 | static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_voltage, NULL, |
99 | WM831X_AUX_BATT_TEMP); |
100 | static SENSOR_DEVICE_ATTR(temp2_label, S_IRUGO, show_label, NULL, |
101 | WM831X_AUX_BATT_TEMP); |
102 | |
103 | static struct attribute *wm831x_attrs[] = { |
104 | &sensor_dev_attr_in0_input.dev_attr.attr, |
105 | &sensor_dev_attr_in1_input.dev_attr.attr, |
106 | &sensor_dev_attr_in2_input.dev_attr.attr, |
107 | &sensor_dev_attr_in3_input.dev_attr.attr, |
108 | |
109 | &sensor_dev_attr_in4_input.dev_attr.attr, |
110 | &sensor_dev_attr_in4_label.dev_attr.attr, |
111 | &sensor_dev_attr_in5_input.dev_attr.attr, |
112 | &sensor_dev_attr_in5_label.dev_attr.attr, |
113 | &sensor_dev_attr_in6_input.dev_attr.attr, |
114 | &sensor_dev_attr_in6_label.dev_attr.attr, |
115 | &sensor_dev_attr_in7_input.dev_attr.attr, |
116 | &sensor_dev_attr_in7_label.dev_attr.attr, |
117 | &sensor_dev_attr_in8_input.dev_attr.attr, |
118 | &sensor_dev_attr_in8_label.dev_attr.attr, |
119 | |
120 | &sensor_dev_attr_temp1_input.dev_attr.attr, |
121 | &sensor_dev_attr_temp1_label.dev_attr.attr, |
122 | &sensor_dev_attr_temp2_input.dev_attr.attr, |
123 | &sensor_dev_attr_temp2_label.dev_attr.attr, |
124 | |
125 | NULL |
126 | }; |
127 | |
128 | ATTRIBUTE_GROUPS(wm831x); |
129 | |
130 | static int wm831x_hwmon_probe(struct platform_device *pdev) |
131 | { |
132 | struct wm831x *wm831x = dev_get_drvdata(dev: pdev->dev.parent); |
133 | struct device *hwmon_dev; |
134 | |
135 | hwmon_dev = devm_hwmon_device_register_with_groups(dev: &pdev->dev, name: "wm831x" , |
136 | drvdata: wm831x, |
137 | groups: wm831x_groups); |
138 | return PTR_ERR_OR_ZERO(ptr: hwmon_dev); |
139 | } |
140 | |
141 | static struct platform_driver wm831x_hwmon_driver = { |
142 | .probe = wm831x_hwmon_probe, |
143 | .driver = { |
144 | .name = "wm831x-hwmon" , |
145 | }, |
146 | }; |
147 | |
148 | module_platform_driver(wm831x_hwmon_driver); |
149 | |
150 | MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>" ); |
151 | MODULE_DESCRIPTION("WM831x Hardware Monitoring" ); |
152 | MODULE_LICENSE("GPL" ); |
153 | MODULE_ALIAS("platform:wm831x-hwmon" ); |
154 | |