Mostly to allow for the possibility of testing 'toggle' fan control easily.
Signed-off-by: Ben Skeggs <bskeggs@redhat.com>
Signed-off-by: Martin Peres <martin.peres@labri.fr>
                        duty = divs - duty;
 
                ret = priv->fan.pwm_set(therm, func.line, divs, duty);
+               if (ret == 0)
+                       ret = priv->fan.pwm_ctrl(therm, func.line, true);
                return ret;
        }
 
 
        return core_temp;
 }
 
+int
+nv40_fan_pwm_ctrl(struct nouveau_therm *therm, int line, bool enable)
+{
+       u32 mask = enable ? 0x80000000 : 0x0000000;
+       if      (line == 2) nv_mask(therm, 0x0010f0, 0x80000000, mask);
+       else if (line == 9) nv_mask(therm, 0x0015f4, 0x80000000, mask);
+       else {
+               nv_error(therm, "unknown pwm ctrl for gpio %d\n", line);
+               return -ENODEV;
+       }
+       return 0;
+}
+
 int
 nv40_fan_pwm_get(struct nouveau_therm *therm, int line, u32 *divs, u32 *duty)
 {
 nv40_fan_pwm_set(struct nouveau_therm *therm, int line, u32 divs, u32 duty)
 {
        if (line == 2) {
-               nv_wr32(therm, 0x0010f0, 0x80000000 | (duty << 16) | divs);
+               nv_mask(therm, 0x0010f0, 0x7fff7fff, (duty << 16) | divs);
        } else
        if (line == 9) {
                nv_wr32(therm, 0x0015f8, divs);
-               nv_wr32(therm, 0x0015f4, duty | 0x80000000);
+               nv_mask(therm, 0x0015f4, 0x7fffffff, duty);
        } else {
                nv_error(therm, "unknown pwm ctrl for gpio %d\n", line);
                return -ENODEV;
        if (ret)
                return ret;
 
+       priv->base.fan.pwm_ctrl = nv40_fan_pwm_ctrl;
        priv->base.fan.pwm_get = nv40_fan_pwm_get;
        priv->base.fan.pwm_set = nv40_fan_pwm_set;
        priv->base.base.temp_get = nv40_temp_get;
 
        return 0;
 }
 
+int
+nv50_fan_pwm_ctrl(struct nouveau_therm *therm, int line, bool enable)
+{
+       u32 data = enable ? 0x00000001 : 0x00000000;
+       int ctrl, id, ret = pwm_info(therm, &line, &ctrl, &id);
+       if (ret == 0)
+               nv_mask(therm, ctrl, 0x00010001 << line, data << line);
+       return ret;
+}
+
 int
 nv50_fan_pwm_get(struct nouveau_therm *therm, int line, u32 *divs, u32 *duty)
 {
        if (ret)
                return ret;
 
-       nv_mask(therm, ctrl, 0x00010001 << line, 0x00000001 << line);
        nv_wr32(therm, 0x00e114 + (id * 8), divs);
        nv_wr32(therm, 0x00e118 + (id * 8), duty | 0x80000000);
        return 0;
        if (ret)
                return ret;
 
+       priv->base.fan.pwm_ctrl = nv50_fan_pwm_ctrl;
        priv->base.fan.pwm_get = nv50_fan_pwm_get;
        priv->base.fan.pwm_set = nv50_fan_pwm_set;
        priv->base.fan.pwm_clock = nv50_fan_pwm_clock;
 
 pwm_info(struct nouveau_therm *therm, int line)
 {
        u32 gpio = nv_rd32(therm, 0x00d610 + (line * 0x04));
-       if (gpio & 0x00000040) {
+       switch (gpio & 0x000000c0) {
+       case 0x00000000: /* normal mode, possibly pwm forced off by us */
+       case 0x00000040: /* nvio special */
                switch (gpio & 0x0000001f) {
                case 0x19: return 1;
                case 0x1c: return 0;
                default:
                        break;
                }
+       default:
+               break;
        }
 
        nv_error(therm, "GPIO %d unknown PWM: 0x%08x\n", line, gpio);
-       return -EINVAL;
+       return -ENODEV;
 }
 
 static int
-nvd0_fan_pwm_get(struct nouveau_therm *therm, int line, u32 *divs, u32 *duty)
+nvd0_fan_pwm_ctrl(struct nouveau_therm *therm, int line, bool enable)
 {
+       u32 data = enable ? 0x00000040 : 0x00000000;
        int indx = pwm_info(therm, line);
        if (indx < 0)
                return indx;
 
-       *divs = nv_rd32(therm, 0x00e114 + (indx * 8));
-       *duty = nv_rd32(therm, 0x00e118 + (indx * 8));
+       nv_mask(therm, 0x00d610 + (line * 0x04), 0x000000c0, data);
        return 0;
 }
 
+static int
+nvd0_fan_pwm_get(struct nouveau_therm *therm, int line, u32 *divs, u32 *duty)
+{
+       int indx = pwm_info(therm, line);
+       if (indx < 0)
+               return indx;
+
+       if (nv_rd32(therm, 0x00d610 + (line * 0x04)) & 0x00000040) {
+               *divs = nv_rd32(therm, 0x00e114 + (indx * 8));
+               *duty = nv_rd32(therm, 0x00e118 + (indx * 8));
+               return 0;
+       }
+
+       return -EINVAL;
+}
+
 static int
 nvd0_fan_pwm_set(struct nouveau_therm *therm, int line, u32 divs, u32 duty)
 {
        if (ret)
                return ret;
 
+       priv->base.fan.pwm_ctrl = nvd0_fan_pwm_ctrl;
        priv->base.fan.pwm_get = nvd0_fan_pwm_get;
        priv->base.fan.pwm_set = nvd0_fan_pwm_set;
        priv->base.fan.pwm_clock = nvd0_fan_pwm_clock;
 
 
                struct dcb_gpio_func tach;
 
+               int (*pwm_ctrl)(struct nouveau_therm *, int line, bool);
                int (*pwm_get)(struct nouveau_therm *, int line, u32*, u32*);
                int (*pwm_set)(struct nouveau_therm *, int line, u32, u32);
                int (*pwm_clock)(struct nouveau_therm *);