drm/nouveau/therm: implement automatic fan management
authorMartin Peres <martin.peres@labri.fr>
Wed, 5 Dec 2012 10:28:09 +0000 (20:28 +1000)
committerBen Skeggs <bskeggs@redhat.com>
Wed, 20 Feb 2013 06:00:21 +0000 (16:00 +1000)
v2: improved design but drops safety monitoring (to be in a later patch)
v3: fix locking and mode management
v4: gently fallback to the no-control mode when temperature cannot be got
    and use kernel-provided min/max macros
v5 (Ben Skeggs):
- rebased on my previous patches
v6: fix hysterisis management in trip-based auto fan management

This commit also forbids access to fan management to nvc0+ chipsets as
fan management is already taken care of my PDAEMON's default fw.

Signed-off-by: Ben Skeggs <bskeggs@redhat.com>
Signed-off-by: Martin Peres <martin.peres@labri.fr>
drivers/gpu/drm/nouveau/core/include/subdev/therm.h
drivers/gpu/drm/nouveau/core/subdev/therm/base.c
drivers/gpu/drm/nouveau/core/subdev/therm/fan.c
drivers/gpu/drm/nouveau/core/subdev/therm/priv.h

index ad3aea01635a2ed021c272d767785303dafed5f7..7c83eccadc6121ae69140e6b3afb10e6d706fda3 100644 (file)
@@ -7,6 +7,7 @@
 enum nouveau_therm_fan_mode {
        FAN_CONTROL_NONE = 0,
        FAN_CONTROL_MANUAL = 1,
+       FAN_CONTROL_AUTO = 2,
        FAN_CONTROL_NR,
 };
 
index 4ebd3c86f8e1f09afb9fb04fe9f665753ca1fb53..dee764c7b693d86b4f837ae1d1b111d797a72d49 100644 (file)
 
 #include "priv.h"
 
+static int
+nouveau_therm_update_trip(struct nouveau_therm *therm)
+{
+       struct nouveau_therm_priv *priv = (void *)therm;
+       struct nouveau_therm_trip_point *trip = priv->fan->bios.trip,
+                                       *cur_trip = NULL,
+                                       *last_trip = priv->last_trip;
+       u8  temp = therm->temp_get(therm);
+       u16 duty, i;
+
+       /* look for the trip point corresponding to the current temperature */
+       cur_trip = NULL;
+       for (i = 0; i < priv->fan->bios.nr_fan_trip; i++) {
+               if (temp >= trip[i].temp)
+                       cur_trip = &trip[i];
+       }
+
+       /* account for the hysteresis cycle */
+       if (last_trip && temp <= (last_trip->temp) &&
+           temp > (last_trip->temp - last_trip->hysteresis))
+               cur_trip = last_trip;
+
+       if (cur_trip) {
+               duty = cur_trip->fan_duty;
+               priv->last_trip = cur_trip;
+       } else {
+               duty = 0;
+               priv->last_trip = NULL;
+       }
+
+       return duty;
+}
+
+static int
+nouveau_therm_update_linear(struct nouveau_therm *therm)
+{
+       struct nouveau_therm_priv *priv = (void *)therm;
+       u8  linear_min_temp = priv->fan->bios.linear_min_temp;
+       u8  linear_max_temp = priv->fan->bios.linear_max_temp;
+       u8  temp = therm->temp_get(therm);
+       u16 duty;
+
+       duty  = (temp - linear_min_temp);
+       duty *= (priv->fan->bios.max_duty - priv->fan->bios.min_duty);
+       duty /= (linear_max_temp - linear_min_temp);
+       duty += priv->fan->bios.min_duty;
+
+       return duty;
+}
+
+static void
+nouveau_therm_update(struct nouveau_therm *therm, int mode)
+{
+       struct nouveau_timer *ptimer = nouveau_timer(therm);
+       struct nouveau_therm_priv *priv = (void *)therm;
+       unsigned long flags;
+       int duty;
+
+       spin_lock_irqsave(&priv->lock, flags);
+       if (mode < 0)
+               mode = priv->mode;
+       priv->mode = mode;
+
+       switch (mode) {
+       case FAN_CONTROL_MANUAL:
+               duty = priv->fan->percent;
+               break;
+       case FAN_CONTROL_AUTO:
+               if (priv->fan->bios.nr_fan_trip)
+                       duty = nouveau_therm_update_trip(therm);
+               else
+                       duty = nouveau_therm_update_linear(therm);
+               break;
+       case FAN_CONTROL_NONE:
+       default:
+               goto done;
+       }
+
+       nouveau_therm_fan_set(therm, (mode != FAN_CONTROL_AUTO), duty);
+
+done:
+       if (list_empty(&priv->alarm.head) && (mode == FAN_CONTROL_AUTO))
+               ptimer->alarm(ptimer, 1000000000ULL, &priv->alarm);
+       spin_unlock_irqrestore(&priv->lock, flags);
+}
+
+static void
+nouveau_therm_alarm(struct nouveau_alarm *alarm)
+{
+       struct nouveau_therm_priv *priv =
+              container_of(alarm, struct nouveau_therm_priv, alarm);
+       nouveau_therm_update(&priv->base, -1);
+}
+
+static int
+nouveau_therm_mode(struct nouveau_therm *therm, int mode)
+{
+       struct nouveau_therm_priv *priv = (void *)therm;
+
+       if (priv->mode == mode)
+               return 0;
+
+       /* The default PDAEMON ucode interferes with fan management */
+       if (nv_device(therm)->card_type >= NV_C0)
+               return -EINVAL;
+
+       switch (mode) {
+       case FAN_CONTROL_NONE:
+               nv_info(therm, "switch to no-control mode\n");
+               break;
+       case FAN_CONTROL_MANUAL:
+               nv_info(therm, "switch to manual mode\n");
+               break;
+       case FAN_CONTROL_AUTO:
+               nv_info(therm, "switch to automatic mode\n");
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       nouveau_therm_update(therm, mode);
+       return 0;
+}
+
 int
 nouveau_therm_attr_get(struct nouveau_therm *therm,
                       enum nouveau_therm_attr_type type)
@@ -41,7 +165,7 @@ nouveau_therm_attr_get(struct nouveau_therm *therm,
        case NOUVEAU_THERM_ATTR_FAN_MAX_DUTY:
                return priv->fan->bios.max_duty;
        case NOUVEAU_THERM_ATTR_FAN_MODE:
-               return priv->fan->mode;
+               return priv->mode;
        case NOUVEAU_THERM_ATTR_THRS_FAN_BOOST:
                return priv->bios_sensor.thrs_fan_boost.temp;
        case NOUVEAU_THERM_ATTR_THRS_FAN_BOOST_HYST:
@@ -85,7 +209,7 @@ nouveau_therm_attr_set(struct nouveau_therm *therm,
                priv->fan->bios.max_duty = value;
                return 0;
        case NOUVEAU_THERM_ATTR_FAN_MODE:
-               return nouveau_therm_fan_set_mode(therm, value);
+               return nouveau_therm_mode(therm, value);
        case NOUVEAU_THERM_ATTR_THRS_FAN_BOOST:
                priv->bios_sensor.thrs_fan_boost.temp = value;
                return 0;
@@ -158,6 +282,9 @@ nouveau_therm_create_(struct nouveau_object *parent,
        if (ret)
                return ret;
 
+       nouveau_alarm_init(&priv->alarm, nouveau_therm_alarm);
+       spin_lock_init(&priv->lock);
+
        priv->base.fan_get = nouveau_therm_fan_user_get;
        priv->base.fan_set = nouveau_therm_fan_user_set;
        priv->base.fan_sense = nouveau_therm_fan_sense;
index 1a0f86364a6ef007e02d8abe70bd021b962262ed..0f210f0155a81ffcd643247c2e0231649184103e 100644 (file)
@@ -107,10 +107,6 @@ int
 nouveau_therm_fan_set(struct nouveau_therm *therm, bool immediate, int percent)
 {
        struct nouveau_therm_priv *priv = (void *)therm;
-
-       if (priv->fan->mode == FAN_CONTROL_NONE)
-               return -EINVAL;
-
        return nouveau_fan_update(priv->fan, immediate, percent);
 }
 
@@ -154,34 +150,6 @@ nouveau_therm_fan_sense(struct nouveau_therm *therm)
                return 0;
 }
 
-int
-nouveau_therm_fan_set_mode(struct nouveau_therm *therm,
-                          enum nouveau_therm_fan_mode mode)
-{
-       struct nouveau_therm_priv *priv = (void *)therm;
-
-       if (priv->fan->mode == mode)
-               return 0;
-
-       if (mode < FAN_CONTROL_NONE || mode >= FAN_CONTROL_NR)
-               return -EINVAL;
-
-       switch (mode)
-       {
-       case FAN_CONTROL_NONE:
-               nv_info(therm, "switch fan to no-control mode\n");
-               break;
-       case FAN_CONTROL_MANUAL:
-               nv_info(therm, "switch fan to manual mode\n");
-               break;
-       case FAN_CONTROL_NR:
-               break;
-       }
-
-       priv->fan->mode = mode;
-       return 0;
-}
-
 int
 nouveau_therm_fan_user_get(struct nouveau_therm *therm)
 {
@@ -193,7 +161,7 @@ nouveau_therm_fan_user_set(struct nouveau_therm *therm, int percent)
 {
        struct nouveau_therm_priv *priv = (void *)therm;
 
-       if (priv->fan->mode != FAN_CONTROL_MANUAL)
+       if (priv->mode != FAN_CONTROL_MANUAL)
                return -EINVAL;
 
        return nouveau_therm_fan_set(therm, true, percent);
@@ -209,6 +177,8 @@ nouveau_therm_fan_set_defaults(struct nouveau_therm *therm)
        priv->fan->bios.max_duty = 100;
        priv->fan->bios.bump_period = 500;
        priv->fan->bios.slow_down_period = 2000;
+       priv->fan->bios.linear_min_temp = 40;
+       priv->fan->bios.linear_max_temp = 85;
 }
 
 static void
@@ -263,7 +233,5 @@ nouveau_therm_fan_ctor(struct nouveau_therm *therm)
        if (nvbios_therm_fan_parse(bios, &priv->fan->bios))
                nv_error(therm, "parsing the thermal table failed\n");
        nouveau_therm_fan_safety_checks(therm);
-
-       nouveau_therm_fan_set_mode(therm, FAN_CONTROL_NONE);
        return 0;
 }
index fca580f16009353ef2e289ed9b6be6e2980c8658..5c628b5832e6b7ef3c3d19e03f461a12baede56c 100644 (file)
@@ -36,7 +36,6 @@
 struct nouveau_fan {
        struct nouveau_therm *parent;
        const char *type;
-       enum nouveau_therm_fan_mode mode;
 
        struct nvbios_therm_fan bios;
        struct nvbios_perf_fan perf;
@@ -54,6 +53,12 @@ struct nouveau_fan {
 struct nouveau_therm_priv {
        struct nouveau_therm base;
 
+       /* automatic thermal management */
+       struct nouveau_alarm alarm;
+       spinlock_t lock;
+       struct nouveau_therm_trip_point *last_trip;
+       int mode;
+
        /* bios */
        struct nvbios_therm_sensor bios_sensor;
 
@@ -78,8 +83,6 @@ int nouveau_therm_fan_get(struct nouveau_therm *therm);
 int nouveau_therm_fan_set(struct nouveau_therm *therm, bool now, int percent);
 int nouveau_therm_fan_user_get(struct nouveau_therm *therm);
 int nouveau_therm_fan_user_set(struct nouveau_therm *therm, int percent);
-int nouveau_therm_fan_set_mode(struct nouveau_therm *therm,
-                          enum nouveau_therm_fan_mode mode);
 
 int nouveau_therm_fan_sense(struct nouveau_therm *therm);