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