xref: /linux/drivers/scsi/mpt3sas/mpt3sas_hwmon.c (revision 67f8bc848ee31831336bd478e57d2f993551902e)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Hardware monitoring (hwmon) support for the LSI / Broadcom mpt3sas
4  * SAS HBA driver. Exposes the IOC and board temperature sensors by
5  * reading MPI IO Unit Page 7.
6  */
7 
8 #include <linux/err.h>
9 #include <linux/hwmon.h>
10 #include <linux/slab.h>
11 
12 #include "mpt3sas_base.h"
13 
14 struct mpt3sas_hwmon {
15 	struct MPT3SAS_ADAPTER *ioc;
16 	struct device *hwmon_dev;
17 	bool ioc_present;
18 	bool board_present;
19 };
20 
21 /*
22  * Convert a (raw, units) reading to millidegrees Celsius.
23  * Returns -ENODATA when the sensor reports "not present" or
24  * unknown units. Temperature values are interpreted as signed
25  * two's-complement integers.
26  *
27  * The MPI2_IOUNITPAGE7_IOC_TEMP_* and MPI2_IOUNITPAGE7_BOARD_TEMP_*
28  * defines in mpi2_cnfg.h share the same values; the IOC ones are
29  * used for both channels.
30  */
31 static int _hwmon_to_mdegc(s16 raw, u8 units, long *out)
32 {
33 	switch (units) {
34 	case MPI2_IOUNITPAGE7_IOC_TEMP_CELSIUS:
35 		*out = (long)raw * 1000;
36 		return 0;
37 	case MPI2_IOUNITPAGE7_IOC_TEMP_FAHRENHEIT:
38 		/* (F - 32) * 5 / 9, expressed in milli-units */
39 		*out = ((long)raw - 32) * 5000 / 9;
40 		return 0;
41 	default:
42 		return -ENODATA;
43 	}
44 }
45 
46 static umode_t _hwmon_is_visible(const void *drvdata,
47 				 enum hwmon_sensor_types type,
48 				 u32 attr, int channel)
49 {
50 	const struct mpt3sas_hwmon *h = drvdata;
51 
52 	if (type != hwmon_temp)
53 		return 0;
54 	if (attr != hwmon_temp_input && attr != hwmon_temp_label)
55 		return 0;
56 	if (channel == 0 && h->ioc_present)
57 		return 0444;
58 	if (channel == 1 && h->board_present)
59 		return 0444;
60 	return 0;
61 }
62 
63 static int _hwmon_read(struct device *dev, enum hwmon_sensor_types type,
64 		       u32 attr, int channel, long *val)
65 {
66 	struct mpt3sas_hwmon *h = dev_get_drvdata(dev);
67 	Mpi2ConfigReply_t mpi_reply;
68 	Mpi2IOUnitPage7_t page;
69 	int r;
70 
71 	if (type != hwmon_temp || attr != hwmon_temp_input)
72 		return -EOPNOTSUPP;
73 
74 	r = mpt3sas_config_get_iounit_pg7(h->ioc, &mpi_reply, &page);
75 	if (r)
76 		return r;
77 
78 	if (channel == 0)
79 		return _hwmon_to_mdegc((s16)le16_to_cpu(page.IOCTemperature),
80 				       page.IOCTemperatureUnits, val);
81 	if (channel == 1)
82 		return _hwmon_to_mdegc((s16)le16_to_cpu(page.BoardTemperature),
83 				       page.BoardTemperatureUnits, val);
84 	return -EOPNOTSUPP;
85 }
86 
87 static const char * const mpt3sas_hwmon_temp_labels[] = {
88 	"IOC",
89 	"Board",
90 };
91 
92 static int _hwmon_read_string(struct device *dev,
93 			      enum hwmon_sensor_types type,
94 			      u32 attr, int channel, const char **str)
95 {
96 	if (type != hwmon_temp || attr != hwmon_temp_label)
97 		return -EOPNOTSUPP;
98 	*str = mpt3sas_hwmon_temp_labels[channel];
99 	return 0;
100 }
101 
102 static const struct hwmon_channel_info * const mpt3sas_hwmon_info[] = {
103 	HWMON_CHANNEL_INFO(temp,
104 			   HWMON_T_INPUT | HWMON_T_LABEL,
105 			   HWMON_T_INPUT | HWMON_T_LABEL),
106 	NULL,
107 };
108 
109 static const struct hwmon_ops mpt3sas_hwmon_ops = {
110 	.is_visible	= _hwmon_is_visible,
111 	.read		= _hwmon_read,
112 	.read_string	= _hwmon_read_string,
113 };
114 
115 static const struct hwmon_chip_info mpt3sas_hwmon_chip_info = {
116 	.ops	= &mpt3sas_hwmon_ops,
117 	.info	= mpt3sas_hwmon_info,
118 };
119 
120 /**
121  * mpt3sas_hwmon_register - register an hwmon device for the IOC
122  * @ioc: per adapter object
123  * Context: sleep.
124  *
125  * Succeeds without registering when no temperature sensors are present,
126  * so cards without thermal monitoring do not expose an empty hwmon node.
127  * Paired with mpt3sas_hwmon_unregister() from the driver's remove path.
128  *
129  * Return: 0 for success, non-zero for failure.
130  */
131 int mpt3sas_hwmon_register(struct MPT3SAS_ADAPTER *ioc)
132 {
133 	struct device *parent = &ioc->pdev->dev;
134 	struct mpt3sas_hwmon *h;
135 	struct device *hwdev;
136 	Mpi2ConfigReply_t mpi_reply;
137 	Mpi2IOUnitPage7_t page;
138 	int r;
139 
140 	r = mpt3sas_config_get_iounit_pg7(ioc, &mpi_reply, &page);
141 	if (r)
142 		return r;
143 
144 	/*
145 	 * A page where both *TemperatureUnits are NOT_PRESENT covers
146 	 * two cases: cards that genuinely lack sensors, and firmware
147 	 * errors that left the page zero-filled (the accessor mirrors
148 	 * _config_request() behaviour). Either way: skip registration.
149 	 */
150 	if (page.IOCTemperatureUnits == MPI2_IOUNITPAGE7_IOC_TEMP_NOT_PRESENT &&
151 	    page.BoardTemperatureUnits == MPI2_IOUNITPAGE7_BOARD_TEMP_NOT_PRESENT)
152 		return 0;
153 
154 	h = kzalloc_obj(*h);
155 	if (!h)
156 		return -ENOMEM;
157 
158 	h->ioc = ioc;
159 	h->ioc_present = page.IOCTemperatureUnits != MPI2_IOUNITPAGE7_IOC_TEMP_NOT_PRESENT;
160 	h->board_present = page.BoardTemperatureUnits != MPI2_IOUNITPAGE7_BOARD_TEMP_NOT_PRESENT;
161 
162 	hwdev = hwmon_device_register_with_info(parent, "mpt3sas", h,
163 						&mpt3sas_hwmon_chip_info,
164 						NULL);
165 	if (IS_ERR(hwdev)) {
166 		kfree(h);
167 		return PTR_ERR(hwdev);
168 	}
169 
170 	h->hwmon_dev = hwdev;
171 	ioc->hwmon = h;
172 	return 0;
173 }
174 
175 /**
176  * mpt3sas_hwmon_unregister - tear down the hwmon device, if any
177  * @ioc: per adapter object
178  *
179  * Safe to call when registration was skipped (no sensors) or
180  * failed; in those cases ioc->hwmon is NULL and this is a no-op.
181  */
182 void mpt3sas_hwmon_unregister(struct MPT3SAS_ADAPTER *ioc)
183 {
184 	struct mpt3sas_hwmon *h = ioc->hwmon;
185 
186 	if (!h)
187 		return;
188 	hwmon_device_unregister(h->hwmon_dev);
189 	kfree(h);
190 	ioc->hwmon = NULL;
191 }
192