drm/nouveau/fan: obey fan bump/slow periods as defined by vbios
authorMartin Peres <martin.peres@labri.fr>
Wed, 5 Dec 2012 09:46:35 +0000 (19:46 +1000)
committerBen Skeggs <bskeggs@redhat.com>
Wed, 20 Feb 2013 06:00:20 +0000 (16:00 +1000)
v2 (Ben Skeggs):
- split from larger patch
- fixed to not require alarm resched patch

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

index aed72a2d9198340957ce67b73e07a84c7b60c105..1a0f86364a6ef007e02d8abe70bd021b962262ed 100644 (file)
 #include <subdev/gpio.h>
 #include <subdev/timer.h>
 
+static int
+nouveau_fan_update(struct nouveau_fan *fan, bool immediate, int target)
+{
+       struct nouveau_therm *therm = fan->parent;
+       struct nouveau_therm_priv *priv = (void *)therm;
+       struct nouveau_timer *ptimer = nouveau_timer(priv);
+       unsigned long flags;
+       int ret = 0;
+       u32 duty;
+
+       /* update target fan speed, restricting to allowed range */
+       spin_lock_irqsave(&fan->lock, flags);
+       if (target < 0)
+               target = fan->percent;
+       target = max_t(u8, target, fan->bios.min_duty);
+       target = min_t(u8, target, fan->bios.max_duty);
+       fan->percent = target;
+
+       /* smooth out the fanspeed increase/decrease */
+       duty = fan->get(therm);
+       if (!immediate && duty >= 0) {
+               /* the constant "3" is a rough approximation taken from
+                * nvidia's behaviour.
+                * it is meant to bump the fan speed more incrementally
+                */
+               if (duty < target)
+                       duty = min(duty + 3, (u32) target);
+               else if (duty > target)
+                       duty = max(duty - 3, (u32) target);
+       } else {
+               duty = target;
+       }
+
+       ret = fan->set(therm, duty);
+       if (ret)
+               goto done;
+
+       /* schedule next fan update, if not at target speed already */
+       if (list_empty(&fan->alarm.head) && target != duty) {
+               u16 bump_period = fan->bios.bump_period;
+               u16 slow_down_period = fan->bios.slow_down_period;
+               u64 delay;
+
+               if (duty > target)
+                       delay = slow_down_period;
+               else if (duty == target)
+                       delay = min(bump_period, slow_down_period) ;
+               else
+                       delay = bump_period;
+
+               ptimer->alarm(ptimer, delay * 1000 * 1000, &fan->alarm);
+       }
+
+done:
+       spin_unlock_irqrestore(&fan->lock, flags);
+       return ret;
+}
+
+static void
+nouveau_fan_alarm(struct nouveau_alarm *alarm)
+{
+       struct nouveau_fan *fan = container_of(alarm, struct nouveau_fan, alarm);
+       nouveau_fan_update(fan, false, -1);
+}
+
 int
 nouveau_therm_fan_get(struct nouveau_therm *therm)
 {
@@ -39,19 +104,14 @@ nouveau_therm_fan_get(struct nouveau_therm *therm)
 }
 
 int
-nouveau_therm_fan_set(struct nouveau_therm *therm, int percent)
+nouveau_therm_fan_set(struct nouveau_therm *therm, bool immediate, int percent)
 {
        struct nouveau_therm_priv *priv = (void *)therm;
 
-       if (percent < priv->fan->bios.min_duty)
-               percent = priv->fan->bios.min_duty;
-       if (percent > priv->fan->bios.max_duty)
-               percent = priv->fan->bios.max_duty;
-
        if (priv->fan->mode == FAN_CONTROL_NONE)
                return -EINVAL;
 
-       return priv->fan->set(therm, percent);
+       return nouveau_fan_update(priv->fan, immediate, percent);
 }
 
 int
@@ -136,7 +196,7 @@ nouveau_therm_fan_user_set(struct nouveau_therm *therm, int percent)
        if (priv->fan->mode != FAN_CONTROL_MANUAL)
                return -EINVAL;
 
-       return nouveau_therm_fan_set(therm, percent);
+       return nouveau_therm_fan_set(therm, true, percent);
 }
 
 void
@@ -147,6 +207,8 @@ nouveau_therm_fan_set_defaults(struct nouveau_therm *therm)
        priv->fan->bios.pwm_freq = 0;
        priv->fan->bios.min_duty = 0;
        priv->fan->bios.max_duty = 100;
+       priv->fan->bios.bump_period = 500;
+       priv->fan->bios.slow_down_period = 2000;
 }
 
 static void
@@ -190,6 +252,11 @@ nouveau_therm_fan_ctor(struct nouveau_therm *therm)
        if (ret)
                priv->fan->tach.func = DCB_GPIO_UNUSED;
 
+       /* initialise fan bump/slow update handling */
+       priv->fan->parent = therm;
+       nouveau_alarm_init(&priv->fan->alarm, nouveau_fan_alarm);
+       spin_lock_init(&priv->fan->lock);
+
        /* other random init... */
        nouveau_therm_fan_set_defaults(therm);
        nvbios_perf_fan_parse(bios, &priv->fan->perf);
index b08e4e2b33e64e1baa0b2e61ed3638c6c87e8274..7e50f1419ac6216af410efea02f83b890f33aca3 100644 (file)
@@ -32,7 +32,6 @@
 
 struct nouveau_fantog_priv {
        struct nouveau_fan base;
-       struct nouveau_therm *parent;
        struct nouveau_alarm alarm;
        spinlock_t lock;
        u32 period_us;
@@ -43,7 +42,7 @@ struct nouveau_fantog_priv {
 static void
 nouveau_fantog_update(struct nouveau_fantog_priv *priv, int percent)
 {
-       struct nouveau_therm_priv *tpriv = (void *)priv->parent;
+       struct nouveau_therm_priv *tpriv = (void *)priv->base.parent;
        struct nouveau_timer *ptimer = nouveau_timer(tpriv);
        struct nouveau_gpio *gpio = nouveau_gpio(tpriv);
        unsigned long flags;
@@ -104,7 +103,6 @@ nouveau_fantog_create(struct nouveau_therm *therm, struct dcb_gpio_func *func)
        if (!priv)
                return -ENOMEM;
 
-       priv->parent = therm;
        priv->base.type = "toggle";
        priv->base.get = nouveau_fantog_get;
        priv->base.set = nouveau_fantog_set;
index 34c85e62685dcc813010f400bbbe014828b30652..fca580f16009353ef2e289ed9b6be6e2980c8658 100644 (file)
 #include <subdev/timer.h>
 
 struct nouveau_fan {
+       struct nouveau_therm *parent;
        const char *type;
        enum nouveau_therm_fan_mode mode;
-       int percent;
 
        struct nvbios_therm_fan bios;
        struct nvbios_perf_fan perf;
 
+       struct nouveau_alarm alarm;
+       spinlock_t lock;
+       int percent;
+
        int (*get)(struct nouveau_therm *therm);
        int (*set)(struct nouveau_therm *therm, int percent);
 
@@ -71,7 +75,7 @@ int nouveau_therm_sensor_ctor(struct nouveau_therm *therm);
 
 int nouveau_therm_fan_ctor(struct nouveau_therm *therm);
 int nouveau_therm_fan_get(struct nouveau_therm *therm);
-int nouveau_therm_fan_set(struct nouveau_therm *therm, int percent);
+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,