msm: kgsl: Use spinlock for A6xx perfcounter updates

Add flag ADRENO_PERFCOUNTER_GROUP_RESTORE for perfcounters that
need to be restored for IFPC and preemption.
For these perfcounters
a) get spinlock
b) update the CP powerup restore list
c) update perfcounter select register
d) release spinlock.
Spinlock prevents race condition where GPU might be restoring
these registers while CPU is updating them at the same time.

Change-Id: I3d902d5cdc8c5b69a86353a944c07c9151b42654
Signed-off-by: Tarun Karra <tkarra@codeaurora.org>
Signed-off-by: Lynus Vaz <lvaz@codeaurora.org>
diff --git a/drivers/gpu/msm/adreno.h b/drivers/gpu/msm/adreno.h
index c3a9868..2d8e7ee 100644
--- a/drivers/gpu/msm/adreno.h
+++ b/drivers/gpu/msm/adreno.h
@@ -913,6 +913,9 @@
 	bool (*sptprac_is_on)(struct adreno_device *);
 	unsigned int (*ccu_invalidate)(struct adreno_device *adreno_dev,
 				unsigned int *cmds);
+	int (*perfcounter_update)(struct adreno_device *adreno_dev,
+				struct adreno_perfcount_register *reg,
+				bool update_reg);
 };
 
 /**
diff --git a/drivers/gpu/msm/adreno_a6xx.c b/drivers/gpu/msm/adreno_a6xx.c
index ea8c3af..b004d24 100644
--- a/drivers/gpu/msm/adreno_a6xx.c
+++ b/drivers/gpu/msm/adreno_a6xx.c
@@ -13,6 +13,7 @@
 #include <linux/firmware.h>
 #include <soc/qcom/subsystem_restart.h>
 #include <linux/pm_opp.h>
+#include <linux/jiffies.h>
 
 #include "adreno.h"
 #include "a6xx_reg.h"
@@ -2977,8 +2978,16 @@
 		A6XX_GMU_CX_GMU_POWER_COUNTER_SELECT_1, },
 };
 
+/*
+ * ADRENO_PERFCOUNTER_GROUP_RESTORE flag is enabled by default
+ * because most of the perfcounter groups need to be restored
+ * as part of preemption and IFPC. Perfcounter groups that are
+ * not restored as part of preemption and IFPC should be defined
+ * using A6XX_PERFCOUNTER_GROUP_FLAGS macro
+ */
 #define A6XX_PERFCOUNTER_GROUP(offset, name) \
-	ADRENO_PERFCOUNTER_GROUP(a6xx, offset, name)
+	ADRENO_PERFCOUNTER_GROUP_FLAGS(a6xx, offset, name, \
+	ADRENO_PERFCOUNTER_GROUP_RESTORE)
 
 #define A6XX_PERFCOUNTER_GROUP_FLAGS(offset, name, flags) \
 	ADRENO_PERFCOUNTER_GROUP_FLAGS(a6xx, offset, name, flags)
@@ -2989,7 +2998,7 @@
 static struct adreno_perfcount_group a6xx_perfcounter_groups
 				[KGSL_PERFCOUNTER_GROUP_MAX] = {
 	A6XX_PERFCOUNTER_GROUP(CP, cp),
-	A6XX_PERFCOUNTER_GROUP(RBBM, rbbm),
+	A6XX_PERFCOUNTER_GROUP_FLAGS(RBBM, rbbm, 0),
 	A6XX_PERFCOUNTER_GROUP(PC, pc),
 	A6XX_PERFCOUNTER_GROUP(VFD, vfd),
 	A6XX_PERFCOUNTER_GROUP(HLSQ, hlsq),
@@ -3004,7 +3013,7 @@
 	A6XX_PERFCOUNTER_GROUP(SP, sp),
 	A6XX_PERFCOUNTER_GROUP(RB, rb),
 	A6XX_PERFCOUNTER_GROUP(VSC, vsc),
-	A6XX_PERFCOUNTER_GROUP(VBIF, vbif),
+	A6XX_PERFCOUNTER_GROUP_FLAGS(VBIF, vbif, 0),
 	A6XX_PERFCOUNTER_GROUP_FLAGS(VBIF_PWR, vbif_pwr,
 		ADRENO_PERFCOUNTER_GROUP_FIXED),
 	A6XX_PERFCOUNTER_GROUP_FLAGS(PWR, pwr,
@@ -3244,6 +3253,69 @@
 	.offset_0 = ADRENO_REG_REGISTER_MAX,
 };
 
+static int a6xx_perfcounter_update(struct adreno_device *adreno_dev,
+	struct adreno_perfcount_register *reg, bool update_reg)
+{
+	struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
+	struct cpu_gpu_lock *lock = adreno_dev->pwrup_reglist.hostptr;
+	struct reg_list_pair *reg_pair = (struct reg_list_pair *)(lock + 1);
+	unsigned int i;
+	unsigned long timeout = jiffies + msecs_to_jiffies(1000);
+	int ret = 0;
+
+	lock->flag_kmd = 1;
+	/* Write flag_kmd before turn */
+	wmb();
+	lock->turn = 0;
+	/* Write these fields before looping */
+	mb();
+
+	/*
+	 * Spin here while GPU ucode holds the lock, lock->flag_ucode will
+	 * be set to 0 after GPU ucode releases the lock. Minimum wait time
+	 * is 1 second and this should be enough for GPU to release the lock
+	 */
+	while (lock->flag_ucode == 1 && lock->turn == 0) {
+		cpu_relax();
+		/* Get the latest updates from GPU */
+		rmb();
+		/*
+		 * Make sure we wait at least 1sec for the lock,
+		 * if we did not get it after 1sec return an error.
+		 */
+		if (time_after(jiffies, timeout) &&
+			(lock->flag_ucode == 1 && lock->turn == 0)) {
+			ret = -EBUSY;
+			goto unlock;
+		}
+	}
+
+	/* Read flag_ucode and turn before list_length */
+	rmb();
+	/*
+	 * If the perfcounter select register is already present in reglist
+	 * update it, otherwise append the <select register, value> pair to
+	 * the end of the list.
+	 */
+	for (i = 0; i < lock->list_length >> 1; i++)
+		if (reg_pair[i].offset == reg->select)
+			break;
+
+	reg_pair[i].offset = reg->select;
+	reg_pair[i].val = reg->countable;
+	if (i == lock->list_length >> 1)
+		lock->list_length += 2;
+
+	if (update_reg)
+		kgsl_regwrite(device, reg->select, reg->countable);
+
+unlock:
+	/* All writes done before releasing the lock */
+	wmb();
+	lock->flag_kmd = 0;
+	return ret;
+}
+
 struct adreno_gpudev adreno_a6xx_gpudev = {
 	.reg_offsets = &a6xx_reg_offsets,
 	.start = a6xx_start,
@@ -3286,4 +3358,5 @@
 	.gx_is_on = a6xx_gx_is_on,
 	.sptprac_is_on = a6xx_sptprac_is_on,
 	.ccu_invalidate = a6xx_ccu_invalidate,
+	.perfcounter_update = a6xx_perfcounter_update,
 };
diff --git a/drivers/gpu/msm/adreno_perfcounter.c b/drivers/gpu/msm/adreno_perfcounter.c
index 03db16d..eef5b7b 100644
--- a/drivers/gpu/msm/adreno_perfcounter.c
+++ b/drivers/gpu/msm/adreno_perfcounter.c
@@ -775,6 +775,7 @@
 	struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
 	struct adreno_gpudev *gpudev = ADRENO_GPU_DEVICE(adreno_dev);
 	struct adreno_perfcount_register *reg;
+	struct adreno_perfcount_group *grp;
 	int i;
 	int ret = 0;
 
@@ -789,7 +790,8 @@
 			if (countable == invalid_countable.countables[i])
 				return -EACCES;
 	}
-	reg = &(counters->groups[group].regs[counter]);
+	grp = &(counters->groups[group]);
+	reg = &(grp->regs[counter]);
 
 	if (!adreno_is_a6xx(adreno_dev) &&
 			test_bit(ADRENO_DEVICE_STARTED, &adreno_dev->priv)) {
@@ -834,12 +836,16 @@
 		}
 	} else {
 		/* Select the desired perfcounter */
-		kgsl_regwrite(device, reg->select, countable);
+		if (gpudev->perfcounter_update && (grp->flags &
+				ADRENO_PERFCOUNTER_GROUP_RESTORE))
+			ret = gpudev->perfcounter_update(adreno_dev, reg, true);
+		else
+			kgsl_regwrite(device, reg->select, countable);
 	}
 
 	if (!ret)
 		reg->value = 0;
-	return 0;
+	return ret;
 }
 
 /**
diff --git a/drivers/gpu/msm/adreno_perfcounter.h b/drivers/gpu/msm/adreno_perfcounter.h
index 8c4db38..bcbc738 100644
--- a/drivers/gpu/msm/adreno_perfcounter.h
+++ b/drivers/gpu/msm/adreno_perfcounter.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2008-2015, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2008-2015, 2017 The Linux Foundation. All rights reserved.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2 and
@@ -70,6 +70,13 @@
 
 #define ADRENO_PERFCOUNTER_GROUP_FIXED BIT(0)
 
+/*
+ * ADRENO_PERFCOUNTER_GROUP_RESTORE indicates CP needs to restore the select
+ * registers of this perfcounter group as part of preemption and IFPC
+ */
+#define ADRENO_PERFCOUNTER_GROUP_RESTORE BIT(1)
+
+
 /**
  * adreno_perfcounts: all available perfcounter groups
  * @groups: available groups for this device