diff --git a/include/linux/sde_io_util.h b/include/linux/sde_io_util.h index e9b257c774..8bca51e522 100644 --- a/include/linux/sde_io_util.h +++ b/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 #include #include +#include #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); diff --git a/include/uapi/display/drm/sde_drm.h b/include/uapi/display/drm/sde_drm.h index 71169b5a02..c3f045f692 100644 --- a/include/uapi/display/drm/sde_drm.h +++ b/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 diff --git a/msm/sde/sde_core_perf.c b/msm/sde/sde_core_perf.c index 02d992d06f..94f8774ab4 100644 --- a/msm/sde/sde_core_perf.c +++ b/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, diff --git a/msm/sde/sde_crtc.c b/msm/sde/sde_crtc.c index 3808d10295..00482c9037 100644 --- a/msm/sde/sde_crtc.c +++ b/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 diff --git a/msm/sde_io_util.c b/msm/sde_io_util.c index 3e35499e65..5135364956 100644 --- a/msm/sde_io_util.c +++ b/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 @@ -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; diff --git a/msm/sde_power_handle.c b/msm/sde_power_handle.c index c0f3774fa3..839326cb2d 100644 --- a/msm/sde_power_handle.c +++ b/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 : ""); + 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; } } diff --git a/msm/sde_power_handle.h b/msm/sde_power_handle.h index 4273308c92..5c8057acd0 100644 --- a/msm/sde_power_handle.h +++ b/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