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