From 7238eca4cf18a5bba8679afc8c71a274e264f82e Mon Sep 17 00:00:00 2001
From: Ben Skeggs <bskeggs@redhat.com>
Date: Fri, 13 Jun 2014 14:17:09 +1000
Subject: [PATCH] drm/nouveau: expose pstate selection per-power source in
 sysfs

echo ac:id >> pstate # select mode when on mains power
echo dc:id >> pstate # select mode when on battery
echo id >> pstate # select mode for both

Signed-off-by: Ben Skeggs <bskeggs@redhat.com>
---
 .../gpu/drm/nouveau/core/engine/device/ctrl.c | 25 ++++++++----
 .../gpu/drm/nouveau/core/include/core/class.h |  7 +++-
 drivers/gpu/drm/nouveau/nouveau_sysfs.c       | 38 +++++++++++++++----
 3 files changed, 52 insertions(+), 18 deletions(-)

diff --git a/drivers/gpu/drm/nouveau/core/engine/device/ctrl.c b/drivers/gpu/drm/nouveau/core/engine/device/ctrl.c
index 754fc1da6a0b9..fb546f3a1af04 100644
--- a/drivers/gpu/drm/nouveau/core/engine/device/ctrl.c
+++ b/drivers/gpu/drm/nouveau/core/engine/device/ctrl.c
@@ -40,15 +40,16 @@ nouveau_control_mthd_pstate_info(struct nouveau_object *object, u32 mthd,
 		return -EINVAL;
 
 	if (clk) {
-		args->count  = clk->state_nr;
-		if (clk->pwrsrc)
-			args->ustate = clk->ustate_ac;
-		else
-			args->ustate = clk->ustate_dc;
+		args->count = clk->state_nr;
+		args->ustate_ac = clk->ustate_ac;
+		args->ustate_dc = clk->ustate_dc;
+		args->pwrsrc = clk->pwrsrc;
 		args->pstate = clk->pstate;
 	} else {
-		args->count  = 0;
-		args->ustate = NV_CONTROL_PSTATE_INFO_USTATE_DISABLE;
+		args->count = 0;
+		args->ustate_ac = NV_CONTROL_PSTATE_INFO_USTATE_DISABLE;
+		args->ustate_dc = NV_CONTROL_PSTATE_INFO_USTATE_DISABLE;
+		args->pwrsrc = -ENOSYS;
 		args->pstate = NV_CONTROL_PSTATE_INFO_PSTATE_UNKNOWN;
 	}
 
@@ -122,11 +123,19 @@ nouveau_control_mthd_pstate_user(struct nouveau_object *object, u32 mthd,
 {
 	struct nouveau_clock *clk = nouveau_clock(object);
 	struct nv_control_pstate_user *args = data;
+	int ret = 0;
 
 	if (size < sizeof(*args) || !clk)
 		return -EINVAL;
 
-	return nouveau_clock_ustate(clk, args->state, clk->pwrsrc);
+	if (args->pwrsrc >= 0) {
+		ret |= nouveau_clock_ustate(clk, args->ustate, args->pwrsrc);
+	} else {
+		ret |= nouveau_clock_ustate(clk, args->ustate, 0);
+		ret |= nouveau_clock_ustate(clk, args->ustate, 1);
+	}
+
+	return ret;
 }
 
 struct nouveau_oclass
diff --git a/drivers/gpu/drm/nouveau/core/include/core/class.h b/drivers/gpu/drm/nouveau/core/include/core/class.h
index e0c812bc884f9..d6fd2cbe43334 100644
--- a/drivers/gpu/drm/nouveau/core/include/core/class.h
+++ b/drivers/gpu/drm/nouveau/core/include/core/class.h
@@ -148,7 +148,9 @@ struct nv_perfctr_read {
 
 struct nv_control_pstate_info {
 	u32 count; /* out: number of power states */
-	s32 ustate; /* out: current target pstate index */
+	s32 ustate_ac; /* out: target pstate index */
+	s32 ustate_dc; /* out: target pstate index */
+	s32 pwrsrc; /* out: current power source */
 	u32 pstate; /* out: current pstate index */
 };
 
@@ -166,7 +168,8 @@ struct nv_control_pstate_attr {
 };
 
 struct nv_control_pstate_user {
-	s32 state; /*  in: pstate identifier */
+	s32 ustate; /*  in: pstate identifier */
+	s32 pwrsrc; /*  in: target power source */
 };
 
 /* DMA FIFO channel classes
diff --git a/drivers/gpu/drm/nouveau/nouveau_sysfs.c b/drivers/gpu/drm/nouveau/nouveau_sysfs.c
index 75dda2b071761..ab5afc50460a4 100644
--- a/drivers/gpu/drm/nouveau/nouveau_sysfs.c
+++ b/drivers/gpu/drm/nouveau/nouveau_sysfs.c
@@ -68,7 +68,9 @@ nouveau_sysfs_pstate_get(struct device *d, struct device_attribute *a, char *b)
 		if (i < info.count)
 			snappendf(buf, cnt, "%02x:", attr.state);
 		else
-			snappendf(buf, cnt, "--:");
+			snappendf(buf, cnt, "%s:", info.pwrsrc == 0 ? "DC" :
+						   info.pwrsrc == 1 ? "AC" :
+						   "--");
 
 		attr.index = 0;
 		do {
@@ -84,9 +86,20 @@ nouveau_sysfs_pstate_get(struct device *d, struct device_attribute *a, char *b)
 			snappendf(buf, cnt, " %s", attr.unit);
 		} while (attr.index);
 
-		if ((state >= 0 && info.pstate == state) ||
-		    (state <  0 && info.ustate < 0))
-			snappendf(buf, cnt, " *");
+		if (state >= 0) {
+			if (info.ustate_ac == state)
+				snappendf(buf, cnt, " AC");
+			if (info.ustate_dc == state)
+				snappendf(buf, cnt, " DC");
+			if (info.pstate == state)
+				snappendf(buf, cnt, " *");
+		} else {
+			if (info.ustate_ac < -1)
+				snappendf(buf, cnt, " AC");
+			if (info.ustate_dc < -1)
+				snappendf(buf, cnt, " DC");
+		}
+
 		snappendf(buf, cnt, "\n");
 	}
 
@@ -98,23 +111,32 @@ nouveau_sysfs_pstate_set(struct device *d, struct device_attribute *a,
 			 const char *buf, size_t count)
 {
 	struct nouveau_sysfs *sysfs = nouveau_sysfs(drm_device(d));
-	struct nv_control_pstate_user args;
+	struct nv_control_pstate_user args = { .pwrsrc = -EINVAL };
 	long value, ret;
 	char *tmp;
 
 	if ((tmp = strchr(buf, '\n')))
 		*tmp = '\0';
 
+	if (!strncasecmp(buf, "dc:", 3)) {
+		args.pwrsrc = 0;
+		buf += 3;
+	} else
+	if (!strncasecmp(buf, "ac:", 3)) {
+		args.pwrsrc = 1;
+		buf += 3;
+	}
+
 	if (!strcasecmp(buf, "none"))
-		args.state = NV_CONTROL_PSTATE_USER_STATE_UNKNOWN;
+		args.ustate = NV_CONTROL_PSTATE_USER_STATE_UNKNOWN;
 	else
 	if (!strcasecmp(buf, "auto"))
-		args.state = NV_CONTROL_PSTATE_USER_STATE_PERFMON;
+		args.ustate = NV_CONTROL_PSTATE_USER_STATE_PERFMON;
 	else {
 		ret = kstrtol(buf, 16, &value);
 		if (ret)
 			return ret;
-		args.state = value;
+		args.ustate = value;
 	}
 
 	ret = nv_exec(sysfs->ctrl, NV_CONTROL_PSTATE_USER, &args, sizeof(args));