| /* |
| * 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 sprintf(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 sprintf(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(sizeof(*hwmon), GFP_KERNEL); |
| 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 |
| } |