From 68197b4ba0a3c90190795b97248b840a3e506545 Mon Sep 17 00:00:00 2001
From: Ben Skeggs <bskeggs@redhat.com>
Date: Wed, 5 Dec 2012 14:56:37 +1000
Subject: [PATCH] drm/nouveau/therm: add interfaces to allow forcing off pwm
 fan control

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>
---
 .../gpu/drm/nouveau/core/subdev/therm/fan.c   |  2 ++
 .../gpu/drm/nouveau/core/subdev/therm/nv40.c  | 18 +++++++++--
 .../gpu/drm/nouveau/core/subdev/therm/nv50.c  | 12 ++++++-
 .../gpu/drm/nouveau/core/subdev/therm/nvd0.c  | 31 ++++++++++++++++---
 .../gpu/drm/nouveau/core/subdev/therm/priv.h  |  1 +
 5 files changed, 56 insertions(+), 8 deletions(-)

diff --git a/drivers/gpu/drm/nouveau/core/subdev/therm/fan.c b/drivers/gpu/drm/nouveau/core/subdev/therm/fan.c
index 4822733ca885..b4d14490d09e 100644
--- a/drivers/gpu/drm/nouveau/core/subdev/therm/fan.c
+++ b/drivers/gpu/drm/nouveau/core/subdev/therm/fan.c
@@ -96,6 +96,8 @@ nouveau_therm_fan_set(struct nouveau_therm *therm, int percent)
 			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;
 	}
 
diff --git a/drivers/gpu/drm/nouveau/core/subdev/therm/nv40.c b/drivers/gpu/drm/nouveau/core/subdev/therm/nv40.c
index f2092af62ce3..47a15ecbe008 100644
--- a/drivers/gpu/drm/nouveau/core/subdev/therm/nv40.c
+++ b/drivers/gpu/drm/nouveau/core/subdev/therm/nv40.c
@@ -79,6 +79,19 @@ nv40_temp_get(struct nouveau_therm *therm)
 	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)
 {
@@ -109,11 +122,11 @@ int
 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;
@@ -136,6 +149,7 @@ nv40_therm_ctor(struct nouveau_object *parent,
 	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;
diff --git a/drivers/gpu/drm/nouveau/core/subdev/therm/nv50.c b/drivers/gpu/drm/nouveau/core/subdev/therm/nv50.c
index b7e1ecf93409..6ebad82e4feb 100644
--- a/drivers/gpu/drm/nouveau/core/subdev/therm/nv50.c
+++ b/drivers/gpu/drm/nouveau/core/subdev/therm/nv50.c
@@ -54,6 +54,16 @@ pwm_info(struct nouveau_therm *therm, int *line, int *ctrl, int *indx)
 	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)
 {
@@ -77,7 +87,6 @@ nv50_fan_pwm_set(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;
@@ -129,6 +138,7 @@ nv50_therm_ctor(struct nouveau_object *parent,
 	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;
diff --git a/drivers/gpu/drm/nouveau/core/subdev/therm/nvd0.c b/drivers/gpu/drm/nouveau/core/subdev/therm/nvd0.c
index f0336a09cf9e..ec06e895b617 100644
--- a/drivers/gpu/drm/nouveau/core/subdev/therm/nvd0.c
+++ b/drivers/gpu/drm/nouveau/core/subdev/therm/nvd0.c
@@ -32,31 +32,51 @@ static int
 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)
 {
@@ -111,6 +131,7 @@ nvd0_therm_ctor(struct nouveau_object *parent,
 	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;
diff --git a/drivers/gpu/drm/nouveau/core/subdev/therm/priv.h b/drivers/gpu/drm/nouveau/core/subdev/therm/priv.h
index bb940e9d4e8f..a08ca85e43ff 100644
--- a/drivers/gpu/drm/nouveau/core/subdev/therm/priv.h
+++ b/drivers/gpu/drm/nouveau/core/subdev/therm/priv.h
@@ -47,6 +47,7 @@ struct nouveau_therm_priv {
 
 		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 *);
-- 
2.34.1