root/drivers/net/ethernet/broadcom/bnxt/bnxt_hwmon.c
/* Broadcom NetXtreme-C/E network driver.
 *
 * Copyright (c) 2023 Broadcom Limited
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation.
 */

#include <linux/dev_printk.h>
#include <linux/errno.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/pci.h>
#include <linux/bnxt/hsi.h>

#include "bnxt.h"
#include "bnxt_hwrm.h"
#include "bnxt_hwmon.h"

void bnxt_hwmon_notify_event(struct bnxt *bp)
{
        u32 attr;

        if (!bp->hwmon_dev)
                return;

        switch (bp->thermal_threshold_type) {
        case ASYNC_EVENT_CMPL_ERROR_REPORT_THERMAL_EVENT_DATA1_THRESHOLD_TYPE_WARN:
                attr = hwmon_temp_max_alarm;
                break;
        case ASYNC_EVENT_CMPL_ERROR_REPORT_THERMAL_EVENT_DATA1_THRESHOLD_TYPE_CRITICAL:
                attr = hwmon_temp_crit_alarm;
                break;
        case ASYNC_EVENT_CMPL_ERROR_REPORT_THERMAL_EVENT_DATA1_THRESHOLD_TYPE_FATAL:
        case ASYNC_EVENT_CMPL_ERROR_REPORT_THERMAL_EVENT_DATA1_THRESHOLD_TYPE_SHUTDOWN:
                attr = hwmon_temp_emergency_alarm;
                break;
        default:
                return;
        }

        hwmon_notify_event(&bp->pdev->dev, hwmon_temp, attr, 0);
}

static int bnxt_hwrm_temp_query(struct bnxt *bp, u8 *temp)
{
        struct hwrm_temp_monitor_query_output *resp;
        struct hwrm_temp_monitor_query_input *req;
        int rc;

        rc = hwrm_req_init(bp, req, HWRM_TEMP_MONITOR_QUERY);
        if (rc)
                return rc;
        resp = hwrm_req_hold(bp, req);
        rc = hwrm_req_send_silent(bp, req);
        if (rc)
                goto drop_req;

        if (temp) {
                *temp = resp->temp;
        } else if (resp->flags &
                   TEMP_MONITOR_QUERY_RESP_FLAGS_THRESHOLD_VALUES_AVAILABLE) {
                bp->fw_cap |= BNXT_FW_CAP_THRESHOLD_TEMP_SUPPORTED;
                bp->warn_thresh_temp = resp->warn_threshold;
                bp->crit_thresh_temp = resp->critical_threshold;
                bp->fatal_thresh_temp = resp->fatal_threshold;
                bp->shutdown_thresh_temp = resp->shutdown_threshold;
        }
drop_req:
        hwrm_req_drop(bp, req);
        return rc;
}

static umode_t bnxt_hwmon_is_visible(const void *_data, enum hwmon_sensor_types type,
                                     u32 attr, int channel)
{
        const struct bnxt *bp = _data;

        if (type != hwmon_temp)
                return 0;

        switch (attr) {
        case hwmon_temp_input:
                return 0444;
        case hwmon_temp_max:
        case hwmon_temp_crit:
        case hwmon_temp_emergency:
        case hwmon_temp_max_alarm:
        case hwmon_temp_crit_alarm:
        case hwmon_temp_emergency_alarm:
                if (!(bp->fw_cap & BNXT_FW_CAP_THRESHOLD_TEMP_SUPPORTED))
                        return 0;
                return 0444;
        default:
                return 0;
        }
}

static int bnxt_hwmon_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
                           int channel, long *val)
{
        struct bnxt *bp = dev_get_drvdata(dev);
        u8 temp = 0;
        int rc;

        switch (attr) {
        case hwmon_temp_input:
                rc = bnxt_hwrm_temp_query(bp, &temp);
                if (!rc)
                        *val = temp * 1000;
                return rc;
        case hwmon_temp_max:
                *val = bp->warn_thresh_temp * 1000;
                return 0;
        case hwmon_temp_crit:
                *val = bp->crit_thresh_temp * 1000;
                return 0;
        case hwmon_temp_emergency:
                *val = bp->fatal_thresh_temp * 1000;
                return 0;
        case hwmon_temp_max_alarm:
                rc = bnxt_hwrm_temp_query(bp, &temp);
                if (!rc)
                        *val = temp >= bp->warn_thresh_temp;
                return rc;
        case hwmon_temp_crit_alarm:
                rc = bnxt_hwrm_temp_query(bp, &temp);
                if (!rc)
                        *val = temp >= bp->crit_thresh_temp;
                return rc;
        case hwmon_temp_emergency_alarm:
                rc = bnxt_hwrm_temp_query(bp, &temp);
                if (!rc)
                        *val = temp >= bp->fatal_thresh_temp;
                return rc;
        default:
                return -EOPNOTSUPP;
        }
}

static const struct hwmon_channel_info *bnxt_hwmon_info[] = {
        HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT |
                           HWMON_T_EMERGENCY | HWMON_T_MAX_ALARM |
                           HWMON_T_CRIT_ALARM | HWMON_T_EMERGENCY_ALARM),
        NULL
};

static const struct hwmon_ops bnxt_hwmon_ops = {
        .is_visible     = bnxt_hwmon_is_visible,
        .read           = bnxt_hwmon_read,
};

static const struct hwmon_chip_info bnxt_hwmon_chip_info = {
        .ops    = &bnxt_hwmon_ops,
        .info   = bnxt_hwmon_info,
};

static ssize_t temp1_shutdown_show(struct device *dev,
                                   struct device_attribute *attr, char *buf)
{
        struct bnxt *bp = dev_get_drvdata(dev);

        return sysfs_emit(buf, "%u\n", bp->shutdown_thresh_temp * 1000);
}

static ssize_t temp1_shutdown_alarm_show(struct device *dev,
                                         struct device_attribute *attr, char *buf)
{
        struct bnxt *bp = dev_get_drvdata(dev);
        u8 temp;
        int rc;

        rc = bnxt_hwrm_temp_query(bp, &temp);
        if (rc)
                return -EIO;

        return sysfs_emit(buf, "%u\n", temp >= bp->shutdown_thresh_temp);
}

static DEVICE_ATTR_RO(temp1_shutdown);
static DEVICE_ATTR_RO(temp1_shutdown_alarm);

static struct attribute *bnxt_temp_extra_attrs[] = {
        &dev_attr_temp1_shutdown.attr,
        &dev_attr_temp1_shutdown_alarm.attr,
        NULL,
};

static umode_t bnxt_temp_extra_attrs_visible(struct kobject *kobj,
                                             struct attribute *attr, int index)
{
        struct device *dev = kobj_to_dev(kobj);
        struct bnxt *bp = dev_get_drvdata(dev);

        /* Shutdown temperature setting in NVM is optional */
        if (!(bp->fw_cap & BNXT_FW_CAP_THRESHOLD_TEMP_SUPPORTED) ||
            !bp->shutdown_thresh_temp)
                return 0;

        return attr->mode;
}

static const struct attribute_group bnxt_temp_extra_group = {
        .attrs          = bnxt_temp_extra_attrs,
        .is_visible     = bnxt_temp_extra_attrs_visible,
};
__ATTRIBUTE_GROUPS(bnxt_temp_extra);

void bnxt_hwmon_uninit(struct bnxt *bp)
{
        if (bp->hwmon_dev) {
                hwmon_device_unregister(bp->hwmon_dev);
                bp->hwmon_dev = NULL;
        }
}

void bnxt_hwmon_init(struct bnxt *bp)
{
        struct pci_dev *pdev = bp->pdev;
        int rc;

        /* temp1_xxx is only sensor, ensure not registered if it will fail */
        rc = bnxt_hwrm_temp_query(bp, NULL);
        if (rc == -EACCES || rc == -EOPNOTSUPP) {
                bnxt_hwmon_uninit(bp);
                return;
        }

        if (bp->hwmon_dev)
                return;

        bp->hwmon_dev = hwmon_device_register_with_info(&pdev->dev,
                                                        DRV_MODULE_NAME, bp,
                                                        &bnxt_hwmon_chip_info,
                                                        bnxt_temp_extra_groups);
        if (IS_ERR(bp->hwmon_dev)) {
                bp->hwmon_dev = NULL;
                dev_warn(&pdev->dev, "Cannot register hwmon device\n");
        }
}