drm/radeon/kms: add support for internal thermal sensors (v3)
authorAlex Deucher <alexdeucher@gmail.com>
Fri, 2 Jul 2010 16:58:16 +0000 (12:58 -0400)
committerDave Airlie <airlied@redhat.com>
Mon, 2 Aug 2010 00:00:00 +0000 (10:00 +1000)
rv6xx/rv7xx/evergreen families supported; older asics did
not have an internal thermal sensor.

Note, not all oems use the internal thermal sensor, so it's
only exposed in cases where it is used.

Note also, that most laptops use an oem specific ACPI solution for
GPU thermal information rather than using the internal thermal
sensor directly.

v2: export millidegrees celsius, use hwmon device properly.
v3: fix Kconfig

Signed-off-by: Alex Deucher <alexdeucher@gmail.com>
Signed-off-by: Dave Airlie <airlied@redhat.com>
drivers/gpu/drm/Kconfig
drivers/gpu/drm/radeon/evergreen.c
drivers/gpu/drm/radeon/evergreend.h
drivers/gpu/drm/radeon/r600.c
drivers/gpu/drm/radeon/r600d.h
drivers/gpu/drm/radeon/radeon.h
drivers/gpu/drm/radeon/radeon_atombios.c
drivers/gpu/drm/radeon/radeon_pm.c
drivers/gpu/drm/radeon/rv770.c
drivers/gpu/drm/radeon/rv770d.h

index 5b7a1a4692a0a8aa7b8953fb1a62ab1a99e21cd0..4cab0c6397e34654086f57433e1de471d1d87b30 100644 (file)
@@ -61,6 +61,7 @@ config DRM_RADEON
         select DRM_KMS_HELPER
         select DRM_TTM
        select POWER_SUPPLY
+       select HWMON
        help
          Choose this option if you have an ATI Radeon graphics card.  There
          are both PCI and AGP versions.  You don't need to choose this to
index 057192acdd36600abc91dc73d0850a541b65fd1c..1b7da39cc5877be0b729559541e2ab1b89cd08c3 100644 (file)
 static void evergreen_gpu_init(struct radeon_device *rdev);
 void evergreen_fini(struct radeon_device *rdev);
 
+/* get temperature in millidegrees */
+u32 evergreen_get_temp(struct radeon_device *rdev)
+{
+       u32 temp = (RREG32(CG_MULT_THERMAL_STATUS) & ASIC_T_MASK) >>
+               ASIC_T_SHIFT;
+       u32 actual_temp = 0;
+
+       if ((temp >> 10) & 1)
+               actual_temp = 0;
+       else if ((temp >> 9) & 1)
+               actual_temp = 255;
+       else
+               actual_temp = (temp >> 1) & 0xff;
+
+       return actual_temp * 1000;
+}
+
 void evergreen_pm_misc(struct radeon_device *rdev)
 {
        int req_ps_idx = rdev->pm.requested_power_state_index;
index a1cd621780e21bc5fdd7251c397dfc362d3c5ec3..9b7532dd30f754d4ad2e3e02915006ae823538f2 100644 (file)
 #define                SE_DB_BUSY                                      (1 << 30)
 #define                SE_CB_BUSY                                      (1 << 31)
 
+#define        CG_MULT_THERMAL_STATUS                          0x740
+#define                ASIC_T(x)                               ((x) << 16)
+#define                ASIC_T_MASK                             0x7FF0000
+#define                ASIC_T_SHIFT                            16
+
 #define        HDP_HOST_PATH_CNTL                              0x2C00
 #define        HDP_NONSURFACE_BASE                             0x2C04
 #define        HDP_NONSURFACE_INFO                             0x2C08
index a73a6e17588d06c613c78ef8631ad4b2fc4f2c07..15fe6c2140343d520219c9c49c237c1f57965cd9 100644 (file)
@@ -92,6 +92,21 @@ void r600_gpu_init(struct radeon_device *rdev);
 void r600_fini(struct radeon_device *rdev);
 void r600_irq_disable(struct radeon_device *rdev);
 
+/* get temperature in millidegrees */
+u32 rv6xx_get_temp(struct radeon_device *rdev)
+{
+       u32 temp = (RREG32(CG_THERMAL_STATUS) & ASIC_T_MASK) >>
+               ASIC_T_SHIFT;
+       u32 actual_temp = 0;
+
+       if ((temp >> 7) & 1)
+               actual_temp = 0;
+       else
+               actual_temp = (temp >> 1) & 0xff;
+
+       return actual_temp * 1000;
+}
+
 void r600_pm_get_dynpm_state(struct radeon_device *rdev)
 {
        int i;
index 59c1f8793e608df971466a9545ccc32c81e529fd..23205f032872d2d7ff9e8ad6adebc19e2a590411 100644 (file)
 #define        GRBM_SOFT_RESET                                 0x8020
 #define                SOFT_RESET_CP                                   (1<<0)
 
+#define        CG_THERMAL_STATUS                               0x7F4
+#define                ASIC_T(x)                               ((x) << 0)
+#define                ASIC_T_MASK                             0x1FF
+#define                ASIC_T_SHIFT                            0
+
 #define        HDP_HOST_PATH_CNTL                              0x2C00
 #define        HDP_NONSURFACE_BASE                             0x2C04
 #define        HDP_NONSURFACE_INFO                             0x2C08
index a5c1a3e9dd3976ad9569d4de7a78a9e8a3db82f2..d4d776d2f1e0a42bad7320d4ad61b870c4db63c8 100644 (file)
@@ -178,6 +178,9 @@ void radeon_combios_get_power_modes(struct radeon_device *rdev);
 void radeon_atombios_get_power_modes(struct radeon_device *rdev);
 void radeon_atom_set_voltage(struct radeon_device *rdev, u16 level);
 void rs690_pm_info(struct radeon_device *rdev);
+extern u32 rv6xx_get_temp(struct radeon_device *rdev);
+extern u32 rv770_get_temp(struct radeon_device *rdev);
+extern u32 evergreen_get_temp(struct radeon_device *rdev);
 
 /*
  * Fences.
@@ -670,6 +673,13 @@ struct radeon_pm_profile {
        int dpms_on_cm_idx;
 };
 
+enum radeon_int_thermal_type {
+       THERMAL_TYPE_NONE,
+       THERMAL_TYPE_RV6XX,
+       THERMAL_TYPE_RV770,
+       THERMAL_TYPE_EVERGREEN,
+};
+
 struct radeon_voltage {
        enum radeon_voltage_type type;
        /* gpio voltage */
@@ -765,6 +775,9 @@ struct radeon_pm {
        enum radeon_pm_profile_type profile;
        int                     profile_index;
        struct radeon_pm_profile profiles[PM_PROFILE_MAX];
+       /* internal thermal controller on rv6xx+ */
+       enum radeon_int_thermal_type int_thermal_type;
+       struct device           *int_hwmon_dev;
 };
 
 
index 99bd8a9c56b38f8431510bf3cde781345510feb1..5dd86b95b992f2e9bb5b0aa78978a52d57dc4b37 100644 (file)
@@ -1773,14 +1773,22 @@ void radeon_atombios_get_power_modes(struct radeon_device *rdev)
                        }
 
                        /* add the i2c bus for thermal/fan chip */
-                       /* no support for internal controller yet */
                        if (controller->ucType > 0) {
-                               if ((controller->ucType == ATOM_PP_THERMALCONTROLLER_RV6xx) ||
-                                   (controller->ucType == ATOM_PP_THERMALCONTROLLER_RV770) ||
-                                   (controller->ucType == ATOM_PP_THERMALCONTROLLER_EVERGREEN)) {
+                               if (controller->ucType == ATOM_PP_THERMALCONTROLLER_RV6xx) {
                                        DRM_INFO("Internal thermal controller %s fan control\n",
                                                 (controller->ucFanParameters &
                                                  ATOM_PP_FANPARAMETERS_NOFAN) ? "without" : "with");
+                                       rdev->pm.int_thermal_type = THERMAL_TYPE_RV6XX;
+                               } else if (controller->ucType == ATOM_PP_THERMALCONTROLLER_RV770) {
+                                       DRM_INFO("Internal thermal controller %s fan control\n",
+                                                (controller->ucFanParameters &
+                                                 ATOM_PP_FANPARAMETERS_NOFAN) ? "without" : "with");
+                                       rdev->pm.int_thermal_type = THERMAL_TYPE_RV770;
+                               } else if (controller->ucType == ATOM_PP_THERMALCONTROLLER_EVERGREEN) {
+                                       DRM_INFO("Internal thermal controller %s fan control\n",
+                                                (controller->ucFanParameters &
+                                                 ATOM_PP_FANPARAMETERS_NOFAN) ? "without" : "with");
+                                       rdev->pm.int_thermal_type = THERMAL_TYPE_EVERGREEN;
                                } else if ((controller->ucType ==
                                            ATOM_PP_THERMALCONTROLLER_EXTERNAL_GPIO) ||
                                           (controller->ucType ==
index 115d26b762cc2b3db9301c86e66d97505f6fd291..ed66062ae9d0044601e843a2caa937559d361d78 100644 (file)
@@ -27,6 +27,8 @@
 #include <linux/acpi.h>
 #endif
 #include <linux/power_supply.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
 
 #define RADEON_IDLE_LOOP_MS 100
 #define RADEON_RECLOCK_DELAY_MS 200
@@ -423,6 +425,82 @@ fail:
 static DEVICE_ATTR(power_profile, S_IRUGO | S_IWUSR, radeon_get_pm_profile, radeon_set_pm_profile);
 static DEVICE_ATTR(power_method, S_IRUGO | S_IWUSR, radeon_get_pm_method, radeon_set_pm_method);
 
+static ssize_t radeon_hwmon_show_temp(struct device *dev,
+                                     struct device_attribute *attr,
+                                     char *buf)
+{
+       struct drm_device *ddev = pci_get_drvdata(to_pci_dev(dev));
+       struct radeon_device *rdev = ddev->dev_private;
+       u32 temp;
+
+       switch (rdev->pm.int_thermal_type) {
+       case THERMAL_TYPE_RV6XX:
+               temp = rv6xx_get_temp(rdev);
+               break;
+       case THERMAL_TYPE_RV770:
+               temp = rv770_get_temp(rdev);
+               break;
+       case THERMAL_TYPE_EVERGREEN:
+               temp = evergreen_get_temp(rdev);
+               break;
+       default:
+               temp = 0;
+               break;
+       }
+
+       return snprintf(buf, PAGE_SIZE, "%d\n", temp);
+}
+
+static ssize_t radeon_hwmon_show_name(struct device *dev,
+                                     struct device_attribute *attr,
+                                     char *buf)
+{
+       return sprintf(buf, "radeon\n");
+}
+
+static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, radeon_hwmon_show_temp, NULL, 0);
+static SENSOR_DEVICE_ATTR(name, S_IRUGO, radeon_hwmon_show_name, NULL, 0);
+
+static struct attribute *hwmon_attributes[] = {
+       &sensor_dev_attr_temp1_input.dev_attr.attr,
+       &sensor_dev_attr_name.dev_attr.attr,
+       NULL
+};
+
+static const struct attribute_group hwmon_attrgroup = {
+       .attrs = hwmon_attributes,
+};
+
+static void radeon_hwmon_init(struct radeon_device *rdev)
+{
+       int err;
+
+       rdev->pm.int_hwmon_dev = NULL;
+
+       switch (rdev->pm.int_thermal_type) {
+       case THERMAL_TYPE_RV6XX:
+       case THERMAL_TYPE_RV770:
+       case THERMAL_TYPE_EVERGREEN:
+               rdev->pm.int_hwmon_dev = hwmon_device_register(rdev->dev);
+               dev_set_drvdata(rdev->pm.int_hwmon_dev, rdev->ddev);
+               err = sysfs_create_group(&rdev->pm.int_hwmon_dev->kobj,
+                                        &hwmon_attrgroup);
+               if (err)
+                       DRM_ERROR("Unable to create hwmon sysfs file: %d\n", err);
+               break;
+       default:
+               break;
+       }
+}
+
+static void radeon_hwmon_fini(struct radeon_device *rdev)
+{
+       if (rdev->pm.int_hwmon_dev) {
+               sysfs_remove_group(&rdev->pm.int_hwmon_dev->kobj, &hwmon_attrgroup);
+               hwmon_device_unregister(rdev->pm.int_hwmon_dev);
+       }
+}
+
 void radeon_pm_suspend(struct radeon_device *rdev)
 {
        bool flush_wq = false;
@@ -470,6 +548,7 @@ int radeon_pm_init(struct radeon_device *rdev)
        rdev->pm.dynpm_can_downclock = true;
        rdev->pm.current_sclk = rdev->clock.default_sclk;
        rdev->pm.current_mclk = rdev->clock.default_mclk;
+       rdev->pm.int_thermal_type = THERMAL_TYPE_NONE;
 
        if (rdev->bios) {
                if (rdev->is_atom_bios)
@@ -480,6 +559,8 @@ int radeon_pm_init(struct radeon_device *rdev)
                radeon_pm_init_profile(rdev);
        }
 
+       /* set up the internal thermal sensor if applicable */
+       radeon_hwmon_init(rdev);
        if (rdev->pm.num_power_states > 1) {
                /* where's the best place to put these? */
                ret = device_create_file(rdev->dev, &dev_attr_power_profile);
@@ -535,6 +616,7 @@ void radeon_pm_fini(struct radeon_device *rdev)
 #endif
        }
 
+       radeon_hwmon_fini(rdev);
        if (rdev->pm.i2c_bus)
                radeon_i2c_destroy(rdev->pm.i2c_bus);
 }
index 6a7bf109197157c395ffe3053477a43505d00612..836c15ab84d1741eb13eec77b4819983c071c59f 100644 (file)
 static void rv770_gpu_init(struct radeon_device *rdev);
 void rv770_fini(struct radeon_device *rdev);
 
+/* get temperature in millidegrees */
+u32 rv770_get_temp(struct radeon_device *rdev)
+{
+       u32 temp = (RREG32(CG_MULT_THERMAL_STATUS) & ASIC_T_MASK) >>
+               ASIC_T_SHIFT;
+       u32 actual_temp = 0;
+
+       if ((temp >> 9) & 1)
+               actual_temp = 0;
+       else
+               actual_temp = (temp >> 1) & 0xff;
+
+       return actual_temp * 1000;
+}
+
 void rv770_pm_misc(struct radeon_device *rdev)
 {
        int req_ps_idx = rdev->pm.requested_power_state_index;
index 9506f8cb99e0552a295bef5bd60dfba49bfaaee0..fd733f268e3d605b1deaad2da851c954ea183fbb 100644 (file)
 #define                GUI_ACTIVE                                      (1<<31)
 #define        GRBM_STATUS2                                    0x8014
 
+#define        CG_MULT_THERMAL_STATUS                          0x740
+#define                ASIC_T(x)                               ((x) << 16)
+#define                ASIC_T_MASK                             0x3FF0000
+#define                ASIC_T_SHIFT                            16
+
 #define        HDP_HOST_PATH_CNTL                              0x2C00
 #define        HDP_NONSURFACE_BASE                             0x2C04
 #define        HDP_NONSURFACE_INFO                             0x2C08