diff --git a/drivers/gpu/drm/msm/adreno/adreno_gpu.c b/drivers/gpu/drm/msm/adreno/adreno_gpu.c
index 31bbf2c83de4..f688e37059b8 100644
--- a/drivers/gpu/drm/msm/adreno/adreno_gpu.c
+++ b/drivers/gpu/drm/msm/adreno/adreno_gpu.c
@@ -441,6 +441,8 @@ int adreno_set_param(struct msm_gpu *gpu, struct msm_file_private *ctx,
 		if (!capable(CAP_SYS_ADMIN))
 			return UERR(EPERM, drm, "invalid permissions");
 		return msm_file_private_set_sysprof(ctx, gpu, value);
+	case MSM_PARAM_REQ_CNTRS:
+		return msm_file_private_request_counters(ctx, gpu, value);
 	default:
 		return UERR(EINVAL, drm, "%s: invalid param: %u", gpu->name, param);
 	}
diff --git a/drivers/gpu/drm/msm/msm_drv.c b/drivers/gpu/drm/msm/msm_drv.c
index 6416d2cb4efc..bf8314ff4a25 100644
--- a/drivers/gpu/drm/msm/msm_drv.c
+++ b/drivers/gpu/drm/msm/msm_drv.c
@@ -377,9 +377,12 @@ static void msm_postclose(struct drm_device *dev, struct drm_file *file)
 	 * It is not possible to set sysprof param to non-zero if gpu
 	 * is not initialized:
 	 */
-	if (priv->gpu)
+	if (ctx->sysprof)
 		msm_file_private_set_sysprof(ctx, priv->gpu, 0);
 
+	if (ctx->counters_requested)
+		msm_file_private_request_counters(ctx, priv->gpu, 0);
+
 	context_close(ctx);
 }
 
diff --git a/drivers/gpu/drm/msm/msm_gpu.c b/drivers/gpu/drm/msm/msm_gpu.c
index 82f204f3bb8f..013b59ca3bb1 100644
--- a/drivers/gpu/drm/msm/msm_gpu.c
+++ b/drivers/gpu/drm/msm/msm_gpu.c
@@ -991,6 +991,7 @@ int msm_gpu_init(struct drm_device *drm, struct platform_device *pdev,
 	gpu->nr_rings = nr_rings;
 
 	refcount_set(&gpu->sysprof_active, 1);
+	refcount_set(&gpu->local_counters_active, 1);
 
 	return 0;
 
diff --git a/drivers/gpu/drm/msm/msm_gpu.h b/drivers/gpu/drm/msm/msm_gpu.h
index e25009150579..83c61e523b1b 100644
--- a/drivers/gpu/drm/msm/msm_gpu.h
+++ b/drivers/gpu/drm/msm/msm_gpu.h
@@ -195,12 +195,28 @@ struct msm_gpu {
 	int nr_rings;
 
 	/**
-	 * sysprof_active:
+	 * @sysprof_active:
 	 *
-	 * The count of contexts that have enabled system profiling.
+	 * The count of contexts that have enabled system profiling plus one.
+	 *
+	 * Note: refcount_t does not like 0->1 transitions.. we want to keep
+	 * the under/overflow checks that refcount_t provides, but allow
+	 * multiple on/off transitions so we track the logical value plus one.)
 	 */
 	refcount_t sysprof_active;
 
+	/**
+	 * @local_counters_active:
+	 *
+	 * The count of contexts that have requested local (intra-submit)
+	 * performance counter usage plus one.
+	 *
+	 * Note: refcount_t does not like 0->1 transitions.. we want to keep
+	 * the under/overflow checks that refcount_t provides, but allow
+	 * multiple on/off transitions so we track the logical value plus one.)
+	 */
+	refcount_t local_counters_active;
+
 	/**
 	 * lock:
 	 *
@@ -383,6 +399,13 @@ struct msm_file_private {
 	 */
 	int sysprof;
 
+	/**
+	 * @counters_requested:
+	 *
+	 * Has the context requested local perfcntr usage.
+	 */
+	bool counters_requested;
+
 	/**
 	 * comm: Overridden task comm, see MSM_PARAM_COMM
 	 *
@@ -626,6 +649,8 @@ void msm_submitqueue_destroy(struct kref *kref);
 
 int msm_file_private_set_sysprof(struct msm_file_private *ctx,
 				 struct msm_gpu *gpu, int sysprof);
+int msm_file_private_request_counters(struct msm_file_private *ctx,
+				      struct msm_gpu *gpu, int reqcntrs);
 void __msm_file_private_destroy(struct kref *kref);
 
 static inline void msm_file_private_put(struct msm_file_private *ctx)
diff --git a/drivers/gpu/drm/msm/msm_submitqueue.c b/drivers/gpu/drm/msm/msm_submitqueue.c
index 7fed1de63b5d..1e1e21e6f7ae 100644
--- a/drivers/gpu/drm/msm/msm_submitqueue.c
+++ b/drivers/gpu/drm/msm/msm_submitqueue.c
@@ -10,6 +10,15 @@
 int msm_file_private_set_sysprof(struct msm_file_private *ctx,
 				 struct msm_gpu *gpu, int sysprof)
 {
+	int ret = 0;
+
+	mutex_lock(&gpu->lock);
+
+	if (sysprof && (refcount_read(&gpu->local_counters_active) > 1)) {
+		ret = UERR(EBUSY, gpu->dev, "Local counter usage active");
+		goto out_unlock;
+	}
+
 	/*
 	 * Since pm_runtime and sysprof_active are both refcounts, we
 	 * call apply the new value first, and then unwind the previous
@@ -18,7 +27,8 @@ int msm_file_private_set_sysprof(struct msm_file_private *ctx,
 
 	switch (sysprof) {
 	default:
-		return UERR(EINVAL, gpu->dev, "Invalid sysprof: %d", sysprof);
+		ret = UERR(EINVAL, gpu->dev, "Invalid sysprof: %d", sysprof);
+		goto out_unlock;
 	case 2:
 		pm_runtime_get_sync(&gpu->pdev->dev);
 		fallthrough;
@@ -43,7 +53,45 @@ int msm_file_private_set_sysprof(struct msm_file_private *ctx,
 
 	ctx->sysprof = sysprof;
 
-	return 0;
+out_unlock:
+	mutex_unlock(&gpu->lock);
+
+	return ret;
+}
+
+int msm_file_private_request_counters(struct msm_file_private *ctx,
+				      struct msm_gpu *gpu, int reqctrs)
+{
+	int ret = 0;
+
+	mutex_lock(&gpu->lock);
+
+	if (reqctrs && (refcount_read(&gpu->sysprof_active) > 1)) {
+		ret = UERR(EBUSY, gpu->dev, "System profiling active");
+		goto out_unlock;
+	}
+
+	if (reqctrs) {
+		if (ctx->counters_requested) {
+			ret = UERR(EINVAL, gpu->dev, "Already requested");
+			goto out_unlock;
+		}
+
+		ctx->counters_requested = true;
+		refcount_inc(&gpu->local_counters_active);
+	} else {
+		if (!ctx->counters_requested) {
+			ret = UERR(EINVAL, gpu->dev, "Not requested");
+			goto out_unlock;
+		}
+		refcount_dec(&gpu->local_counters_active);
+		ctx->counters_requested = false;
+	}
+
+out_unlock:
+	mutex_unlock(&gpu->lock);
+
+	return ret;
 }
 
 void __msm_file_private_destroy(struct kref *kref)
diff --git a/include/uapi/drm/msm_drm.h b/include/uapi/drm/msm_drm.h
index 2342cb90857e..ae7fb355e4a1 100644
--- a/include/uapi/drm/msm_drm.h
+++ b/include/uapi/drm/msm_drm.h
@@ -91,6 +91,7 @@ struct drm_msm_timespec {
 #define MSM_PARAM_UBWC_SWIZZLE 0x12 /* RO */
 #define MSM_PARAM_MACROTILE_MODE 0x13 /* RO */
 #define MSM_PARAM_UCHE_TRAP_BASE 0x14 /* RO */
+#define MSM_PARAM_REQ_CNTRS  0x15 /* WO: request "local" (intra-submit) perfcntr usage  */
 
 /* For backwards compat.  The original support for preemption was based on
  * a single ring per priority level so # of priority levels equals the #
