Browse Source

disp: msm: add support to request mdp core clk through mmrm

For Waipio onwards, mdp core clock rate needs to be requested
through the mmrm driver, which will make sure the requested
rate can be sustained by the its power rail, which is shared
by multiple clients.
Current change adds the support to request the mdp core clk rate
through mmrm driver as well as returns the corresponding failures
to user space during when a rate cannot be supported by the
system.

Change-Id: Icc0e8ab5b71e8c99baf7913bdbd96d9504cc11ae
Signed-off-by: Ingrid Gallardo <[email protected]>
Ingrid Gallardo 4 năm trước cách đây
mục cha
commit
a005a785fe

+ 22 - 1
include/linux/sde_io_util.h

@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: GPL-2.0-only */
 /*
- * Copyright (c) 2012, 2017-2020, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2012, 2017-2021, The Linux Foundation. All rights reserved.
  */
 
 #ifndef __SDE_IO_UTIL_H__
@@ -11,6 +11,7 @@
 #include <linux/regulator/consumer.h>
 #include <linux/i2c.h>
 #include <linux/types.h>
+#include <linux/soc/qcom/msm_mmrm.h>
 
 #ifdef DEBUG
 #define DEV_DBG(fmt, args...)   pr_err(fmt, ##args)
@@ -62,15 +63,31 @@ struct dss_gpio {
 enum dss_clk_type {
 	DSS_CLK_AHB, /* no set rate. rate controlled through rpm */
 	DSS_CLK_PCLK,
+	DSS_CLK_MMRM, /* set rate called through mmrm driver */
 	DSS_CLK_OTHER,
 };
 
+struct dss_clk_mmrm_cb {
+	void *phandle;
+	struct dss_clk *clk;
+};
+
+struct dss_clk_mmrm {
+	unsigned int clk_id;
+	unsigned int flags;
+	struct mmrm_client *mmrm_client;
+	struct dss_clk_mmrm_cb *mmrm_cb_data;
+	unsigned long mmrm_requested_clk;
+	wait_queue_head_t mmrm_cb_wq;
+};
+
 struct dss_clk {
 	struct clk *clk; /* clk handle */
 	char clk_name[32];
 	enum dss_clk_type type;
 	unsigned long rate;
 	unsigned long max_rate;
+	struct dss_clk_mmrm mmrm;
 };
 
 struct dss_module_power {
@@ -101,6 +118,10 @@ int msm_dss_get_vreg(struct device *dev, struct dss_vreg *in_vreg,
 int msm_dss_enable_vreg(struct dss_vreg *in_vreg, int num_vreg,	int enable);
 
 int msm_dss_get_clk(struct device *dev, struct dss_clk *clk_arry, int num_clk);
+int msm_dss_mmrm_register(struct device *dev, struct dss_module_power *mp,
+	int (*cb_fnc)(struct mmrm_client_notifier_data *data), void *phandle,
+	bool *mmrm_enable);
+void msm_dss_mmrm_deregister(struct device *dev, struct dss_module_power *mp);
 void msm_dss_put_clk(struct dss_clk *clk_arry, int num_clk);
 int msm_dss_clk_set_rate(struct dss_clk *clk_arry, int num_clk);
 int msm_dss_single_clk_set_rate(struct dss_clk *clk);

+ 2 - 1
include/uapi/display/drm/sde_drm.h

@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */
 /*
- * Copyright (c) 2017-2020, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2017-2021, The Linux Foundation. All rights reserved.
  */
 
 #ifndef _SDE_DRM_H_
@@ -691,6 +691,7 @@ struct drm_msm_display_hint {
 #define DRM_EVENT_LTM_HIST 0X80000008
 #define DRM_EVENT_LTM_WB_PB 0X80000009
 #define DRM_EVENT_LTM_OFF 0X8000000A
+#define DRM_EVENT_MMRM_CB 0X8000000B
 
 /* display hint flags*/
 #define DRM_MSM_DISPLAY_EARLY_WAKEUP_HINT         0x01

+ 109 - 4
msm/sde/sde_core_perf.c

@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0-only
 /*
- * Copyright (c) 2016-2020, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2016-2021, The Linux Foundation. All rights reserved.
  */
 
 #define pr_fmt(fmt)	"[drm:%s:%d] " fmt, __func__, __LINE__
@@ -195,9 +195,11 @@ int sde_core_perf_crtc_check(struct drm_crtc *crtc,
 	u64 bw_sum_of_intfs = 0;
 	enum sde_crtc_client_type curr_client_type;
 	struct sde_crtc_state *sde_cstate;
+	struct msm_drm_private *priv;
 	struct drm_crtc *tmp_crtc;
 	struct sde_kms *kms;
-	int i;
+	u64 current_clk_rate, new_clk_rate;
+	int i, ret;
 
 	if (!crtc || !state) {
 		SDE_ERROR("invalid crtc\n");
@@ -211,10 +213,28 @@ int sde_core_perf_crtc_check(struct drm_crtc *crtc,
 	}
 
 	sde_cstate = to_sde_crtc_state(state);
+	priv = kms->dev->dev_private;
 
 	/* obtain new values */
 	_sde_core_perf_calc_crtc(kms, crtc, state, &sde_cstate->new_perf);
 
+	/* reserve core clk */
+	current_clk_rate = kms->perf.core_clk_rate;
+	new_clk_rate = sde_cstate->new_perf.core_clk_rate;
+	if (new_clk_rate > current_clk_rate) {
+		new_clk_rate = clk_round_rate(kms->perf.core_clk,
+			new_clk_rate);
+		ret = sde_power_clk_set_rate(&priv->phandle,
+			kms->perf.clk_name, new_clk_rate,
+			MMRM_CLIENT_DATA_FLAG_RESERVE_ONLY);
+		if (ret) {
+			SDE_ERROR("cannot reserve core clk rate:%llu\n",
+				new_clk_rate);
+
+			return -E2BIG;
+		}
+	}
+
 	for (i = SDE_POWER_HANDLE_DBUS_ID_MNOC;
 			i < SDE_POWER_HANDLE_DBUS_ID_MAX; i++) {
 		bw_sum_of_intfs = sde_cstate->new_perf.bw_ctl[i];
@@ -997,7 +1017,7 @@ void sde_core_perf_crtc_update(struct drm_crtc *crtc,
 		SDE_EVT32(kms->dev, stop_req, clk_rate, params_changed,
 			old->core_clk_rate, new->core_clk_rate);
 		ret = sde_power_clk_set_rate(&priv->phandle,
-				kms->perf.clk_name, clk_rate);
+				kms->perf.clk_name, clk_rate, 0);
 		if (ret) {
 			SDE_ERROR("failed to set %s clock rate %llu\n",
 					kms->perf.clk_name, clk_rate);
@@ -1105,7 +1125,7 @@ static ssize_t _sde_core_perf_mode_write(struct file *file,
 				(u64) cfg->max_bw_high * 1000;
 
 		ret = sde_power_clk_set_rate(perf->phandle,
-				perf->clk_name, perf->max_core_clk_rate);
+			perf->clk_name, perf->max_core_clk_rate, 0);
 		if (ret) {
 			SDE_ERROR("failed to set %s clock rate %llu\n",
 					perf->clk_name,
@@ -1159,6 +1179,83 @@ static ssize_t _sde_core_perf_mode_read(struct file *file,
 	return len;
 }
 
+static ssize_t _sde_core_perf_mmrm_write(struct file *file,
+		    const char __user *user_buf, size_t count, loff_t *ppos)
+{
+	struct sde_core_perf *perf = file->private_data;
+	struct dss_module_power *mp = &perf->phandle->mp;
+	char buf[20];
+	int i, ret = 0;
+	unsigned long requested_clk;
+	struct dss_clk *clk = NULL;
+
+	if (!perf || !mp)
+		return -ENODEV;
+
+	if (count >= sizeof(buf))
+		return -EFAULT;
+
+	if (copy_from_user(buf, user_buf, count))
+		return -EFAULT;
+
+	buf[count] = 0;	/* end of string */
+
+	if (kstrtoul(buf, 0, &requested_clk))
+		return -EFAULT;
+
+	for (i = 0; i < mp->num_clk; i++) {
+		if (!strcmp(mp->clk_config[i].clk_name, perf->clk_name)) {
+			clk = &mp->clk_config[i];
+			break;
+		}
+	}
+	if (!clk) {
+		SDE_ERROR("Cannot find the clk %s\n", perf->clk_name);
+		goto exit;
+	}
+
+	requested_clk = clk_round_rate(clk->clk, requested_clk);
+	DRM_INFO("requesting limit rate:%lu for clk:%s\n",
+		requested_clk, clk->clk_name);
+
+	ret = sde_power_mmrm_set_clk_limit(clk,
+		perf->phandle, requested_clk);
+	if (ret)
+		SDE_ERROR("Failed to set %s clock rate %llu\n",
+			clk->clk_name, requested_clk);
+
+exit:
+	return count;
+}
+
+static ssize_t _sde_core_perf_mmrm_read(struct file *file,
+			char __user *buff, size_t count, loff_t *ppos)
+{
+	struct sde_core_perf *perf = file->private_data;
+	int len = 0;
+	char buf[128] = {'\0'};
+
+	if (!perf)
+		return -ENODEV;
+
+	if (*ppos)
+		return 0;	/* the end */
+
+	len = snprintf(buf, sizeof(buf),
+			"mmrm clk_limit:%lu clk:%s\n",
+			sde_power_mmrm_get_requested_clk(perf->phandle,
+			perf->clk_name), perf->clk_name);
+	if (len < 0 || len >= sizeof(buf))
+		return 0;
+
+	if ((count < sizeof(buf)) || copy_to_user(buff, buf, len))
+		return -EFAULT;
+
+	*ppos += len;   /* increase offset */
+
+	return len;
+}
+
 static const struct file_operations sde_core_perf_threshold_high_fops = {
 	.open = simple_open,
 	.read = _sde_core_perf_threshold_high_read,
@@ -1171,6 +1268,12 @@ static const struct file_operations sde_core_perf_mode_fops = {
 	.write = _sde_core_perf_mode_write,
 };
 
+static const struct file_operations sde_core_perf_mmrm_fops = {
+	.open = simple_open,
+	.read = _sde_core_perf_mmrm_read,
+	.write = _sde_core_perf_mmrm_write,
+};
+
 static void sde_core_perf_debugfs_destroy(struct sde_core_perf *perf)
 {
 	debugfs_remove_recursive(perf->debugfs_root);
@@ -1214,6 +1317,8 @@ int sde_core_perf_debugfs_init(struct sde_core_perf *perf,
 			(u32 *)&catalog->perf.min_dram_ib);
 	debugfs_create_file("perf_mode", 0600, perf->debugfs_root,
 			(u32 *)perf, &sde_core_perf_mode_fops);
+	debugfs_create_file("mmrm_clk_cb", 0600, perf->debugfs_root,
+			(u32 *)perf, &sde_core_perf_mmrm_fops);
 	debugfs_create_u32("bw_vote_mode", 0600, perf->debugfs_root,
 			&perf->bw_vote_mode);
 	debugfs_create_bool("bw_vote_mode_updated", 0600, perf->debugfs_root,

+ 46 - 1
msm/sde/sde_crtc.c

@@ -62,6 +62,8 @@ static int sde_crtc_power_interrupt_handler(struct drm_crtc *crtc_drm,
 	bool en, struct sde_irq_callback *ad_irq);
 static int sde_crtc_idle_interrupt_handler(struct drm_crtc *crtc_drm,
 	bool en, struct sde_irq_callback *idle_irq);
+static int sde_crtc_mmrm_interrupt_handler(struct drm_crtc *crtc_drm,
+	bool en, struct sde_irq_callback *idle_irq);
 static int sde_crtc_pm_event_handler(struct drm_crtc *crtc, bool en,
 		struct sde_irq_callback *noirq);
 
@@ -74,6 +76,7 @@ static struct sde_crtc_custom_events custom_events[] = {
 	{DRM_EVENT_LTM_HIST, sde_cp_ltm_hist_interrupt},
 	{DRM_EVENT_LTM_WB_PB, sde_cp_ltm_wb_pb_interrupt},
 	{DRM_EVENT_LTM_OFF, sde_cp_ltm_off_event_handler},
+	{DRM_EVENT_MMRM_CB, sde_crtc_mmrm_interrupt_handler},
 };
 
 /* default input fence timeout, in ms */
@@ -4087,6 +4090,38 @@ static void sde_crtc_post_ipc(struct drm_crtc *crtc)
 	sde_cp_crtc_post_ipc(crtc);
 }
 
+static void sde_crtc_mmrm_cb_notification(struct drm_crtc *crtc)
+{
+	struct msm_drm_private *priv;
+	unsigned long requested_clk;
+	struct sde_kms *kms = NULL;
+	struct drm_event event;
+
+	if (!crtc->dev->dev_private) {
+		pr_err("invalid crtc priv\n");
+		return;
+	}
+	priv = crtc->dev->dev_private;
+	kms = to_sde_kms(priv->kms);
+	if (!kms) {
+		SDE_ERROR("invalid parameters\n");
+		return;
+	}
+
+	requested_clk = sde_power_mmrm_get_requested_clk(&priv->phandle,
+			kms->perf.clk_name);
+
+	/* notify user space the reduced clk rate */
+	event.type = DRM_EVENT_MMRM_CB;
+	event.length = sizeof(unsigned long);
+	msm_mode_object_event_notify(&crtc->base, crtc->dev,
+			&event, (u8 *)&requested_clk);
+
+	SDE_EVT32(DRMID(crtc), requested_clk);
+	SDE_DEBUG("crtc[%d]: MMRM cb notified clk:%d\n",
+		crtc->base.id, requested_clk);
+}
+
 static void sde_crtc_handle_power_event(u32 event_type, void *arg)
 {
 	struct drm_crtc *crtc = arg;
@@ -4161,6 +4196,10 @@ static void sde_crtc_handle_power_event(u32 event_type, void *arg)
 		msm_mode_object_event_notify(&crtc->base, crtc->dev, &event,
 				(u8 *)&power_on);
 		break;
+	case SDE_POWER_EVENT_MMRM_CALLBACK:
+		sde_crtc_mmrm_cb_notification(crtc);
+
+		break;
 	default:
 		SDE_DEBUG("event:%d not handled\n", event_type);
 		break;
@@ -4427,7 +4466,7 @@ static void sde_crtc_enable(struct drm_crtc *crtc,
 	sde_crtc->power_event = sde_power_handle_register_event(
 		&priv->phandle,
 		SDE_POWER_EVENT_POST_ENABLE | SDE_POWER_EVENT_POST_DISABLE |
-		SDE_POWER_EVENT_PRE_DISABLE,
+		SDE_POWER_EVENT_PRE_DISABLE | SDE_POWER_EVENT_MMRM_CALLBACK,
 		sde_crtc_handle_power_event, crtc, sde_crtc->name);
 
 	/* Enable ESD thread */
@@ -7038,6 +7077,12 @@ static int sde_crtc_idle_interrupt_handler(struct drm_crtc *crtc_drm,
 	return 0;
 }
 
+static int sde_crtc_mmrm_interrupt_handler(struct drm_crtc *crtc_drm,
+	bool en, struct sde_irq_callback *irq)
+{
+	return 0;
+}
+
 /**
  * sde_crtc_update_cont_splash_settings - update mixer settings
  *	and initial clk during device bootup for cont_splash use case

+ 131 - 2
msm/sde_io_util.c

@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0-only
 /*
- * Copyright (c) 2012-2015, 2017-2020 The Linux Foundation. All rights reserved.
+ * Copyright (c) 2012-2015, 2017-2021 The Linux Foundation. All rights reserved.
  */
 
 #include <linux/clk.h>
@@ -532,6 +532,98 @@ error:
 } /* msm_dss_get_clk */
 EXPORT_SYMBOL(msm_dss_get_clk);
 
+int msm_dss_mmrm_register(struct device *dev, struct dss_module_power *mp,
+	int (*cb_fnc)(struct mmrm_client_notifier_data *data), void *phandle,
+	bool *mmrm_enable)
+{
+	int i, rc = 0;
+	struct dss_clk *clk_array = mp->clk_config;
+	int num_clk = mp->num_clk;
+	*mmrm_enable = false;
+
+	for (i = 0; i < num_clk; i++) {
+		struct mmrm_client_desc desc;
+		char *name = (char *)desc.client_info.desc.name;
+		struct dss_clk_mmrm_cb *mmrm_cb_data;
+
+		if (clk_array[i].type != DSS_CLK_MMRM)
+			continue;
+
+		desc.client_type = MMRM_CLIENT_CLOCK;
+		desc.client_info.desc.client_domain =
+			MMRM_CLIENT_DOMAIN_DISPLAY;
+		desc.client_info.desc.client_id =
+			clk_array[i].mmrm.clk_id;
+		strlcpy(name, clk_array[i].clk_name,
+			sizeof(desc.client_info.desc.name));
+		desc.client_info.desc.clk = clk_array[i].clk;
+		desc.priority = MMRM_CLIENT_PRIOR_LOW;
+
+		/* init callback wait queue */
+		init_waitqueue_head(&clk_array[i].mmrm.mmrm_cb_wq);
+
+		/* register the callback */
+		mmrm_cb_data = kzalloc(sizeof(*mmrm_cb_data), GFP_KERNEL);
+		if (!mmrm_cb_data)
+			return -ENOMEM;
+
+		mmrm_cb_data->phandle = phandle;
+		mmrm_cb_data->clk = &clk_array[i];
+		clk_array[i].mmrm.mmrm_cb_data = mmrm_cb_data;
+
+		desc.pvt_data = (void *)mmrm_cb_data;
+		desc.notifier_callback_fn = cb_fnc;
+
+		clk_array[i].mmrm.mmrm_client = mmrm_client_register(&desc);
+		if (!clk_array[i].mmrm.mmrm_client) {
+			DEV_ERR("mmrm register error\n");
+			DEV_ERR("clk[%d] type:%d id:%d name:%s\n",
+				i, desc.client_type,
+				desc.client_info.desc.client_id,
+				desc.client_info.desc.name);
+
+			rc = -EINVAL;
+		} else {
+			*mmrm_enable = true;
+			DEV_DBG("mmrm register id:%d name=%s prio:%d\n",
+				desc.client_info.desc.client_id,
+				desc.client_info.desc.name,
+				desc.priority);
+		}
+	}
+
+	return rc;
+} /* msm_dss_mmrm_register */
+EXPORT_SYMBOL(msm_dss_mmrm_register);
+
+void msm_dss_mmrm_deregister(struct device *dev,
+	struct dss_module_power *mp)
+{
+	int i, ret;
+	struct dss_clk *clk_array = mp->clk_config;
+	int num_clk = mp->num_clk;
+
+	for (i = 0; i < num_clk; i++) {
+		if (clk_array[i].type != DSS_CLK_MMRM)
+			continue;
+
+		ret = mmrm_client_deregister(
+			clk_array[i].mmrm.mmrm_client);
+		if (ret) {
+			DEV_DBG("fail mmrm deregister ret:%d clk:%s\n",
+				ret, clk_array[i].clk_name);
+			continue;
+		}
+
+		kfree(clk_array[i].mmrm.mmrm_cb_data);
+
+		DEV_DBG("msm dss mmrm deregister clk[%d] name=%s\n",
+			i, clk_array[i].clk_name);
+
+	}
+} /* msm_dss_mmrm_deregister */
+EXPORT_SYMBOL(msm_dss_mmrm_deregister);
+
 int msm_dss_single_clk_set_rate(struct dss_clk *clk)
 {
 	int rc = 0;
@@ -545,13 +637,50 @@ int msm_dss_single_clk_set_rate(struct dss_clk *clk)
 			__builtin_return_address(0), __func__,
 			clk->clk_name);
 
-	if (clk->type != DSS_CLK_AHB) {
+	/* When MMRM enabled, avoid setting the rate for the branch clock,
+	 * MMRM is always expecting the vote from the SRC clock only
+	 */
+	if (!strcmp(clk->clk_name, "branch_clk"))
+		return 0;
+
+	if (clk->type != DSS_CLK_AHB &&
+	    clk->type != DSS_CLK_MMRM &&
+	    !clk->mmrm.flags) {
 		rc = clk_set_rate(clk->clk, clk->rate);
 		if (rc)
 			DEV_ERR("%pS->%s: %s failed. rc=%d\n",
 					__builtin_return_address(0),
 					__func__,
 					clk->clk_name, rc);
+	} else if (clk->type == DSS_CLK_MMRM) {
+		struct mmrm_client_data client_data;
+
+		memset(&client_data, 0, sizeof(client_data));
+		client_data.num_hw_blocks = 1;
+		client_data.flags = clk->mmrm.flags;
+
+		rc = mmrm_client_set_value(
+			clk->mmrm.mmrm_client,
+			&client_data,
+			clk->rate);
+		if (rc) {
+			DEV_ERR("%pS->%s: %s mmrm setval fail rc:%d\n",
+				__builtin_return_address(0),
+				__func__,
+				clk->clk_name, rc);
+		} else if (clk->mmrm.mmrm_requested_clk &&
+			(clk->rate <= clk->mmrm.mmrm_requested_clk)) {
+
+			/* notify any pending clk request from mmrm cb,
+			 * new clk must be less or equal than callback
+			 * request, set requested clock to zero to
+			 * succeed mmrm callback
+			 */
+			clk->mmrm.mmrm_requested_clk = 0;
+
+			/* notify callback */
+			wake_up_all(&clk->mmrm.mmrm_cb_wq);
+		}
 	}
 
 	return rc;

+ 140 - 6
msm/sde_power_handle.c

@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0-only
 /*
- * Copyright (c) 2014-2020, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2014-2021, The Linux Foundation. All rights reserved.
  */
 
 #define pr_fmt(fmt)	"[drm:%s:%d]: " fmt, __func__, __LINE__
@@ -23,6 +23,11 @@
 
 #define KBPS2BPS(x) ((x) * 1000ULL)
 
+/* wait for at most 2 vsync for lowest refresh rate (1hz) */
+#define SDE_MMRM_CB_TIMEOUT_MS		2000
+#define SDE_MMRM_CB_TIMEOUT_JIFFIES  msecs_to_jiffies( \
+		SDE_MMRM_CB_TIMEOUT_MS)
+
 static const char *data_bus_name[SDE_POWER_HANDLE_DBUS_ID_MAX] = {
 	[SDE_POWER_HANDLE_DBUS_ID_MNOC] = "qcom,sde-data-bus",
 	[SDE_POWER_HANDLE_DBUS_ID_LLCC] = "qcom,sde-llcc-bus",
@@ -37,17 +42,21 @@ const char *sde_power_handle_get_dbus_name(u32 bus_id)
 	return NULL;
 }
 
-static void sde_power_event_trigger_locked(struct sde_power_handle *phandle,
+static int sde_power_event_trigger_locked(struct sde_power_handle *phandle,
 		u32 event_type)
 {
 	struct sde_power_event *event;
+	int ret = -EPERM;
 
 	phandle->last_event_handled = event_type;
 	list_for_each_entry(event, &phandle->event_list, list) {
 		if (event->event_type & event_type) {
 			event->cb_fnc(event_type, event->usr);
+			ret = 0;
 		}
 	}
+
+	return ret;
 }
 
 static inline void sde_power_rsc_client_init(struct sde_power_handle *phandle)
@@ -229,6 +238,7 @@ static int sde_power_parse_dt_clock(struct platform_device *pdev,
 	u32 i = 0, rc = 0;
 	const char *clock_name;
 	u32 clock_rate = 0;
+	u32 clock_mmrm = 0;
 	u32 clock_max_rate = 0;
 	int num_clk = 0;
 
@@ -269,6 +279,17 @@ static int sde_power_parse_dt_clock(struct platform_device *pdev,
 		else
 			mp->clk_config[i].type = DSS_CLK_PCLK;
 
+		clock_mmrm = 0;
+		of_property_read_u32_index(pdev->dev.of_node, "clock-mmrm",
+							i, &clock_mmrm);
+		if (clock_mmrm) {
+			mp->clk_config[i].type = DSS_CLK_MMRM;
+			mp->clk_config[i].mmrm.clk_id = clock_mmrm;
+		}
+		pr_debug("clk[%d]:%d mmrm:%d rate:%d name:%s dev:%s\n",
+			i, clock_mmrm, clock_rate, clock_name,
+			pdev->name ? pdev->name : "<unknown>");
+
 		clock_max_rate = 0;
 		of_property_read_u32_index(pdev->dev.of_node, "clock-max-rate",
 							i, &clock_max_rate);
@@ -547,6 +568,90 @@ static int sde_power_reg_bus_update(struct sde_power_reg_bus_handle *reg_bus,
 	return rc;
 }
 
+int sde_power_mmrm_set_clk_limit(struct dss_clk *clk,
+	struct sde_power_handle *phandle, unsigned long requested_clk)
+{
+	int ret;
+
+	clk->mmrm.mmrm_requested_clk = requested_clk;
+
+	SDE_EVT32_VERBOSE(SDE_EVTLOG_FUNC_ENTRY,
+		clk->mmrm.mmrm_requested_clk);
+	ret = sde_power_event_trigger_locked(phandle,
+		SDE_POWER_EVENT_MMRM_CALLBACK);
+	if (ret) {
+		/* no crtc's present, we cannot process the cb */
+		pr_err("error cannot process mmrm cb\n");
+		goto exit;
+	}
+
+	SDE_EVT32_VERBOSE(SDE_EVTLOG_FUNC_CASE1,
+		clk->mmrm.mmrm_requested_clk);
+	/* wait for the request to reduce the clk */
+	ret = wait_event_timeout(clk->mmrm.mmrm_cb_wq,
+		clk->mmrm.mmrm_requested_clk == 0,
+		SDE_MMRM_CB_TIMEOUT_JIFFIES);
+	if (!ret) {
+		/* requested clk was not reduced, fail cb */
+		ret = -EPERM;
+		/* Clear the request */
+		clk->mmrm.mmrm_requested_clk = 0;
+		pr_err("error cannot process mmrm cb clk request\n");
+	} else {
+		ret = 0; // Succeed, clk was reduced
+	}
+
+exit:
+	SDE_EVT32(SDE_EVTLOG_FUNC_EXIT, ret);
+	return ret;
+}
+
+int sde_power_mmrm_callback(
+	struct mmrm_client_notifier_data *notifier_data)
+{
+	struct dss_clk_mmrm_cb *mmrm_cb_data =
+		(struct dss_clk_mmrm_cb *)notifier_data->pvt_data;
+	struct sde_power_handle *phandle =
+		(struct sde_power_handle *)mmrm_cb_data->phandle;
+	struct dss_clk *clk = mmrm_cb_data->clk;
+	int ret = -EPERM;
+
+	if (notifier_data->cb_type == MMRM_CLIENT_RESOURCE_VALUE_CHANGE) {
+		unsigned long requested_clk =
+			notifier_data->cb_data.val_chng.new_val;
+
+		ret = sde_power_mmrm_set_clk_limit(clk, phandle, requested_clk);
+		if (ret)
+			pr_err("mmrm callback error reducing clk:%lu ret:%d\n",
+				requested_clk, ret);
+	}
+
+	return ret;
+}
+
+u64 sde_power_mmrm_get_requested_clk(struct sde_power_handle *phandle,
+	char *clock_name)
+{
+	struct dss_module_power *mp;
+	u64 rate = -EINVAL;
+	int i;
+
+	if (!phandle) {
+		pr_err("invalid input power handle\n");
+		return -EINVAL;
+	}
+	mp = &phandle->mp;
+
+	for (i = 0; i < mp->num_clk; i++) {
+		if (!strcmp(mp->clk_config[i].clk_name, clock_name)) {
+			rate = mp->clk_config[i].mmrm.mmrm_requested_clk;
+			break;
+		}
+	}
+
+	return rate;
+}
+
 int sde_power_resource_init(struct platform_device *pdev,
 	struct sde_power_handle *phandle)
 {
@@ -561,6 +666,9 @@ int sde_power_resource_init(struct platform_device *pdev,
 	mp = &phandle->mp;
 	phandle->dev = &pdev->dev;
 
+	/* event init must happen before mmrm register */
+	INIT_LIST_HEAD(&phandle->event_list);
+
 	rc = sde_power_parse_dt_clock(pdev, mp);
 	if (rc) {
 		pr_err("device clock parsing failed\n");
@@ -586,6 +694,14 @@ int sde_power_resource_init(struct platform_device *pdev,
 		goto clkget_err;
 	}
 
+	rc = msm_dss_mmrm_register(&pdev->dev, mp,
+		sde_power_mmrm_callback, (void *)phandle,
+		&phandle->mmrm_enable);
+	if (rc) {
+		pr_err("mmrm register failed rc=%d\n", rc);
+		goto clkmmrm_err;
+	}
+
 	rc = msm_dss_clk_set_rate(mp->clk_config, mp->num_clk);
 	if (rc) {
 		pr_err("clock set rate failed rc=%d\n", rc);
@@ -598,8 +714,6 @@ int sde_power_resource_init(struct platform_device *pdev,
 		goto bus_err;
 	}
 
-	INIT_LIST_HEAD(&phandle->event_list);
-
 	phandle->rsc_client = NULL;
 	phandle->rsc_client_init = false;
 
@@ -610,6 +724,8 @@ int sde_power_resource_init(struct platform_device *pdev,
 bus_err:
 	sde_power_bus_unregister(phandle);
 clkset_err:
+	msm_dss_mmrm_deregister(&pdev->dev, mp);
+clkmmrm_err:
 	msm_dss_put_clk(mp->clk_config, mp->num_clk);
 clkget_err:
 	msm_dss_get_vreg(&pdev->dev, mp->vreg_config, mp->num_vreg, 0);
@@ -650,6 +766,8 @@ void sde_power_resource_deinit(struct platform_device *pdev,
 
 	sde_power_bus_unregister(phandle);
 
+	msm_dss_mmrm_deregister(&pdev->dev, mp);
+
 	msm_dss_put_clk(mp->clk_config, mp->num_clk);
 
 	msm_dss_get_vreg(&pdev->dev, mp->vreg_config, mp->num_vreg, 0);
@@ -803,7 +921,7 @@ vreg_err:
 }
 
 int sde_power_clk_set_rate(struct sde_power_handle *phandle, char *clock_name,
-	u64 rate)
+	u64 rate, u32 flags)
 {
 	int i, rc = -EINVAL;
 	struct dss_module_power *mp;
@@ -813,8 +931,18 @@ int sde_power_clk_set_rate(struct sde_power_handle *phandle, char *clock_name,
 		return -EINVAL;
 	}
 
+	/*
+	 * Return early if mmrm is disabled and the flags to reserve the mmrm
+	 * mmrm clock are set.
+	 */
+	if (flags && !phandle->mmrm_enable) {
+		pr_debug("mmrm disabled, return early for reserve flags\n");
+		return 0;
+	}
+
 	mutex_lock(&phandle->phandle_lock);
-	if (phandle->last_event_handled & SDE_POWER_EVENT_POST_DISABLE) {
+	if (phandle->last_event_handled & SDE_POWER_EVENT_POST_DISABLE &&
+	    !flags) {
 		pr_debug("invalid power state %u\n",
 				phandle->last_event_handled);
 		SDE_EVT32(phandle->last_event_handled, SDE_EVTLOG_ERROR);
@@ -831,7 +959,13 @@ int sde_power_clk_set_rate(struct sde_power_handle *phandle, char *clock_name,
 				rate = mp->clk_config[i].max_rate;
 
 			mp->clk_config[i].rate = rate;
+			mp->clk_config[i].mmrm.flags = flags;
+			pr_debug("set rate clk:%s rate:%lu flags:0x%x\n",
+				clock_name, rate, flags);
+
+			SDE_ATRACE_BEGIN("sde_clk_set_rate");
 			rc = msm_dss_single_clk_set_rate(&mp->clk_config[i]);
+			SDE_ATRACE_END("sde_clk_set_rate");
 			break;
 		}
 	}

+ 42 - 2
msm/sde_power_handle.h

@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: GPL-2.0-only */
 /*
- * Copyright (c) 2016-2020, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2016-2021, The Linux Foundation. All rights reserved.
  */
 
 #ifndef _SDE_POWER_HANDLE_H_
@@ -31,6 +31,10 @@
 
 /* event will be triggered after power handler enable */
 #define SDE_POWER_EVENT_POST_ENABLE	0x8
+
+/* event will be triggered during MMRM callback */
+#define SDE_POWER_EVENT_MMRM_CALLBACK	0x10
+
 #define DATA_BUS_PATH_MAX	0x2
 
 /*
@@ -153,6 +157,7 @@ struct sde_power_event {
  * @event_list: current power handle event list
  * @rsc_client: sde rsc client pointer
  * @rsc_client_init: boolean to control rsc client create
+ * @mmrm_enable: boolean to indicate if mmrm is enabled
  */
 struct sde_power_handle {
 	struct dss_module_power mp;
@@ -165,6 +170,7 @@ struct sde_power_handle {
 	u32 last_event_handled;
 	struct sde_rsc_client *rsc_client;
 	bool rsc_client_init;
+	bool mmrm_enable;
 };
 
 /**
@@ -223,11 +229,45 @@ int sde_power_data_bus_state_update(struct sde_power_handle *phandle,
  * @pdata:  power handle containing the resources
  * @clock_name: clock name which needs rate update.
  * @rate:       Requested rate.
+ * @flags:      Flags to be set for this clk
  *
  * Return: error code.
  */
 int sde_power_clk_set_rate(struct sde_power_handle *pdata, char *clock_name,
-	u64 rate);
+	u64 rate, u32 flags);
+
+/**
+ * sde_power_mmrm_set_clk_limit() - sets a limit for mdp core clk
+ * @clk: pointer to the mdp core clk
+ * @phandle: power handle containing the resources
+ * @requested_clk: limit requested for the core clk
+ *
+ * This function must be called in a thread different than the
+ * commit-thread, otherwise it will always fail, since this function
+ * sends a notification to user-space to reduce the clk, and then it
+ * waits for the next commit after this call to reduce the clk rate.
+ * Hence, if the commit-thread is blocked (by this function waiting)
+ * or if user-space does not send another commit to reduce the clk,
+ * this function will timeout after some time and fail.
+ *
+ * Return: 0 upon succeeding setting the clk to a value lower or
+ *  equal than the 'requested_clk', otherwise it returns an
+ *  error code.
+ */
+int sde_power_mmrm_set_clk_limit(struct dss_clk *clk,
+	struct sde_power_handle *phandle, unsigned long requested_clk);
+
+/**
+ * sde_power_mmrm_get_requested_clk() - get clk rate requested by mmrm
+ * @pdata:  power handle containing the resources
+ * @clock_name: clock name to get the rate requested by mmrm driver
+ *   to decrease
+ *
+ * Return: clock rate requested by mmrm driver, if 0,
+ *   then no active request by mmrm driver
+ */
+u64 sde_power_mmrm_get_requested_clk(struct sde_power_handle *pdata,
+	char *clock_name);
 
 /**
  * sde_power_clk_get_rate() - get the clock rate