drm/radeon/dpm: add smc fan control for SI (v2)
authorAlex Deucher <alexander.deucher@amd.com>
Mon, 15 Sep 2014 01:14:14 +0000 (21:14 -0400)
committerAlex Deucher <alexander.deucher@amd.com>
Thu, 20 Nov 2014 18:00:09 +0000 (13:00 -0500)
Enable smc fan control for SI boards.  Should
reduce the fan noise on systems with a higher
default fan profile.

v2: disable by default, add rpm controls

bug:
https://bugs.freedesktop.org/show_bug.cgi?id=73338

Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
drivers/gpu/drm/radeon/ppsmc.h
drivers/gpu/drm/radeon/r600_dpm.h
drivers/gpu/drm/radeon/si_dpm.c
drivers/gpu/drm/radeon/si_dpm.h
drivers/gpu/drm/radeon/sid.h
drivers/gpu/drm/radeon/sislands_smc.h

index 11c0e4d..0c4eaa6 100644 (file)
@@ -56,6 +56,9 @@
 #define PPSMC_STATEFLAG_DEEPSLEEP_THROTTLE 0x20
 #define PPSMC_STATEFLAG_DEEPSLEEP_BYPASS   0x40
 
+#define FDO_MODE_HARDWARE 0
+#define FDO_MODE_PIECE_WISE_LINEAR 1
+
 #define PPSMC_Result_OK             ((uint8_t)0x01)
 #define PPSMC_Result_Failed         ((uint8_t)0xFF)
 
@@ -79,6 +82,8 @@ typedef uint8_t PPSMC_Result;
 #define PPSMC_MSG_DisableCac                ((uint8_t)0x54)
 #define PPSMC_TDPClampingActive             ((uint8_t)0x59)
 #define PPSMC_TDPClampingInactive           ((uint8_t)0x5A)
+#define PPSMC_StartFanControl               ((uint8_t)0x5B)
+#define PPSMC_StopFanControl                ((uint8_t)0x5C)
 #define PPSMC_MSG_NoDisplay                 ((uint8_t)0x5D)
 #define PPSMC_MSG_HasDisplay                ((uint8_t)0x5E)
 #define PPSMC_MSG_UVDPowerOFF               ((uint8_t)0x60)
index 46b9d2a..bd499d7 100644 (file)
@@ -96,6 +96,9 @@
 #define R600_TEMP_RANGE_MIN (90 * 1000)
 #define R600_TEMP_RANGE_MAX (120 * 1000)
 
+#define FDO_PWM_MODE_STATIC  1
+#define FDO_PWM_MODE_STATIC_RPM 5
+
 enum r600_power_level {
        R600_POWER_LEVEL_LOW = 0,
        R600_POWER_LEVEL_MEDIUM = 1,
index b59e1d6..cf4c420 100644 (file)
@@ -3398,6 +3398,15 @@ static int si_process_firmware_header(struct radeon_device *rdev)
 
        ret = si_read_smc_sram_dword(rdev,
                                     SISLANDS_SMC_FIRMWARE_HEADER_LOCATION +
+                                    SISLANDS_SMC_FIRMWARE_HEADER_fanTable,
+                                    &tmp, si_pi->sram_end);
+       if (ret)
+               return ret;
+
+       si_pi->fan_table_start = tmp;
+
+       ret = si_read_smc_sram_dword(rdev,
+                                    SISLANDS_SMC_FIRMWARE_HEADER_LOCATION +
                                     SISLANDS_SMC_FIRMWARE_HEADER_mcArbDramAutoRefreshTable,
                                     &tmp, si_pi->sram_end);
        if (ret)
@@ -5825,20 +5834,20 @@ static int si_thermal_enable_alert(struct radeon_device *rdev,
        if (enable) {
                PPSMC_Result result;
 
-               thermal_int |= THERM_INT_MASK_HIGH | THERM_INT_MASK_LOW;
-               rdev->irq.dpm_thermal = true;
+               thermal_int &= ~(THERM_INT_MASK_HIGH | THERM_INT_MASK_LOW);
+               WREG32(CG_THERMAL_INT, thermal_int);
+               rdev->irq.dpm_thermal = false;
                result = si_send_msg_to_smc(rdev, PPSMC_MSG_EnableThermalInterrupt);
                if (result != PPSMC_Result_OK) {
                        DRM_DEBUG_KMS("Could not enable thermal interrupts.\n");
                        return -EINVAL;
                }
        } else {
-               thermal_int &= ~(THERM_INT_MASK_HIGH | THERM_INT_MASK_LOW);
-               rdev->irq.dpm_thermal = false;
+               thermal_int |= THERM_INT_MASK_HIGH | THERM_INT_MASK_LOW;
+               WREG32(CG_THERMAL_INT, thermal_int);
+               rdev->irq.dpm_thermal = true;
        }
 
-       WREG32(CG_THERMAL_INT, thermal_int);
-
        return 0;
 }
 
@@ -5867,6 +5876,309 @@ static int si_thermal_set_temperature_range(struct radeon_device *rdev,
        return 0;
 }
 
+static void si_fan_ctrl_set_static_mode(struct radeon_device *rdev, u32 mode)
+{
+       struct si_power_info *si_pi = si_get_pi(rdev);
+       u32 tmp;
+
+       if (si_pi->fan_ctrl_is_in_default_mode) {
+               tmp = (RREG32(CG_FDO_CTRL2) & FDO_PWM_MODE_MASK) >> FDO_PWM_MODE_SHIFT;
+               si_pi->fan_ctrl_default_mode = tmp;
+               tmp = (RREG32(CG_FDO_CTRL2) & TMIN_MASK) >> TMIN_SHIFT;
+               si_pi->t_min = tmp;
+               si_pi->fan_ctrl_is_in_default_mode = false;
+       }
+
+       tmp = RREG32(CG_FDO_CTRL2) & ~TMIN_MASK;
+       tmp |= TMIN(0);
+       WREG32(CG_FDO_CTRL2, tmp);
+
+       tmp = RREG32(CG_FDO_CTRL2) & FDO_PWM_MODE_MASK;
+       tmp |= FDO_PWM_MODE(mode);
+       WREG32(CG_FDO_CTRL2, tmp);
+}
+
+static int si_thermal_setup_fan_table(struct radeon_device *rdev)
+{
+       struct si_power_info *si_pi = si_get_pi(rdev);
+       PP_SIslands_FanTable fan_table = { FDO_MODE_HARDWARE };
+       u32 duty100;
+       u32 t_diff1, t_diff2, pwm_diff1, pwm_diff2;
+       u16 fdo_min, slope1, slope2;
+       u32 reference_clock, tmp;
+       int ret;
+       u64 tmp64;
+
+       if (!si_pi->fan_table_start) {
+               rdev->pm.dpm.fan.ucode_fan_control = false;
+               return 0;
+       }
+
+       duty100 = (RREG32(CG_FDO_CTRL1) & FMAX_DUTY100_MASK) >> FMAX_DUTY100_SHIFT;
+
+       if (duty100 == 0) {
+               rdev->pm.dpm.fan.ucode_fan_control = false;
+               return 0;
+       }
+
+       tmp64 = (u64)rdev->pm.dpm.fan.pwm_min * duty100;
+       do_div(tmp64, 10000);
+       fdo_min = (u16)tmp64;
+
+       t_diff1 = rdev->pm.dpm.fan.t_med - rdev->pm.dpm.fan.t_min;
+       t_diff2 = rdev->pm.dpm.fan.t_high - rdev->pm.dpm.fan.t_med;
+
+       pwm_diff1 = rdev->pm.dpm.fan.pwm_med - rdev->pm.dpm.fan.pwm_min;
+       pwm_diff2 = rdev->pm.dpm.fan.pwm_high - rdev->pm.dpm.fan.pwm_med;
+
+       slope1 = (u16)((50 + ((16 * duty100 * pwm_diff1) / t_diff1)) / 100);
+       slope2 = (u16)((50 + ((16 * duty100 * pwm_diff2) / t_diff2)) / 100);
+
+       fan_table.slope1 = cpu_to_be16(slope1);
+       fan_table.slope2 = cpu_to_be16(slope2);
+
+       fan_table.fdo_min = cpu_to_be16(fdo_min);
+
+       fan_table.hys_down = cpu_to_be16(rdev->pm.dpm.fan.t_hyst);
+
+       fan_table.hys_up = cpu_to_be16(1);
+
+       fan_table.hys_slope = cpu_to_be16(1);
+
+       fan_table.temp_resp_lim = cpu_to_be16(5);
+
+       reference_clock = radeon_get_xclk(rdev);
+
+       fan_table.refresh_period = cpu_to_be32((rdev->pm.dpm.fan.cycle_delay *
+                                               reference_clock) / 1600);
+
+       fan_table.fdo_max = cpu_to_be16((u16)duty100);
+
+       tmp = (RREG32(CG_MULT_THERMAL_CTRL) & TEMP_SEL_MASK) >> TEMP_SEL_SHIFT;
+       fan_table.temp_src = (uint8_t)tmp;
+
+       ret = si_copy_bytes_to_smc(rdev,
+                                  si_pi->fan_table_start,
+                                  (u8 *)(&fan_table),
+                                  sizeof(fan_table),
+                                  si_pi->sram_end);
+
+       if (ret) {
+               DRM_ERROR("Failed to load fan table to the SMC.");
+               rdev->pm.dpm.fan.ucode_fan_control = false;
+       }
+
+       return 0;
+}
+
+static int si_fan_ctrl_start_smc_fan_control(struct radeon_device *rdev)
+{
+       PPSMC_Result ret;
+
+       ret = si_send_msg_to_smc(rdev, PPSMC_StartFanControl);
+       if (ret == PPSMC_Result_OK)
+               return 0;
+       else
+               return -EINVAL;
+}
+
+static int si_fan_ctrl_stop_smc_fan_control(struct radeon_device *rdev)
+{
+       PPSMC_Result ret;
+
+       ret = si_send_msg_to_smc(rdev, PPSMC_StopFanControl);
+       if (ret == PPSMC_Result_OK)
+               return 0;
+       else
+               return -EINVAL;
+}
+
+#if 0
+static int si_fan_ctrl_get_fan_speed_percent(struct radeon_device *rdev,
+                                            u32 *speed)
+{
+       u32 duty, duty100;
+       u64 tmp64;
+
+       if (rdev->pm.no_fan)
+               return -ENOENT;
+
+       duty100 = (RREG32(CG_FDO_CTRL1) & FMAX_DUTY100_MASK) >> FMAX_DUTY100_SHIFT;
+       duty = (RREG32(CG_THERMAL_STATUS) & FDO_PWM_DUTY_MASK) >> FDO_PWM_DUTY_SHIFT;
+
+       if (duty100 == 0)
+               return -EINVAL;
+
+       tmp64 = (u64)duty * 100;
+       do_div(tmp64, duty100);
+       *speed = (u32)tmp64;
+
+       if (*speed > 100)
+               *speed = 100;
+
+       return 0;
+}
+
+static int si_fan_ctrl_set_fan_speed_percent(struct radeon_device *rdev,
+                                            u32 speed)
+{
+       u32 tmp;
+       u32 duty, duty100;
+       u64 tmp64;
+
+       if (rdev->pm.no_fan)
+               return -ENOENT;
+
+       if (speed > 100)
+               return -EINVAL;
+
+       if (rdev->pm.dpm.fan.ucode_fan_control)
+               si_fan_ctrl_stop_smc_fan_control(rdev);
+
+       duty100 = (RREG32(CG_FDO_CTRL1) & FMAX_DUTY100_MASK) >> FMAX_DUTY100_SHIFT;
+
+       if (duty100 == 0)
+               return -EINVAL;
+
+       tmp64 = (u64)speed * duty100;
+       do_div(tmp64, 100);
+       duty = (u32)tmp64;
+
+       tmp = RREG32(CG_FDO_CTRL0) & ~FDO_STATIC_DUTY_MASK;
+       tmp |= FDO_STATIC_DUTY(duty);
+       WREG32(CG_FDO_CTRL0, tmp);
+
+       si_fan_ctrl_set_static_mode(rdev, FDO_PWM_MODE_STATIC);
+
+       return 0;
+}
+
+static int si_fan_ctrl_get_fan_speed_rpm(struct radeon_device *rdev,
+                                        u32 *speed)
+{
+       u32 tach_period;
+       u32 xclk = radeon_get_xclk(rdev);
+
+       if (rdev->pm.no_fan)
+               return -ENOENT;
+
+       if (rdev->pm.fan_pulses_per_revolution == 0)
+               return -ENOENT;
+
+       tach_period = (RREG32(CG_TACH_STATUS) & TACH_PERIOD_MASK) >> TACH_PERIOD_SHIFT;
+       if (tach_period == 0)
+               return -ENOENT;
+
+       *speed = 60 * xclk * 10000 / tach_period;
+
+       return 0;
+}
+
+static int si_fan_ctrl_set_fan_speed_rpm(struct radeon_device *rdev,
+                                        u32 speed)
+{
+       u32 tach_period, tmp;
+       u32 xclk = radeon_get_xclk(rdev);
+
+       if (rdev->pm.no_fan)
+               return -ENOENT;
+
+       if (rdev->pm.fan_pulses_per_revolution == 0)
+               return -ENOENT;
+
+       if ((speed < rdev->pm.fan_min_rpm) ||
+           (speed > rdev->pm.fan_max_rpm))
+               return -EINVAL;
+
+       if (rdev->pm.dpm.fan.ucode_fan_control)
+               si_fan_ctrl_stop_smc_fan_control(rdev);
+
+       tach_period = 60 * xclk * 10000 / (8 * speed);
+       tmp = RREG32(CG_TACH_CTRL) & ~TARGET_PERIOD_MASK;
+       tmp |= TARGET_PERIOD(tach_period);
+       WREG32(CG_TACH_CTRL, tmp);
+
+       si_fan_ctrl_set_static_mode(rdev, FDO_PWM_MODE_STATIC);
+
+       return 0;
+}
+#endif
+
+static void si_fan_ctrl_set_default_mode(struct radeon_device *rdev)
+{
+       struct si_power_info *si_pi = si_get_pi(rdev);
+       u32 tmp;
+
+       if (!si_pi->fan_ctrl_is_in_default_mode) {
+               tmp = RREG32(CG_FDO_CTRL2) & ~FDO_PWM_MODE_MASK;
+               tmp |= FDO_PWM_MODE(si_pi->fan_ctrl_default_mode);
+               WREG32(CG_FDO_CTRL2, tmp);
+
+               tmp = RREG32(CG_FDO_CTRL2) & TMIN_MASK;
+               tmp |= TMIN(si_pi->t_min);
+               WREG32(CG_FDO_CTRL2, tmp);
+               si_pi->fan_ctrl_is_in_default_mode = true;
+       }
+}
+
+static void si_thermal_start_smc_fan_control(struct radeon_device *rdev)
+{
+       if (rdev->pm.dpm.fan.ucode_fan_control) {
+               si_fan_ctrl_start_smc_fan_control(rdev);
+               si_fan_ctrl_set_static_mode(rdev, FDO_PWM_MODE_STATIC);
+       }
+}
+
+static void si_thermal_initialize(struct radeon_device *rdev)
+{
+       u32 tmp;
+
+       if (rdev->pm.fan_pulses_per_revolution) {
+               tmp = RREG32(CG_TACH_CTRL) & ~EDGE_PER_REV_MASK;
+               tmp |= EDGE_PER_REV(rdev->pm.fan_pulses_per_revolution -1);
+               WREG32(CG_TACH_CTRL, tmp);
+       }
+
+       tmp = RREG32(CG_FDO_CTRL2) & ~TACH_PWM_RESP_RATE_MASK;
+       tmp |= TACH_PWM_RESP_RATE(0x28);
+       WREG32(CG_FDO_CTRL2, tmp);
+}
+
+static int si_thermal_start_thermal_controller(struct radeon_device *rdev)
+{
+       int ret;
+
+       si_thermal_initialize(rdev);
+       ret = si_thermal_set_temperature_range(rdev, R600_TEMP_RANGE_MIN, R600_TEMP_RANGE_MAX);
+       if (ret)
+               return ret;
+       ret = si_thermal_enable_alert(rdev, true);
+       if (ret)
+               return ret;
+       if (rdev->pm.dpm.fan.ucode_fan_control) {
+               ret = si_halt_smc(rdev);
+               if (ret)
+                       return ret;
+               ret = si_thermal_setup_fan_table(rdev);
+               if (ret)
+                       return ret;
+               ret = si_resume_smc(rdev);
+               if (ret)
+                       return ret;
+               si_thermal_start_smc_fan_control(rdev);
+       }
+
+       return 0;
+}
+
+static void si_thermal_stop_thermal_controller(struct radeon_device *rdev)
+{
+       if (!rdev->pm.no_fan) {
+               si_fan_ctrl_set_default_mode(rdev);
+               si_fan_ctrl_stop_smc_fan_control(rdev);
+       }
+}
+
 int si_dpm_enable(struct radeon_device *rdev)
 {
        struct rv7xx_power_info *pi = rv770_get_pi(rdev);
@@ -5979,6 +6291,8 @@ int si_dpm_enable(struct radeon_device *rdev)
 
        si_enable_auto_throttle_source(rdev, RADEON_DPM_AUTO_THROTTLE_SRC_THERMAL, true);
 
+       si_thermal_start_thermal_controller(rdev);
+
        ni_update_current_ps(rdev, boot_ps);
 
        return 0;
@@ -6019,6 +6333,7 @@ void si_dpm_disable(struct radeon_device *rdev)
 
        if (!si_is_smc_running(rdev))
                return;
+       si_thermal_stop_thermal_controller(rdev);
        si_disable_ulv(rdev);
        si_clear_vc(rdev);
        if (pi->thermal_protection)
@@ -6557,6 +6872,9 @@ int si_dpm_init(struct radeon_device *rdev)
                rdev->pm.dpm.dyn_state.max_clock_voltage_on_dc =
                        rdev->pm.dpm.dyn_state.max_clock_voltage_on_ac;
 
+       si_pi->fan_ctrl_is_in_default_mode = true;
+       rdev->pm.dpm.fan.ucode_fan_control = false;
+
        return 0;
 }
 
index 8b5c06a..d16bb1b 100644 (file)
@@ -182,6 +182,7 @@ struct si_power_info {
        u32 dte_table_start;
        u32 spll_table_start;
        u32 papm_cfg_table_start;
+       u32 fan_table_start;
        /* CAC stuff */
        const struct si_cac_config_reg *cac_weights;
        const struct si_cac_config_reg *lcac_config;
@@ -197,6 +198,10 @@ struct si_power_info {
        /* SVI2 */
        u8 svd_gpio_id;
        u8 svc_gpio_id;
+       /* fan control */
+       bool fan_ctrl_is_in_default_mode;
+       u32 t_min;
+       u32 fan_ctrl_default_mode;
 };
 
 #define SISLANDS_INITIAL_STATE_ARB_INDEX    0
index 6635da9..c549c16 100644 (file)
 #define                DIG_THERM_DPM(x)                        ((x) << 14)
 #define                DIG_THERM_DPM_MASK                      0x003FC000
 #define                DIG_THERM_DPM_SHIFT                     14
-
+#define        CG_THERMAL_STATUS                               0x704
+#define                FDO_PWM_DUTY(x)                         ((x) << 9)
+#define                FDO_PWM_DUTY_MASK                       (0xff << 9)
+#define                FDO_PWM_DUTY_SHIFT                      9
 #define        CG_THERMAL_INT                                  0x708
 #define                DIG_THERM_INTH(x)                       ((x) << 8)
 #define                DIG_THERM_INTH_MASK                     0x0000FF00
 #define        THERM_INT_MASK_HIGH                     (1 << 24)
 #define        THERM_INT_MASK_LOW                      (1 << 25)
 
+#define        CG_MULT_THERMAL_CTRL                                    0x710
+#define                TEMP_SEL(x)                                     ((x) << 20)
+#define                TEMP_SEL_MASK                                   (0xff << 20)
+#define                TEMP_SEL_SHIFT                                  20
 #define        CG_MULT_THERMAL_STATUS                                  0x714
 #define                ASIC_MAX_TEMP(x)                                ((x) << 0)
 #define                ASIC_MAX_TEMP_MASK                              0x000001ff
 #define                CTF_TEMP_MASK                                   0x0003fe00
 #define                CTF_TEMP_SHIFT                                  9
 
+#define        CG_FDO_CTRL0                                    0x754
+#define                FDO_STATIC_DUTY(x)                      ((x) << 0)
+#define                FDO_STATIC_DUTY_MASK                    0x0000000F
+#define                FDO_STATIC_DUTY_SHIFT                   0
+#define        CG_FDO_CTRL1                                    0x758
+#define                FMAX_DUTY100(x)                         ((x) << 0)
+#define                FMAX_DUTY100_MASK                       0x0000000F
+#define                FMAX_DUTY100_SHIFT                      0
+#define        CG_FDO_CTRL2                                    0x75C
+#define                TMIN(x)                                 ((x) << 0)
+#define                TMIN_MASK                               0x0000000F
+#define                TMIN_SHIFT                              0
+#define                FDO_PWM_MODE(x)                         ((x) << 11)
+#define                FDO_PWM_MODE_MASK                       (3 << 11)
+#define                FDO_PWM_MODE_SHIFT                      11
+#define                TACH_PWM_RESP_RATE(x)                   ((x) << 25)
+#define                TACH_PWM_RESP_RATE_MASK                 (0x7f << 25)
+#define                TACH_PWM_RESP_RATE_SHIFT                25
+
+#define CG_TACH_CTRL                                    0x770
+#       define EDGE_PER_REV(x)                          ((x) << 0)
+#       define EDGE_PER_REV_MASK                        (0x7 << 0)
+#       define EDGE_PER_REV_SHIFT                       0
+#       define TARGET_PERIOD(x)                         ((x) << 3)
+#       define TARGET_PERIOD_MASK                       0xfffffff8
+#       define TARGET_PERIOD_SHIFT                      3
+#define CG_TACH_STATUS                                  0x774
+#       define TACH_PERIOD(x)                           ((x) << 0)
+#       define TACH_PERIOD_MASK                         0xffffffff
+#       define TACH_PERIOD_SHIFT                        0
+
 #define GENERAL_PWRMGT                                  0x780
 #       define GLOBAL_PWRMGT_EN                         (1 << 0)
 #       define STATIC_PM_EN                             (1 << 1)
index 623a0b1..3c77983 100644 (file)
@@ -245,6 +245,31 @@ typedef struct SISLANDS_SMC_STATETABLE SISLANDS_SMC_STATETABLE;
 #define SI_SMC_SOFT_REGISTER_svi_rework_gpio_id_svd   0x11c
 #define SI_SMC_SOFT_REGISTER_svi_rework_gpio_id_svc   0x120
 
+struct PP_SIslands_FanTable
+{
+       uint8_t  fdo_mode;
+       uint8_t  padding;
+       int16_t  temp_min;
+       int16_t  temp_med;
+       int16_t  temp_max;
+       int16_t  slope1;
+       int16_t  slope2;
+       int16_t  fdo_min;
+       int16_t  hys_up;
+       int16_t  hys_down;
+       int16_t  hys_slope;
+       int16_t  temp_resp_lim;
+       int16_t  temp_curr;
+       int16_t  slope_curr;
+       int16_t  pwm_curr;
+       uint32_t refresh_period;
+       int16_t  fdo_max;
+       uint8_t  temp_src;
+       int8_t  padding2;
+};
+
+typedef struct PP_SIslands_FanTable PP_SIslands_FanTable;
+
 #define SMC_SISLANDS_LKGE_LUT_NUM_OF_TEMP_ENTRIES 16
 #define SMC_SISLANDS_LKGE_LUT_NUM_OF_VOLT_ENTRIES 32