root/drivers/gpu/drm/nouveau/nouveau_hwmon.c
/*
 * Copyright 2010 Red Hat Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * Authors: Ben Skeggs
 */

#ifdef CONFIG_ACPI
#include <linux/acpi.h>
#endif
#include <linux/power_supply.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>

#include "nouveau_drv.h"
#include "nouveau_hwmon.h"

#include <nvkm/subdev/iccsense.h>
#include <nvkm/subdev/volt.h>

#if defined(CONFIG_HWMON) || (defined(MODULE) && defined(CONFIG_HWMON_MODULE))

static ssize_t
nouveau_hwmon_show_temp1_auto_point1_pwm(struct device *d,
                                         struct device_attribute *a, char *buf)
{
        return sysfs_emit(buf, "%d\n", 100);
}
static SENSOR_DEVICE_ATTR(temp1_auto_point1_pwm, 0444,
                          nouveau_hwmon_show_temp1_auto_point1_pwm, NULL, 0);

static ssize_t
nouveau_hwmon_temp1_auto_point1_temp(struct device *d,
                                     struct device_attribute *a, char *buf)
{
        struct drm_device *dev = dev_get_drvdata(d);
        struct nouveau_drm *drm = nouveau_drm(dev);
        struct nvkm_therm *therm = nvxx_therm(drm);

        return sysfs_emit(buf, "%d\n",
                          therm->attr_get(therm, NVKM_THERM_ATTR_THRS_FAN_BOOST) * 1000);
}
static ssize_t
nouveau_hwmon_set_temp1_auto_point1_temp(struct device *d,
                                         struct device_attribute *a,
                                         const char *buf, size_t count)
{
        struct drm_device *dev = dev_get_drvdata(d);
        struct nouveau_drm *drm = nouveau_drm(dev);
        struct nvkm_therm *therm = nvxx_therm(drm);
        long value;

        if (kstrtol(buf, 10, &value))
                return -EINVAL;

        therm->attr_set(therm, NVKM_THERM_ATTR_THRS_FAN_BOOST,
                        value / 1000);

        return count;
}
static SENSOR_DEVICE_ATTR(temp1_auto_point1_temp, 0644,
                          nouveau_hwmon_temp1_auto_point1_temp,
                          nouveau_hwmon_set_temp1_auto_point1_temp, 0);

static ssize_t
nouveau_hwmon_temp1_auto_point1_temp_hyst(struct device *d,
                                          struct device_attribute *a, char *buf)
{
        struct drm_device *dev = dev_get_drvdata(d);
        struct nouveau_drm *drm = nouveau_drm(dev);
        struct nvkm_therm *therm = nvxx_therm(drm);

        return sysfs_emit(buf, "%d\n",
                          therm->attr_get(therm, NVKM_THERM_ATTR_THRS_FAN_BOOST_HYST) * 1000);
}
static ssize_t
nouveau_hwmon_set_temp1_auto_point1_temp_hyst(struct device *d,
                                              struct device_attribute *a,
                                              const char *buf, size_t count)
{
        struct drm_device *dev = dev_get_drvdata(d);
        struct nouveau_drm *drm = nouveau_drm(dev);
        struct nvkm_therm *therm = nvxx_therm(drm);
        long value;

        if (kstrtol(buf, 10, &value))
                return -EINVAL;

        therm->attr_set(therm, NVKM_THERM_ATTR_THRS_FAN_BOOST_HYST,
                        value / 1000);

        return count;
}
static SENSOR_DEVICE_ATTR(temp1_auto_point1_temp_hyst, 0644,
                          nouveau_hwmon_temp1_auto_point1_temp_hyst,
                          nouveau_hwmon_set_temp1_auto_point1_temp_hyst, 0);

static ssize_t
nouveau_hwmon_get_pwm1_max(struct device *d,
                           struct device_attribute *a, char *buf)
{
        struct drm_device *dev = dev_get_drvdata(d);
        struct nouveau_drm *drm = nouveau_drm(dev);
        struct nvkm_therm *therm = nvxx_therm(drm);
        int ret;

        ret = therm->attr_get(therm, NVKM_THERM_ATTR_FAN_MAX_DUTY);
        if (ret < 0)
                return ret;

        return sysfs_emit(buf, "%i\n", ret);
}

static ssize_t
nouveau_hwmon_get_pwm1_min(struct device *d,
                           struct device_attribute *a, char *buf)
{
        struct drm_device *dev = dev_get_drvdata(d);
        struct nouveau_drm *drm = nouveau_drm(dev);
        struct nvkm_therm *therm = nvxx_therm(drm);
        int ret;

        ret = therm->attr_get(therm, NVKM_THERM_ATTR_FAN_MIN_DUTY);
        if (ret < 0)
                return ret;

        return sysfs_emit(buf, "%i\n", ret);
}

static ssize_t
nouveau_hwmon_set_pwm1_min(struct device *d, struct device_attribute *a,
                           const char *buf, size_t count)
{
        struct drm_device *dev = dev_get_drvdata(d);
        struct nouveau_drm *drm = nouveau_drm(dev);
        struct nvkm_therm *therm = nvxx_therm(drm);
        long value;
        int ret;

        if (kstrtol(buf, 10, &value))
                return -EINVAL;

        ret = therm->attr_set(therm, NVKM_THERM_ATTR_FAN_MIN_DUTY, value);
        if (ret < 0)
                return ret;

        return count;
}
static SENSOR_DEVICE_ATTR(pwm1_min, 0644,
                          nouveau_hwmon_get_pwm1_min,
                          nouveau_hwmon_set_pwm1_min, 0);

static ssize_t
nouveau_hwmon_set_pwm1_max(struct device *d, struct device_attribute *a,
                           const char *buf, size_t count)
{
        struct drm_device *dev = dev_get_drvdata(d);
        struct nouveau_drm *drm = nouveau_drm(dev);
        struct nvkm_therm *therm = nvxx_therm(drm);
        long value;
        int ret;

        if (kstrtol(buf, 10, &value))
                return -EINVAL;

        ret = therm->attr_set(therm, NVKM_THERM_ATTR_FAN_MAX_DUTY, value);
        if (ret < 0)
                return ret;

        return count;
}
static SENSOR_DEVICE_ATTR(pwm1_max, 0644,
                          nouveau_hwmon_get_pwm1_max,
                          nouveau_hwmon_set_pwm1_max, 0);

static struct attribute *pwm_fan_sensor_attrs[] = {
        &sensor_dev_attr_pwm1_min.dev_attr.attr,
        &sensor_dev_attr_pwm1_max.dev_attr.attr,
        NULL
};
static const struct attribute_group pwm_fan_sensor_group = {
        .attrs = pwm_fan_sensor_attrs,
};

static struct attribute *temp1_auto_point_sensor_attrs[] = {
        &sensor_dev_attr_temp1_auto_point1_pwm.dev_attr.attr,
        &sensor_dev_attr_temp1_auto_point1_temp.dev_attr.attr,
        &sensor_dev_attr_temp1_auto_point1_temp_hyst.dev_attr.attr,
        NULL
};
static const struct attribute_group temp1_auto_point_sensor_group = {
        .attrs = temp1_auto_point_sensor_attrs,
};

#define N_ATTR_GROUPS   3

static const struct hwmon_channel_info * const nouveau_info[] = {
        HWMON_CHANNEL_INFO(chip,
                           HWMON_C_UPDATE_INTERVAL),
        HWMON_CHANNEL_INFO(temp,
                           HWMON_T_INPUT |
                           HWMON_T_MAX | HWMON_T_MAX_HYST |
                           HWMON_T_CRIT | HWMON_T_CRIT_HYST |
                           HWMON_T_EMERGENCY | HWMON_T_EMERGENCY_HYST),
        HWMON_CHANNEL_INFO(fan,
                           HWMON_F_INPUT),
        HWMON_CHANNEL_INFO(in,
                           HWMON_I_INPUT |
                           HWMON_I_MIN | HWMON_I_MAX |
                           HWMON_I_LABEL),
        HWMON_CHANNEL_INFO(pwm,
                           HWMON_PWM_INPUT | HWMON_PWM_ENABLE),
        HWMON_CHANNEL_INFO(power,
                           HWMON_P_INPUT | HWMON_P_CAP_MAX | HWMON_P_CRIT),
        NULL
};

static umode_t
nouveau_chip_is_visible(const void *data, u32 attr, int channel)
{
        switch (attr) {
        case hwmon_chip_update_interval:
                return 0444;
        default:
                return 0;
        }
}

static umode_t
nouveau_power_is_visible(const void *data, u32 attr, int channel)
{
        struct nouveau_drm *drm = nouveau_drm((struct drm_device *)data);
        struct nvkm_iccsense *iccsense = nvxx_iccsense(drm);

        if (!iccsense || !iccsense->data_valid || list_empty(&iccsense->rails))
                return 0;

        switch (attr) {
        case hwmon_power_input:
                return 0444;
        case hwmon_power_max:
                if (iccsense->power_w_max)
                        return 0444;
                return 0;
        case hwmon_power_crit:
                if (iccsense->power_w_crit)
                        return 0444;
                return 0;
        default:
                return 0;
        }
}

static umode_t
nouveau_temp_is_visible(const void *data, u32 attr, int channel)
{
        struct nouveau_drm *drm = nouveau_drm((struct drm_device *)data);
        struct nvkm_therm *therm = nvxx_therm(drm);

        if (!therm || !therm->attr_get || nvkm_therm_temp_get(therm) < 0)
                return 0;

        switch (attr) {
        case hwmon_temp_input:
                return 0444;
        case hwmon_temp_max:
        case hwmon_temp_max_hyst:
        case hwmon_temp_crit:
        case hwmon_temp_crit_hyst:
        case hwmon_temp_emergency:
        case hwmon_temp_emergency_hyst:
                return 0644;
        default:
                return 0;
        }
}

static umode_t
nouveau_pwm_is_visible(const void *data, u32 attr, int channel)
{
        struct nouveau_drm *drm = nouveau_drm((struct drm_device *)data);
        struct nvkm_therm *therm = nvxx_therm(drm);

        if (!therm || !therm->attr_get || !therm->fan_get ||
            therm->fan_get(therm) < 0)
                return 0;

        switch (attr) {
        case hwmon_pwm_enable:
        case hwmon_pwm_input:
                return 0644;
        default:
                return 0;
        }
}

static umode_t
nouveau_input_is_visible(const void *data, u32 attr, int channel)
{
        struct nouveau_drm *drm = nouveau_drm((struct drm_device *)data);
        struct nvkm_volt *volt = nvxx_volt(drm);

        if (!volt || nvkm_volt_get(volt) < 0)
                return 0;

        switch (attr) {
        case hwmon_in_input:
        case hwmon_in_label:
        case hwmon_in_min:
        case hwmon_in_max:
                return 0444;
        default:
                return 0;
        }
}

static umode_t
nouveau_fan_is_visible(const void *data, u32 attr, int channel)
{
        struct nouveau_drm *drm = nouveau_drm((struct drm_device *)data);
        struct nvkm_therm *therm = nvxx_therm(drm);

        if (!therm || !therm->attr_get || nvkm_therm_fan_sense(therm) < 0)
                return 0;

        switch (attr) {
        case hwmon_fan_input:
                return 0444;
        default:
                return 0;
        }
}

static int
nouveau_chip_read(struct device *dev, u32 attr, int channel, long *val)
{
        switch (attr) {
        case hwmon_chip_update_interval:
                *val = 1000;
                break;
        default:
                return -EOPNOTSUPP;
        }

        return 0;
}

static int
nouveau_temp_read(struct device *dev, u32 attr, int channel, long *val)
{
        struct drm_device *drm_dev = dev_get_drvdata(dev);
        struct nouveau_drm *drm = nouveau_drm(drm_dev);
        struct nvkm_therm *therm = nvxx_therm(drm);
        int ret;

        if (!therm || !therm->attr_get)
                return -EOPNOTSUPP;

        switch (attr) {
        case hwmon_temp_input:
                if (drm_dev->switch_power_state != DRM_SWITCH_POWER_ON)
                        return -EINVAL;
                ret = nvkm_therm_temp_get(therm);
                *val = ret < 0 ? ret : (ret * 1000);
                break;
        case hwmon_temp_max:
                *val = therm->attr_get(therm, NVKM_THERM_ATTR_THRS_DOWN_CLK)
                                        * 1000;
                break;
        case hwmon_temp_max_hyst:
                *val = therm->attr_get(therm, NVKM_THERM_ATTR_THRS_DOWN_CLK_HYST)
                                        * 1000;
                break;
        case hwmon_temp_crit:
                *val = therm->attr_get(therm, NVKM_THERM_ATTR_THRS_CRITICAL)
                                        * 1000;
                break;
        case hwmon_temp_crit_hyst:
                *val = therm->attr_get(therm, NVKM_THERM_ATTR_THRS_CRITICAL_HYST)
                                        * 1000;
                break;
        case hwmon_temp_emergency:
                *val = therm->attr_get(therm, NVKM_THERM_ATTR_THRS_SHUTDOWN)
                                        * 1000;
                break;
        case hwmon_temp_emergency_hyst:
                *val = therm->attr_get(therm, NVKM_THERM_ATTR_THRS_SHUTDOWN_HYST)
                                        * 1000;
                break;
        default:
                return -EOPNOTSUPP;
        }

        return 0;
}

static int
nouveau_fan_read(struct device *dev, u32 attr, int channel, long *val)
{
        struct drm_device *drm_dev = dev_get_drvdata(dev);
        struct nouveau_drm *drm = nouveau_drm(drm_dev);
        struct nvkm_therm *therm = nvxx_therm(drm);

        if (!therm)
                return -EOPNOTSUPP;

        switch (attr) {
        case hwmon_fan_input:
                if (drm_dev->switch_power_state != DRM_SWITCH_POWER_ON)
                        return -EINVAL;
                *val = nvkm_therm_fan_sense(therm);
                break;
        default:
                return -EOPNOTSUPP;
        }

        return 0;
}

static int
nouveau_in_read(struct device *dev, u32 attr, int channel, long *val)
{
        struct drm_device *drm_dev = dev_get_drvdata(dev);
        struct nouveau_drm *drm = nouveau_drm(drm_dev);
        struct nvkm_volt *volt = nvxx_volt(drm);
        int ret;

        if (!volt)
                return -EOPNOTSUPP;

        switch (attr) {
        case hwmon_in_input:
                if (drm_dev->switch_power_state != DRM_SWITCH_POWER_ON)
                        return -EINVAL;
                ret = nvkm_volt_get(volt);
                *val = ret < 0 ? ret : (ret / 1000);
                break;
        case hwmon_in_min:
                *val = volt->min_uv > 0 ? (volt->min_uv / 1000) : -ENODEV;
                break;
        case hwmon_in_max:
                *val = volt->max_uv > 0 ? (volt->max_uv / 1000) : -ENODEV;
                break;
        default:
                return -EOPNOTSUPP;
        }

        return 0;
}

static int
nouveau_pwm_read(struct device *dev, u32 attr, int channel, long *val)
{
        struct drm_device *drm_dev = dev_get_drvdata(dev);
        struct nouveau_drm *drm = nouveau_drm(drm_dev);
        struct nvkm_therm *therm = nvxx_therm(drm);

        if (!therm || !therm->attr_get || !therm->fan_get)
                return -EOPNOTSUPP;

        switch (attr) {
        case hwmon_pwm_enable:
                *val = therm->attr_get(therm, NVKM_THERM_ATTR_FAN_MODE);
                break;
        case hwmon_pwm_input:
                if (drm_dev->switch_power_state != DRM_SWITCH_POWER_ON)
                        return -EINVAL;
                *val = therm->fan_get(therm);
                break;
        default:
                return -EOPNOTSUPP;
        }

        return 0;
}

static int
nouveau_power_read(struct device *dev, u32 attr, int channel, long *val)
{
        struct drm_device *drm_dev = dev_get_drvdata(dev);
        struct nouveau_drm *drm = nouveau_drm(drm_dev);
        struct nvkm_iccsense *iccsense = nvxx_iccsense(drm);

        if (!iccsense)
                return -EOPNOTSUPP;

        switch (attr) {
        case hwmon_power_input:
                if (drm_dev->switch_power_state != DRM_SWITCH_POWER_ON)
                        return -EINVAL;
                *val = nvkm_iccsense_read_all(iccsense);
                break;
        case hwmon_power_max:
                *val = iccsense->power_w_max;
                break;
        case hwmon_power_crit:
                *val = iccsense->power_w_crit;
                break;
        default:
                return -EOPNOTSUPP;
        }

        return 0;
}

static int
nouveau_temp_write(struct device *dev, u32 attr, int channel, long val)
{
        struct drm_device *drm_dev = dev_get_drvdata(dev);
        struct nouveau_drm *drm = nouveau_drm(drm_dev);
        struct nvkm_therm *therm = nvxx_therm(drm);

        if (!therm || !therm->attr_set)
                return -EOPNOTSUPP;

        switch (attr) {
        case hwmon_temp_max:
                return therm->attr_set(therm, NVKM_THERM_ATTR_THRS_DOWN_CLK,
                                        val / 1000);
        case hwmon_temp_max_hyst:
                return therm->attr_set(therm, NVKM_THERM_ATTR_THRS_DOWN_CLK_HYST,
                                        val / 1000);
        case hwmon_temp_crit:
                return therm->attr_set(therm, NVKM_THERM_ATTR_THRS_CRITICAL,
                                        val / 1000);
        case hwmon_temp_crit_hyst:
                return therm->attr_set(therm, NVKM_THERM_ATTR_THRS_CRITICAL_HYST,
                                        val / 1000);
        case hwmon_temp_emergency:
                return therm->attr_set(therm, NVKM_THERM_ATTR_THRS_SHUTDOWN,
                                        val / 1000);
        case hwmon_temp_emergency_hyst:
                return therm->attr_set(therm, NVKM_THERM_ATTR_THRS_SHUTDOWN_HYST,
                                        val / 1000);
        default:
                return -EOPNOTSUPP;
        }
}

static int
nouveau_pwm_write(struct device *dev, u32 attr, int channel, long val)
{
        struct drm_device *drm_dev = dev_get_drvdata(dev);
        struct nouveau_drm *drm = nouveau_drm(drm_dev);
        struct nvkm_therm *therm = nvxx_therm(drm);

        if (!therm || !therm->attr_set)
                return -EOPNOTSUPP;

        switch (attr) {
        case hwmon_pwm_input:
                return therm->fan_set(therm, val);
        case hwmon_pwm_enable:
                return therm->attr_set(therm, NVKM_THERM_ATTR_FAN_MODE, val);
        default:
                return -EOPNOTSUPP;
        }
}

static umode_t
nouveau_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr,
                        int channel)
{
        switch (type) {
        case hwmon_chip:
                return nouveau_chip_is_visible(data, attr, channel);
        case hwmon_temp:
                return nouveau_temp_is_visible(data, attr, channel);
        case hwmon_fan:
                return nouveau_fan_is_visible(data, attr, channel);
        case hwmon_in:
                return nouveau_input_is_visible(data, attr, channel);
        case hwmon_pwm:
                return nouveau_pwm_is_visible(data, attr, channel);
        case hwmon_power:
                return nouveau_power_is_visible(data, attr, channel);
        default:
                return 0;
        }
}

static const char input_label[] = "GPU core";

static int
nouveau_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr,
                    int channel, const char **buf)
{
        if (type == hwmon_in && attr == hwmon_in_label) {
                *buf = input_label;
                return 0;
        }

        return -EOPNOTSUPP;
}

static int
nouveau_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
                                                        int channel, long *val)
{
        switch (type) {
        case hwmon_chip:
                return nouveau_chip_read(dev, attr, channel, val);
        case hwmon_temp:
                return nouveau_temp_read(dev, attr, channel, val);
        case hwmon_fan:
                return nouveau_fan_read(dev, attr, channel, val);
        case hwmon_in:
                return nouveau_in_read(dev, attr, channel, val);
        case hwmon_pwm:
                return nouveau_pwm_read(dev, attr, channel, val);
        case hwmon_power:
                return nouveau_power_read(dev, attr, channel, val);
        default:
                return -EOPNOTSUPP;
        }
}

static int
nouveau_write(struct device *dev, enum hwmon_sensor_types type, u32 attr,
                                                        int channel, long val)
{
        switch (type) {
        case hwmon_temp:
                return nouveau_temp_write(dev, attr, channel, val);
        case hwmon_pwm:
                return nouveau_pwm_write(dev, attr, channel, val);
        default:
                return -EOPNOTSUPP;
        }
}

static const struct hwmon_ops nouveau_hwmon_ops = {
        .is_visible = nouveau_is_visible,
        .read = nouveau_read,
        .read_string = nouveau_read_string,
        .write = nouveau_write,
};

static const struct hwmon_chip_info nouveau_chip_info = {
        .ops = &nouveau_hwmon_ops,
        .info = nouveau_info,
};
#endif

int
nouveau_hwmon_init(struct drm_device *dev)
{
#if defined(CONFIG_HWMON) || (defined(MODULE) && defined(CONFIG_HWMON_MODULE))
        struct nouveau_drm *drm = nouveau_drm(dev);
        struct nvkm_iccsense *iccsense = nvxx_iccsense(drm);
        struct nvkm_therm *therm = nvxx_therm(drm);
        struct nvkm_volt *volt = nvxx_volt(drm);
        const struct attribute_group *special_groups[N_ATTR_GROUPS];
        struct nouveau_hwmon *hwmon;
        struct device *hwmon_dev;
        int ret = 0;
        int i = 0;

        if (!iccsense && !therm && !volt) {
                NV_DEBUG(drm, "Skipping hwmon registration\n");
                return 0;
        }

        hwmon = drm->hwmon = kzalloc_obj(*hwmon);
        if (!hwmon)
                return -ENOMEM;
        hwmon->dev = dev;

        if (therm && therm->attr_get && therm->attr_set) {
                if (nvkm_therm_temp_get(therm) >= 0)
                        special_groups[i++] = &temp1_auto_point_sensor_group;
                if (therm->fan_get && therm->fan_get(therm) >= 0)
                        special_groups[i++] = &pwm_fan_sensor_group;
        }

        special_groups[i] = NULL;
        hwmon_dev = hwmon_device_register_with_info(dev->dev, "nouveau", dev,
                                                        &nouveau_chip_info,
                                                        special_groups);
        if (IS_ERR(hwmon_dev)) {
                ret = PTR_ERR(hwmon_dev);
                NV_ERROR(drm, "Unable to register hwmon device: %d\n", ret);
                return ret;
        }

        hwmon->hwmon = hwmon_dev;
        return 0;
#else
        return 0;
#endif
}

void
nouveau_hwmon_fini(struct drm_device *dev)
{
#if defined(CONFIG_HWMON) || (defined(MODULE) && defined(CONFIG_HWMON_MODULE))
        struct nouveau_hwmon *hwmon = nouveau_hwmon(dev);

        if (!hwmon)
                return;

        if (hwmon->hwmon)
                hwmon_device_unregister(hwmon->hwmon);

        nouveau_drm(dev)->hwmon = NULL;
        kfree(hwmon);
#endif
}