Merge branch 'LA.UM.9.12.C10.11.00.00.840.478' via branch 'qcom-msm-4.19-7250' into android-msm-pixel-4.19
Bug: 261541074
Change-Id: I92c4233b786e0681ee6e236334481486e5563637
Signed-off-by: JohnnLee <johnnlee@google.com>
diff --git a/Makefile b/Makefile
index b282962..0f921da 100644
--- a/Makefile
+++ b/Makefile
@@ -1,20 +1,26 @@
# SPDX-License-Identifier: GPL-2.0-only
# auto-detect subdirs
-ifeq ($(CONFIG_ARCH_KONA), y)
-include $(srctree)/techpack/display/config/konadisp.conf
+ifeq ($(CONFIG_DRM_MSM:M=m), m)
+SUFFIX_DRM_MSM:=-gki
+else
+SUFFIX_DRM_MSM:=
endif
ifeq ($(CONFIG_ARCH_KONA), y)
-LINUXINCLUDE += -include $(srctree)/techpack/display/config/konadispconf.h
+include $(srctree)/techpack/display/config/konadisp$(SUFFIX_DRM_MSM).conf
+endif
+
+ifeq ($(CONFIG_ARCH_KONA), y)
+LINUXINCLUDE += -include $(srctree)/techpack/display/config/konadispconf$(SUFFIX_DRM_MSM).h
endif
ifeq ($(CONFIG_ARCH_LITO), y)
-include $(srctree)/techpack/display/config/saipdisp.conf
+include $(srctree)/techpack/display/config/saipdisp$(SUFFIX_DRM_MSM).conf
endif
ifeq ($(CONFIG_ARCH_LITO), y)
-LINUXINCLUDE += -include $(srctree)/techpack/display/config/saipdispconf.h
+LINUXINCLUDE += -include $(srctree)/techpack/display/config/saipdispconf$(SUFFIX_DRM_MSM).h
endif
ifeq ($(CONFIG_ARCH_BENGAL), y)
@@ -27,4 +33,3 @@
obj-$(CONFIG_DRM_MSM) += msm/
obj-$(CONFIG_MSM_SDE_ROTATOR) += rotator/
-obj-$(CONFIG_QCOM_MDSS_PLL) += pll/
diff --git a/config/konadisp-gki.conf b/config/konadisp-gki.conf
new file mode 100644
index 0000000..7e69785
--- /dev/null
+++ b/config/konadisp-gki.conf
@@ -0,0 +1,11 @@
+export CONFIG_DRM_MSM=m
+export CONFIG_DRM_MSM_SDE=y
+export CONFIG_SYNC_FILE=y
+export CONFIG_DRM_MSM_DSI=y
+export CONFIG_DRM_MSM_DP=y
+export CONFIG_QCOM_MDSS_DP_PLL=y
+export CONFIG_DSI_PARSER=y
+export CONFIG_DRM_SDE_WB=y
+export CONFIG_DRM_MSM_REGISTER_LOGGING=y
+export CONFIG_QCOM_MDSS_PLL=y
+export CONFIG_DRM_SDE_RSC=y
diff --git a/config/konadisp.conf b/config/konadisp.conf
index dbbf3c8..48bdc0b 100644
--- a/config/konadisp.conf
+++ b/config/konadisp.conf
@@ -1,4 +1,4 @@
-export CONFIG_DRM_MSM=y
+export CONFIG_DRM_MSM=m
export CONFIG_DRM_MSM_SDE=y
export CONFIG_SYNC_FILE=y
export CONFIG_DRM_MSM_DSI=y
@@ -8,6 +8,5 @@
export CONFIG_DRM_SDE_WB=y
export CONFIG_DRM_MSM_REGISTER_LOGGING=y
export CONFIG_QCOM_MDSS_PLL=y
-export CONFIG_MSM_SDE_ROTATOR=y
export CONFIG_MSM_SDE_ROTATOR_EVTLOG_DEBUG=y
export CONFIG_DRM_SDE_RSC=y
diff --git a/config/konadispconf-gki.h b/config/konadispconf-gki.h
new file mode 100644
index 0000000..e8f68a2
--- /dev/null
+++ b/config/konadispconf-gki.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2019, The Linux Foundation. All rights reserved.
+ */
+
+#define CONFIG_DRM_MSM_MODULE 1
+#define CONFIG_DRM_MSM_SDE 1
+#define CONFIG_SYNC_FILE 1
+#define CONFIG_DRM_MSM_DSI 1
+#define CONFIG_DRM_MSM_DP 1
+#define CONFIG_QCOM_MDSS_DP_PLL 1
+#define CONFIG_DSI_PARSER 1
+#define CONFIG_DRM_SDE_WB 1
+#define CONFIG_DRM_MSM_REGISTER_LOGGING 1
+#define CONFIG_DRM_SDE_EVTLOG_DEBUG 1
+#define CONFIG_QCOM_MDSS_PLL 1
+#define CONFIG_DRM_SDE_RSC 1
+
diff --git a/config/konadispconf.h b/config/konadispconf.h
index 690d4ec..a3ccfc2 100644
--- a/config/konadispconf.h
+++ b/config/konadispconf.h
@@ -3,7 +3,7 @@
* Copyright (c) 2019, The Linux Foundation. All rights reserved.
*/
-#define CONFIG_DRM_MSM 1
+#define CONFIG_DRM_MSM_MODULE 1
#define CONFIG_DRM_MSM_SDE 1
#define CONFIG_SYNC_FILE 1
#define CONFIG_DRM_MSM_DSI 1
diff --git a/config/saipdisp-gki.conf b/config/saipdisp-gki.conf
new file mode 100644
index 0000000..7e69785
--- /dev/null
+++ b/config/saipdisp-gki.conf
@@ -0,0 +1,11 @@
+export CONFIG_DRM_MSM=m
+export CONFIG_DRM_MSM_SDE=y
+export CONFIG_SYNC_FILE=y
+export CONFIG_DRM_MSM_DSI=y
+export CONFIG_DRM_MSM_DP=y
+export CONFIG_QCOM_MDSS_DP_PLL=y
+export CONFIG_DSI_PARSER=y
+export CONFIG_DRM_SDE_WB=y
+export CONFIG_DRM_MSM_REGISTER_LOGGING=y
+export CONFIG_QCOM_MDSS_PLL=y
+export CONFIG_DRM_SDE_RSC=y
diff --git a/config/saipdispconf-gki.h b/config/saipdispconf-gki.h
new file mode 100644
index 0000000..d91b4fb
--- /dev/null
+++ b/config/saipdispconf-gki.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2019, The Linux Foundation. All rights reserved.
+ */
+
+#define CONFIG_DRM_MSM_MODULE 1
+#define CONFIG_DRM_MSM_SDE 1
+#define CONFIG_SYNC_FILE 1
+#define CONFIG_DRM_MSM_DSI 1
+#define CONFIG_DRM_MSM_DP 1
+#define CONFIG_QCOM_MDSS_DP_PLL 1
+#define CONFIG_DSI_PARSER 1
+#define CONFIG_DRM_SDE_WB 1
+#define CONFIG_DRM_MSM_REGISTER_LOGGING 1
+#define CONFIG_DRM_SDE_EVTLOG_DEBUG 1
+#define CONFIG_QCOM_MDSS_PLL 1
+#define CONFIG_DRM_SDE_RSC 1
diff --git a/msm/Makefile b/msm/Makefile
index 18eb12c..5791224 100644
--- a/msm/Makefile
+++ b/msm/Makefile
@@ -3,6 +3,16 @@
ccflags-y += -I$(srctree)/techpack/display/msm/sde
ccflags-y += -I$(srctree)/techpack/display/rotator
+ccflags-y += -I$(srctree)/techpack/display/pll
+ccflags-y += -I$(srctree)/drivers/clk/qcom/
+
+msm_drm-$(CONFIG_QCOM_MDSS_PLL) += $(patsubst %,../pll/%,pll_util.o pll_drv.o dsi_pll_10nm.o \
+ dsi_pll_7nm.o dsi_pll_28lpm.o dsi_pll_28nm_util.o dsi_pll_14nm.o dsi_pll_14nm_util.o \
+ hdmi_pll_28lpm.o)
+
+msm_drm-$(CONFIG_QCOM_MDSS_DP_PLL) += $(patsubst %,../pll/%,dp_pll_7nm.o dp_pll_7nm_util.o \
+ dp_pll_10nm.o dp_pll_10nm_util.o dp_pll_14nm.o)
+
msm_drm-$(CONFIG_DRM_MSM_DP) += dp/dp_usbpd.o \
dp/dp_parser.o \
dp/dp_power.o \
@@ -98,9 +108,12 @@
dsi/dsi_drm.o \
dsi/dsi_display.o \
dsi/dsi_panel.o \
+ dsi/dsi_backlight.o \
dsi/dsi_clk_manager.o \
dsi/dsi_display_test.o \
+msm_drm-$(CONFIG_DRM_MSM_DSI) += dsi/dsi_panel_switch.o
+
msm_drm-$(CONFIG_DSI_PARSER) += dsi/dsi_parser.o \
msm_drm-$(CONFIG_DRM_MSM) += \
diff --git a/msm/dp/dp_audio.c b/msm/dp/dp_audio.c
index 90c3ad1..778b89d 100644
--- a/msm/dp/dp_audio.c
+++ b/msm/dp/dp_audio.c
@@ -602,7 +602,7 @@
rc = -ENODEV;
goto end;
}
-#if defined(CONFIG_MSM_EXT_DISPLAY)
+#if IS_ENABLED(CONFIG_MSM_EXT_DISPLAY)
rc = msm_ext_disp_register_intf(audio->ext_pdev, ext);
if (rc)
DP_ERR("failed to register disp\n");
@@ -643,7 +643,7 @@
goto end;
}
-#if defined(CONFIG_MSM_EXT_DISPLAY)
+#if IS_ENABLED(CONFIG_MSM_EXT_DISPLAY)
rc = msm_ext_disp_deregister_intf(audio->ext_pdev, ext);
if (rc)
DP_ERR("failed to deregister disp\n");
diff --git a/msm/dp/dp_aux.c b/msm/dp/dp_aux.c
index 4a1f54e..b9ae738 100644
--- a/msm/dp/dp_aux.c
+++ b/msm/dp/dp_aux.c
@@ -1,6 +1,5 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
- * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
* Copyright (c) 2012-2019, The Linux Foundation. All rights reserved.
*/
@@ -772,16 +771,11 @@
aux = container_of(dp_aux, struct dp_aux_private, dp_aux);
if (!aux->aux_switch_node) {
- DP_DEBUG("undefined aux switch handle\n");
+ DP_DEBUG("undefined fsa4480 handle\n");
rc = -EINVAL;
goto end;
}
- if (strcmp(aux->aux_switch_node->name, "fsa4480")) {
- DP_DEBUG("Not an fsa4480 aux switch\n");
- goto end;
- }
-
if (enable) {
switch (orientation) {
case ORIENTATION_CC1:
diff --git a/msm/dp/dp_debug.c b/msm/dp/dp_debug.c
index 0bccc7b..6303c1c 100644
--- a/msm/dp/dp_debug.c
+++ b/msm/dp/dp_debug.c
@@ -1,6 +1,5 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
- * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
* Copyright (c) 2017-2020, The Linux Foundation. All rights reserved.
*/
@@ -2285,7 +2284,6 @@
dp_debug->dp_mst_connector_list.con_id = -1;
dp_debug->dp_mst_connector_list.conn = NULL;
dp_debug->dp_mst_connector_list.debug_en = false;
- mutex_init(&dp_debug->dp_mst_connector_list.lock);
dp_debug->max_pclk_khz = debug->parser->max_pclk_khz;
diff --git a/msm/dp/dp_display.c b/msm/dp/dp_display.c
index 26554a4..97c5c18 100644
--- a/msm/dp/dp_display.c
+++ b/msm/dp/dp_display.c
@@ -1,6 +1,5 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
- * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
* Copyright (c) 2017-2021, The Linux Foundation. All rights reserved.
*/
@@ -722,11 +721,6 @@
kobject_uevent_env(&dev->primary->kdev->kobj, KOBJ_CHANGE,
envp);
- if (dev->mode_config.funcs->output_poll_changed)
- dev->mode_config.funcs->output_poll_changed(dev);
-
- drm_client_dev_hotplug(dev);
-
if (connector->status == connector_status_connected) {
dp_display_state_add(DP_STATE_CONNECT_NOTIFIED);
dp_display_state_remove(DP_STATE_DISCONNECT_NOTIFIED);
@@ -982,8 +976,6 @@
dp->dp_display.max_pclk_khz = min(dp->parser->max_pclk_khz,
dp->debug->max_pclk_khz);
- dp->dp_display.max_hdisplay = dp->parser->max_hdisplay;
- dp->dp_display.max_vdisplay = dp->parser->max_vdisplay;
/*
* If dp video session is not restored from a previous session teardown
@@ -2324,162 +2316,23 @@
return 0;
}
-static int dp_display_validate_resources(
- struct dp_display *dp_display,
- void *panel, struct drm_display_mode *mode,
- const struct msm_resource_caps_info *avail_res)
-{
- struct dp_display_private *dp;
- struct dp_panel *dp_panel;
- struct dp_debug *debug;
- struct dp_display_mode dp_mode;
- u32 mode_rate_khz, supported_rate_khz, mode_bpp, num_lm;
- int rc, tmds_max_clock, rate;
- bool dsc_en;
-
- dp = container_of(dp_display, struct dp_display_private, dp_display);
- dp_panel = panel;
- debug = dp->debug;
-
- dp_display->convert_to_dp_mode(dp_display, panel, mode, &dp_mode);
-
- dsc_en = dp_mode.timing.comp_info.comp_ratio ? true : false;
- mode_bpp = dsc_en ? dp_mode.timing.comp_info.dsc_info.bpp :
- dp_mode.timing.bpp;
-
- mode_rate_khz = mode->clock * mode_bpp;
- rate = drm_dp_bw_code_to_link_rate(dp->link->link_params.bw_code);
- supported_rate_khz = dp->link->link_params.lane_count * rate * 8;
- tmds_max_clock = dp_panel->connector->display_info.max_tmds_clock;
-
- if (mode_rate_khz > supported_rate_khz) {
- DP_DEBUG("pclk:%d, supported_rate:%d\n",
- mode->clock, supported_rate_khz);
- return -EINVAL;
- }
-
- if (mode->clock > dp_display->max_pclk_khz) {
- DP_DEBUG("clk:%d, max:%d\n", mode->clock,
- dp_display->max_pclk_khz);
- return -EINVAL;
- }
-
- if ((dp_display->max_hdisplay > 0) && (dp_display->max_vdisplay > 0) &&
- ((mode->hdisplay > dp_display->max_hdisplay) ||
- (mode->vdisplay > dp_display->max_vdisplay))) {
- DP_DEBUG("hdisplay:%d, max-hdisplay:%d",
- mode->hdisplay, dp_display->max_hdisplay);
- DP_DEBUG("vdisplay:%d, max-vdisplay:%d\n",
- mode->vdisplay, dp_display->max_vdisplay);
- return -EINVAL;
- }
-
- if (tmds_max_clock > 0 && mode->clock > tmds_max_clock) {
- DP_DEBUG("clk:%d, max tmds:%d\n", mode->clock,
- tmds_max_clock);
- return -EINVAL;
- }
-
- rc = msm_get_mixer_count(dp->priv, mode, avail_res, &num_lm);
- if (rc) {
- DP_ERR("error getting mixer count. rc:%d\n", rc);
- return -EINVAL;
- }
-
- if (num_lm > avail_res->num_lm ||
- (num_lm == 2 && !avail_res->num_3dmux)) {
- DP_DEBUG("num_lm:%d, req lm:%d 3dmux:%d\n", num_lm,
- avail_res->num_lm, avail_res->num_3dmux);
- return -EINVAL;
- }
-
- return 0;
-}
-
-static int dp_display_check_overrides(
- struct dp_display *dp_display,
- void *panel, struct drm_display_mode *mode,
- const struct msm_resource_caps_info *avail_res)
-{
- struct dp_mst_connector *mst_connector;
- struct dp_display_private *dp;
- struct dp_panel *dp_panel;
- struct dp_debug *debug;
- bool in_list = false;
- int hdis, vdis, vref, ar, _hdis, _vdis, _vref, _ar;
-
- dp = container_of(dp_display, struct dp_display_private, dp_display);
- dp_panel = panel;
- debug = dp->debug;
-
- /*
- * If the connector exists in the mst connector list and if debug is
- * enabled for that connector, use the mst connector settings from the
- * list for validation. Otherwise, use non-mst default settings.
- */
- mutex_lock(&debug->dp_mst_connector_list.lock);
-
- if (list_empty(&debug->dp_mst_connector_list.list)) {
- DP_MST_DEBUG("MST connect list is empty\n");
- mutex_unlock(&debug->dp_mst_connector_list.lock);
- goto verify_default;
- }
-
- list_for_each_entry(mst_connector, &debug->dp_mst_connector_list.list,
- list) {
- if (mst_connector->con_id == dp_panel->connector->base.id) {
- in_list = true;
-
- if (!mst_connector->debug_en) {
- mutex_unlock(
- &debug->dp_mst_connector_list.lock);
- return 0;
- }
-
- hdis = mst_connector->hdisplay;
- vdis = mst_connector->vdisplay;
- vref = mst_connector->vrefresh;
- ar = mst_connector->aspect_ratio;
-
- _hdis = mode->hdisplay;
- _vdis = mode->vdisplay;
- _vref = mode->vrefresh;
- _ar = mode->picture_aspect_ratio;
-
- if (hdis == _hdis && vdis == _vdis && vref == _vref &&
- ar == _ar) {
- mutex_unlock(
- &debug->dp_mst_connector_list.lock);
- return 0;
- }
- break;
- }
- }
-
- mutex_unlock(&debug->dp_mst_connector_list.lock);
- if (in_list)
- return -EINVAL;
-
-verify_default:
- if (debug->debug_en && (mode->hdisplay != debug->hdisplay ||
- mode->vdisplay != debug->vdisplay ||
- mode->vrefresh != debug->vrefresh ||
- mode->picture_aspect_ratio != debug->aspect_ratio))
- return -EINVAL;
-
- return 0;
-}
-
static enum drm_mode_status dp_display_validate_mode(
struct dp_display *dp_display,
void *panel, struct drm_display_mode *mode,
const struct msm_resource_caps_info *avail_res)
{
struct dp_display_private *dp;
-
+ u32 mode_rate_khz = 0, supported_rate_khz = 0, mode_bpp = 0;
struct dp_panel *dp_panel;
struct dp_debug *debug;
enum drm_mode_status mode_status = MODE_BAD;
+ bool in_list = false;
+ struct dp_mst_connector *mst_connector;
+ int hdis, vdis, vref, ar, _hdis, _vdis, _vref, _ar, rate;
+ struct dp_display_mode dp_mode;
+ bool dsc_en;
+ u32 num_lm = 0;
+ int rc = 0, tmds_max_clock = 0;
if (!dp_display || !mode || !panel ||
!avail_res || !avail_res->max_mixer_width) {
@@ -2498,26 +2351,109 @@
}
debug = dp->debug;
- if (!debug) {
- DP_ERR("invalid debug node\n");
+ if (!debug)
+ goto end;
+
+ dp_display->convert_to_dp_mode(dp_display, panel, mode, &dp_mode);
+
+ dsc_en = dp_mode.timing.comp_info.comp_ratio ? true : false;
+ mode_bpp = dsc_en ? dp_mode.timing.comp_info.dsc_info.bpp :
+ dp_mode.timing.bpp;
+
+ mode_rate_khz = mode->clock * mode_bpp;
+ rate = drm_dp_bw_code_to_link_rate(dp->link->link_params.bw_code);
+ supported_rate_khz = dp->link->link_params.lane_count * rate * 8;
+ tmds_max_clock = dp_panel->connector->display_info.max_tmds_clock;
+
+ if (mode_rate_khz > supported_rate_khz) {
+ DP_MST_DEBUG("pclk:%d, supported_rate:%d\n",
+ mode->clock, supported_rate_khz);
goto end;
}
- if (dp_display_validate_resources(dp_display, panel, mode, avail_res)) {
- DP_DEBUG("DP bad mode %dx%d@%d\n",
- mode->hdisplay, mode->vdisplay, mode->clock);
+ if (mode->clock > dp_display->max_pclk_khz) {
+ DP_MST_DEBUG("clk:%d, max:%d\n", mode->clock,
+ dp_display->max_pclk_khz);
goto end;
}
- if (dp_display_check_overrides(dp_display, panel,
- mode, avail_res)) {
- DP_MST_DEBUG("DP overrides ignore mode %dx%d@%d\n",
- mode->hdisplay, mode->vdisplay, mode->clock);
+ if (tmds_max_clock > 0 && mode->clock > tmds_max_clock) {
+ DP_MST_DEBUG("clk:%d, max tmds:%d\n", mode->clock,
+ tmds_max_clock);
goto end;
}
- DP_DEBUG("DP ok mode %dx%d@%d\n",
- mode->hdisplay, mode->vdisplay, mode->clock);
+ rc = msm_get_mixer_count(dp->priv, mode, avail_res, &num_lm);
+ if (rc) {
+ DP_ERR("error getting mixer count. rc:%d\n", rc);
+ goto end;
+ }
+
+ if (num_lm > avail_res->num_lm ||
+ (num_lm == 2 && !avail_res->num_3dmux)) {
+ DP_MST_DEBUG("num_lm:%d, req lm:%d 3dmux:%d\n", num_lm,
+ avail_res->num_lm, avail_res->num_3dmux);
+ goto end;
+ }
+
+ /*
+ * If the connector exists in the mst connector list and if debug is
+ * enabled for that connector, use the mst connector settings from the
+ * list for validation. Otherwise, use non-mst default settings.
+ */
+ mutex_lock(&debug->dp_mst_connector_list.lock);
+
+ if (list_empty(&debug->dp_mst_connector_list.list)) {
+ mutex_unlock(&debug->dp_mst_connector_list.lock);
+ goto verify_default;
+ }
+
+ list_for_each_entry(mst_connector, &debug->dp_mst_connector_list.list,
+ list) {
+ if (mst_connector->con_id == dp_panel->connector->base.id) {
+ in_list = true;
+
+ if (!mst_connector->debug_en) {
+ mode_status = MODE_OK;
+ mutex_unlock(
+ &debug->dp_mst_connector_list.lock);
+ goto end;
+ }
+
+ hdis = mst_connector->hdisplay;
+ vdis = mst_connector->vdisplay;
+ vref = mst_connector->vrefresh;
+ ar = mst_connector->aspect_ratio;
+
+ _hdis = mode->hdisplay;
+ _vdis = mode->vdisplay;
+ _vref = mode->vrefresh;
+ _ar = mode->picture_aspect_ratio;
+
+ if (hdis == _hdis && vdis == _vdis && vref == _vref &&
+ ar == _ar) {
+ mode_status = MODE_OK;
+ mutex_unlock(
+ &debug->dp_mst_connector_list.lock);
+ goto end;
+ }
+
+ break;
+ }
+ }
+
+ mutex_unlock(&debug->dp_mst_connector_list.lock);
+
+ if (in_list)
+ goto end;
+
+verify_default:
+ if (debug->debug_en && (mode->hdisplay != debug->hdisplay ||
+ mode->vdisplay != debug->vdisplay ||
+ mode->vrefresh != debug->vrefresh ||
+ mode->picture_aspect_ratio != debug->aspect_ratio))
+ goto end;
+
mode_status = MODE_OK;
end:
mutex_unlock(&dp->session_lock);
@@ -2720,11 +2656,6 @@
goto end;
}
- if (strcmp(dp->aux_switch_node->name, "fsa4480")) {
- DP_DEBUG("Not an fsa4480 aux switch\n");
- goto end;
- }
-
nb.notifier_call = dp_display_fsa4480_callback;
nb.priority = 0;
@@ -3362,7 +3293,7 @@
},
};
-static int __init dp_display_init(void)
+int __init dp_display_init(void)
{
int ret;
@@ -3374,10 +3305,14 @@
return ret;
}
-late_initcall(dp_display_init);
-static void __exit dp_display_cleanup(void)
+
+void __exit dp_display_cleanup(void)
{
platform_driver_unregister(&dp_display_driver);
}
+
+#ifndef CONFIG_DRM_MSM_MODULE
+late_initcall(dp_display_init);
module_exit(dp_display_cleanup);
+#endif
diff --git a/msm/dp/dp_display.h b/msm/dp/dp_display.h
index 0172889..3f4b8d9 100644
--- a/msm/dp/dp_display.h
+++ b/msm/dp/dp_display.h
@@ -1,6 +1,5 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
- * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
* Copyright (c) 2017-2021, The Linux Foundation. All rights reserved.
*/
@@ -71,8 +70,6 @@
bool is_sst_connected;
bool is_mst_supported;
u32 max_pclk_khz;
- u32 max_hdisplay;
- u32 max_vdisplay;
u32 no_mst_encoder;
void *dp_mst_prv_info;
bool is_primary;
diff --git a/msm/dp/dp_parser.c b/msm/dp/dp_parser.c
index e33f781..4c37aa2 100644
--- a/msm/dp/dp_parser.c
+++ b/msm/dp/dp_parser.c
@@ -1,6 +1,5 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
- * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
* Copyright (c) 2012-2021, The Linux Foundation. All rights reserved.
*/
@@ -171,16 +170,6 @@
if (!parser->display_type)
parser->display_type = "unknown";
- rc = of_property_read_u32(of_node,
- "qcom,max-hdisplay", &parser->max_hdisplay);
- if (rc)
- parser->max_hdisplay = 0;
-
- rc = of_property_read_u32(of_node,
- "qcom,max-vdisplay", &parser->max_vdisplay);
- if (rc)
- parser->max_vdisplay = 0;
-
return 0;
}
diff --git a/msm/dp/dp_parser.h b/msm/dp/dp_parser.h
index 61cbe38..5d937ca 100644
--- a/msm/dp/dp_parser.h
+++ b/msm/dp/dp_parser.h
@@ -1,6 +1,5 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
- * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
* Copyright (c) 2012-2021, The Linux Foundation. All rights reserved.
*/
@@ -187,8 +186,6 @@
* @l_pnswap: P/N swap status on each lane
* @max_pclk_khz: maximum pixel clock supported for the platform
* @max_lclk_khz: maximum link clock supported for the platform
- * @max_hdisplay: maximum supported horizontal display by the platform for dp
- * @max_vdisplay: maximum supported vertical display by the platform for dp
* @hw_cfg: DP HW specific settings
* @has_mst: MST feature enable status
* @has_mst_sideband: MST sideband feature enable status
@@ -220,8 +217,6 @@
struct dp_aux_cfg aux_cfg[AUX_CFG_LEN];
u32 max_pclk_khz;
u32 max_lclk_khz;
- u32 max_hdisplay;
- u32 max_vdisplay;
struct dp_hw_cfg hw_cfg;
bool has_mst;
bool has_mst_sideband;
diff --git a/msm/dsi/dsi_backlight.c b/msm/dsi/dsi_backlight.c
new file mode 100644
index 0000000..e4cb20c
--- /dev/null
+++ b/msm/dsi/dsi_backlight.c
@@ -0,0 +1,2217 @@
+/*
+ * Copyright (c) 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
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#define pr_fmt(fmt) "%s:%d: " fmt, __func__, __LINE__
+#include <linux/backlight.h>
+#include <linux/debugfs.h>
+#include <linux/of_gpio.h>
+#include <linux/pwm.h>
+#include <linux/sysfs.h>
+#include <linux/list.h>
+#include <linux/list_sort.h>
+#include <video/mipi_display.h>
+
+#include <dsi_drm.h>
+#include <sde_crtc.h>
+#include <sde_encoder.h>
+
+#include "dsi_display.h"
+#include "dsi_panel.h"
+#include "sde_connector.h"
+
+#define BL_NODE_NAME_SIZE 32
+#define BL_BRIGHTNESS_BUF_SIZE 2
+
+struct dsi_backlight_pwm_config {
+ struct pwm_device *pwm_bl;
+ bool pwm_enabled;
+ u32 pwm_period_usecs;
+};
+
+static void dsi_panel_bl_hbm_free(struct device *dev,
+ struct dsi_backlight_config *bl);
+
+static void dsi_panel_bl_notifier_free(struct device *dev,
+ struct dsi_backlight_config *bl);
+
+static int dsi_panel_bl_find_range(struct dsi_backlight_config *bl,
+ int brightness, u32 *range);
+
+static inline bool is_lp_mode(unsigned long state)
+{
+ return (state & (BL_STATE_LP | BL_STATE_LP2)) != 0;
+}
+
+static inline bool is_on_mode(unsigned long state)
+{
+ return (!is_lp_mode(state) && !is_standby_mode(state));
+}
+
+static inline unsigned int regulator_mode_from_state(unsigned long state)
+{
+ if (is_standby_mode(state))
+ return REGULATOR_MODE_STANDBY;
+ else if (is_lp_mode(state))
+ return REGULATOR_MODE_IDLE;
+ else
+ return REGULATOR_MODE_NORMAL;
+}
+
+static int dsi_panel_pwm_bl_register(struct dsi_backlight_config *bl);
+
+static void dsi_panel_bl_free_unregister(struct dsi_backlight_config *bl)
+{
+ kfree(bl->priv);
+}
+
+static int dsi_backlight_update_dcs(struct dsi_backlight_config *bl, u32 bl_lvl)
+{
+ int rc = 0;
+ struct dsi_panel *panel;
+ struct mipi_dsi_device *dsi;
+ size_t num_params;
+ const u32 hbyte = bl->high_byte_offset;
+
+ if (!bl || (bl_lvl > 0xffff)) {
+ pr_err("invalid params\n");
+ return -EINVAL;
+ }
+
+ panel = container_of(bl, struct dsi_panel, bl_config);
+ /* if no change in backlight, abort */
+ if (bl_lvl == bl->bl_actual)
+ return 0;
+
+ dsi = &panel->mipi_device;
+
+ num_params = bl->bl_max_level >= BIT(hbyte) ? 2 : 1;
+ if (num_params == 2) {
+ u8 payload[2] = { bl_lvl >> hbyte, (BIT(hbyte) - 1) & bl_lvl };
+
+ rc = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_DISPLAY_BRIGHTNESS,
+ &payload, sizeof(payload));
+ } else {
+ u8 payload = bl_lvl;
+
+ rc = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_DISPLAY_BRIGHTNESS,
+ &payload, sizeof(payload));
+ }
+
+ if (rc < 0)
+ pr_err("failed to update dcs backlight:%d\n", bl_lvl);
+
+ return rc;
+}
+
+/* Linearly interpolate value x from range [x1, x2] to determine the
+ * corresponding value in range [y1, y2].
+ */
+static int dsi_backlight_lerp(u16 x1, u16 x2, u16 y1, u16 y2, u16 x, u32 *y)
+{
+ if ((x2 < x1) || (y2 < y1))
+ return -EINVAL;
+
+ if (((x2 - x1) == 0) || (x <= x1))
+ *y = y1;
+ else if (x >= x2)
+ *y = y2;
+ else
+ *y = DIV_ROUND_CLOSEST((x - x1) * (y2 - y1), x2 - x1) + y1;
+
+ return 0;
+}
+
+static u32 dsi_backlight_calculate_normal(struct dsi_backlight_config *bl,
+ int brightness)
+{
+ u32 bl_lvl = 0;
+ int rc = 0;
+
+ if (bl->lut) {
+ /*
+ * look up panel brightness; the first entry in the LUT
+ corresponds to userspace brightness level 1
+ */
+ if (WARN_ON(brightness > bl->brightness_max_level))
+ bl_lvl = bl->lut[bl->brightness_max_level];
+ else
+ bl_lvl = bl->lut[brightness];
+ } else {
+ /* map UI brightness into driver backlight level rounding it */
+ rc = dsi_backlight_lerp(
+ 1, bl->brightness_max_level,
+ bl->bl_min_level ? : 1, bl->bl_max_level,
+ brightness, &bl_lvl);
+ if (unlikely(rc))
+ pr_err("failed to linearly interpolate, brightness unmodified\n");
+ }
+
+ pr_debug("normal bl: bl_lut %sused\n", bl->lut ? "" : "un");
+
+ return bl_lvl;
+}
+int dsi_panel_switch_update_hbm(struct dsi_panel *panel)
+{
+ if (!panel || !panel->funcs || !panel->funcs->update_hbm)
+ return -EOPNOTSUPP;
+
+ return panel->funcs->update_hbm(panel);
+}
+
+int dsi_backlight_hbm_dimming_start(struct dsi_backlight_config *bl,
+ u32 num_frames, struct dsi_panel_cmd_set *stop_cmd)
+{
+ struct hbm_data *hbm = bl->hbm;
+
+ if (!hbm || !num_frames)
+ return 0;
+
+ if (unlikely(!hbm->dimming_workq)) {
+ pr_err("hbm: tried to start dimming, but missing worker thread\n");
+ return -EINVAL;
+ }
+
+ if (!hbm->dimming_active) {
+ struct dsi_display *display =
+ dev_get_drvdata(hbm->panel->parent);
+ int rc;
+
+ if (likely(display->bridge &&
+ display->bridge->base.encoder &&
+ display->bridge->base.encoder->crtc)) {
+ rc = drm_crtc_vblank_get(
+ display->bridge->base.encoder->crtc);
+ } else {
+ pr_err("hbm: missing crtc during dimming start.\n");
+ return -EINVAL;
+ }
+
+ if (rc) {
+ pr_err("hbm: failed drm request to get vblank\n: %d",
+ rc);
+ return rc;
+ }
+ }
+
+ hbm->dimming_frames_total = num_frames;
+ hbm->dimming_frames_left = num_frames;
+ hbm->dimming_stop_cmd = stop_cmd;
+ hbm->dimming_active = true;
+
+ pr_debug("hbm dimming starting\n");
+ queue_work(hbm->dimming_workq, &hbm->dimming_work);
+
+ return 0;
+}
+
+void dsi_backlight_hbm_dimming_stop(struct dsi_backlight_config *bl)
+{
+ struct dsi_display *display;
+ struct hbm_data *hbm = bl->hbm;
+ struct dsi_panel *panel = container_of(bl, struct dsi_panel, bl_config);
+ int rc = 0;
+
+ if (!hbm || !hbm->dimming_active)
+ return;
+
+ display = dev_get_drvdata(hbm->panel->parent);
+ if (likely(display->bridge &&
+ display->bridge->base.encoder &&
+ display->bridge->base.encoder->crtc)) {
+ drm_crtc_vblank_put(display->bridge->base.encoder->crtc);
+ } else {
+ pr_err("hbm: missing crtc during dimming end.\n");
+ }
+
+ hbm->dimming_frames_total = 0;
+ hbm->dimming_frames_left = 0;
+ hbm->dimming_active = false;
+
+ if (hbm->dimming_stop_cmd) {
+ rc = dsi_panel_switch_update_hbm(panel);
+ if (rc == -EOPNOTSUPP)
+ rc = dsi_panel_cmd_set_transfer(hbm->panel,
+ hbm->dimming_stop_cmd);
+ if (rc)
+ pr_err("hbm: failed to disable brightness dimming.\n");
+ }
+
+ hbm->dimming_stop_cmd = NULL;
+
+ if (panel->hbm_pending_irc_on) {
+ rc = dsi_panel_bl_update_irc(bl, true);
+
+ if (rc)
+ pr_err("hmb sv: failed to enable IRC.\n");
+ panel->hbm_pending_irc_on = false;
+ }
+
+ pr_debug("hbm dimming stopped\n");
+}
+
+static void dsi_backlight_hbm_dimming_restart(struct dsi_backlight_config *bl)
+{
+ struct hbm_data *hbm = bl->hbm;
+
+ if (!hbm || !hbm->dimming_active)
+ return;
+
+ hbm->dimming_frames_left = hbm->dimming_frames_total;
+ pr_debug("hbm: dimming restarted\n");
+}
+
+static int dsi_backlight_hbm_wait_frame(struct hbm_data *hbm)
+{
+ struct dsi_display *display = dev_get_drvdata(hbm->panel->parent);
+
+ if (likely(display->bridge && display->bridge->base.encoder)) {
+ int rc = sde_encoder_wait_for_event(
+ display->bridge->base.encoder, MSM_ENC_VBLANK);
+ if (rc)
+ return rc;
+ } else {
+ pr_err("hbm: missing sde encoder, can't wait for vblank\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void dsi_backlight_hbm_dimming_work(struct work_struct *work)
+{
+ struct dsi_panel *panel;
+ struct hbm_data *hbm =
+ container_of(work, struct hbm_data, dimming_work);
+
+ if (!hbm)
+ return;
+
+ panel = hbm->panel;
+ while (hbm->dimming_active) {
+ int rc = dsi_backlight_hbm_wait_frame(hbm);
+
+ /*
+ * it's possible that this thread is running while the driver is
+ * attempting to shut down. if this is the case, the driver
+ * will signal for dimming to stop while holding panel_lock.
+ * so if we fail to acquire the lock, wait a bit, then check the
+ * state of dimming_active again.
+ */
+ if (!mutex_trylock(&panel->panel_lock)) {
+ usleep_range(1000, 2000);
+ continue;
+ }
+
+ pr_debug("hbm: dimming waited on frame %d of %d\n",
+ hbm->dimming_frames_left, hbm->dimming_frames_total);
+ if (!hbm->dimming_active) {
+ mutex_unlock(&panel->panel_lock);
+ break;
+ }
+
+ if (rc) {
+ pr_err("hbm: failed to wait for vblank, disabling dimming now\n");
+ hbm->dimming_frames_left = 0;
+ } else if (hbm->dimming_frames_left > 0) {
+ hbm->dimming_frames_left--;
+ }
+
+ if (!hbm->dimming_frames_left)
+ dsi_backlight_hbm_dimming_stop(&panel->bl_config);
+
+ mutex_unlock(&panel->panel_lock);
+ }
+}
+
+int dsi_backlight_hbm_find_range(struct dsi_backlight_config *bl,
+ int brightness, u32 *range)
+{
+ u32 i;
+
+ if (!bl || !bl->hbm || !range)
+ return -EINVAL;
+
+ for (i = 0; i < bl->hbm->num_ranges; i++) {
+ if (brightness <= bl->hbm->ranges[i].user_bri_end) {
+ *range = i;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static u32 dsi_backlight_calculate_hbm(struct dsi_backlight_config *bl,
+ int brightness)
+{
+ struct dsi_panel *panel = container_of(bl, struct dsi_panel, bl_config);
+ struct hbm_data *hbm = bl->hbm;
+ struct hbm_range *range = NULL;
+ u32 bl_lvl = 0;
+ int rc = 0;
+ /* It's unlikely that a brightness value of 0 will make it to this
+ * function, but if it does use the dimmest HBM range.
+ */
+ u32 target_range = 0;
+
+ if (likely(brightness)) {
+ rc = dsi_backlight_hbm_find_range(bl, brightness,
+ &target_range);
+ if (rc) {
+ pr_err("Did not find a matching HBM range for brightness %d\n",
+ brightness);
+ return bl->bl_actual;
+ }
+ }
+
+ range = hbm->ranges + target_range;
+ if (hbm->cur_range != target_range) {
+ dsi_backlight_hbm_dimming_start(bl, range->num_dimming_frames,
+ &range->dimming_stop_cmd);
+ pr_info("hbm: range %d -> %d\n", hbm->cur_range, target_range);
+ hbm->cur_range = target_range;
+
+ rc = dsi_panel_switch_update_hbm(panel);
+ if (rc == -EOPNOTSUPP)
+ rc = dsi_panel_cmd_set_transfer(panel,
+ &range->entry_cmd);
+ if (rc) {
+ pr_err("Failed to send command for range %d\n",
+ target_range);
+ return bl->bl_actual;
+ }
+ }
+
+ rc = dsi_backlight_lerp(
+ range->user_bri_start, range->user_bri_end,
+ range->panel_bri_start, range->panel_bri_end,
+ brightness, &bl_lvl);
+ if (unlikely(rc))
+ pr_err("hbm: failed to linearly interpolate, brightness unmodified\n");
+
+ pr_debug("hbm: user %d-%d, panel %d-%d\n",
+ range->user_bri_start, range->user_bri_end,
+ range->panel_bri_start, range->panel_bri_end);
+
+ return bl_lvl;
+}
+
+static u32 dsi_backlight_calculate(struct dsi_backlight_config *bl,
+ int brightness)
+{
+ struct dsi_panel *panel = container_of(bl, struct dsi_panel, bl_config);
+ u32 bl_lvl = 0;
+ u32 bl_temp;
+
+ if (brightness <= 0)
+ return 0;
+
+ /* scale backlight */
+ bl_temp = mult_frac(brightness, bl->bl_scale,
+ MAX_BL_SCALE_LEVEL);
+
+ bl_temp = mult_frac(bl_temp, bl->bl_scale_sv,
+ MAX_SV_BL_SCALE_LEVEL);
+
+ if (panel->hbm_mode != HBM_MODE_OFF)
+ bl_lvl = dsi_backlight_calculate_hbm(bl, bl_temp);
+ else
+ bl_lvl = dsi_backlight_calculate_normal(bl, bl_temp);
+
+ pr_debug("brightness=%d, bl_scale=%d, sv=%d, bl_lvl=%d, hbm = %d\n",
+ brightness, bl->bl_scale, bl->bl_scale_sv, bl_lvl,
+ panel->hbm_mode);
+
+ return bl_lvl;
+}
+
+static void dsi_panel_bl_elvss_clean_flag(struct dsi_backlight_config *bl)
+{
+ struct dynamic_elvss_data *elvss = bl->elvss;
+
+ if (!bl || !elvss)
+ return;
+
+ elvss->cur_elvss_range = DYNAMIC_ELVSS_RANGE_MAX;
+ elvss->cur_mode = ELVSS_MODE_INIT;
+ pr_debug("ELVSS: Set variables to default value\n");
+}
+
+static enum elvss_mode dsi_panel_get_elvss_mode(struct backlight_device *bd)
+{
+ struct dsi_backlight_config *bl = bl_get_data(bd);
+ struct dsi_panel *panel = container_of(bl, struct dsi_panel, bl_config);
+
+ if ((is_on_mode(bd->props.state) && dsi_panel_get_hbm(panel)) ||
+ (is_lp_mode(bd->props.state)))
+ return ELVSS_MODE_DISABLE;
+ else
+ return ELVSS_MODE_ENABLE;
+}
+
+static void dsi_panel_bl_elvss_update(struct backlight_device *bd,
+ enum ctrl_elvss elvss_update)
+{
+ struct dsi_backlight_config *bl = bl_get_data(bd);
+ struct dsi_panel *panel = container_of(bl, struct dsi_panel, bl_config);
+ struct dynamic_elvss_data *elvss = bl->elvss;
+ int rc, i;
+ enum elvss_mode mode;
+
+ if (!elvss)
+ return;
+
+ if (elvss->enable_dynamic_elvss == false)
+ mode = ELVSS_MODE_DISABLE;
+ else
+ mode = dsi_panel_get_elvss_mode(bd);
+
+ if ((mode == elvss->cur_mode) && (mode != ELVSS_MODE_ENABLE))
+ return;
+
+ switch (mode) {
+ case ELVSS_MODE_DISABLE:
+ pr_debug("ELVSS: Disable Dynamic ELVSS\n");
+
+ rc = dsi_panel_cmd_set_transfer(panel,
+ &elvss->disable_dynamic_elvss_cmd);
+ if (rc)
+ pr_err("ELVSS: [%s] failed to send disable dynamic ELVSS cmd, rc=%d\n",
+ panel->name, rc);
+
+ elvss->cur_elvss_range = DYNAMIC_ELVSS_RANGE_MAX;
+ elvss->cur_mode = ELVSS_MODE_DISABLE;
+
+ break;
+ case ELVSS_MODE_ENABLE:
+ for (i = 0; i < elvss->num_ranges; i++) {
+ if (bd->props.brightness <=
+ elvss->nodes[i].brightness_threshold)
+ break;
+ }
+
+ if (i == elvss->num_ranges) {
+ pr_warn("ELVSS: brightness is larger than brightness_threshold\n");
+ return;
+ }
+
+ if ((elvss_update == ELVSS_PRE_UPDATE &&
+ elvss->cur_elvss_range < i) ||
+ (elvss_update == ELVSS_POST_UPDATE &&
+ elvss->cur_elvss_range > i)) {
+ pr_debug("ELVSS: Update: ori_range=%d, new_range=%d, ctrl_elvss_update:%d\n",
+ elvss->cur_elvss_range, i, elvss_update);
+
+ rc = dsi_panel_cmd_set_transfer(panel,
+ &elvss->nodes[i].elvss_cmd);
+ if (rc)
+ pr_err("ELVSS: [%s] failed to send elvss_cmd, rc=%d\n",
+ panel->name, rc);
+
+ elvss->cur_elvss_range = i;
+ elvss->cur_mode = ELVSS_MODE_ENABLE;
+ }
+ break;
+ default:
+ pr_warn("ELVSS: Invalid Mode\n");
+ break;
+ }
+}
+
+static int dsi_panel_bl_parse_elvss_node(struct device *parent,
+ struct dsi_backlight_config *bl, struct device_node *np,
+ struct elvss_range *node)
+{
+ int rc;
+ u32 val;
+
+ rc = of_property_read_u32(np,
+ "google,dsi-elvss-range-brightness-threshold", &val);
+ if (rc) {
+ pr_err("Unable to parse dsi-elvss-range-brightness-threshold\n");
+ return rc;
+ }
+ if (val > bl->brightness_max_level) {
+ pr_err("elvss-range-brightness-threshold exceeds max userspace brightness\n");
+ return -EINVAL;
+ }
+ node->brightness_threshold = val;
+
+ rc = dsi_panel_parse_dt_cmd_set(np,
+ "google,dsi-elvss-range-update-command",
+ "google,dsi-elvss-range-commands-state", &node->elvss_cmd);
+ if (rc) {
+ pr_warn("Unable to parse google,dsi-elvss-range-update-command\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void dsi_panel_bl_elvss_free(struct device *dev,
+ struct dsi_backlight_config *bl)
+{
+ u32 i;
+ struct dynamic_elvss_data *elvss = bl->elvss;
+
+ if (!elvss)
+ return;
+
+ dsi_panel_destroy_cmd_packets(&elvss->disable_dynamic_elvss_cmd);
+ dsi_panel_dealloc_cmd_packets(&elvss->disable_dynamic_elvss_cmd);
+
+ for (i = 0; i < elvss->num_ranges; i++) {
+ dsi_panel_destroy_cmd_packets(&elvss->nodes[i].elvss_cmd);
+ dsi_panel_dealloc_cmd_packets(&elvss->nodes[i].elvss_cmd);
+ }
+
+ devm_kfree(dev, elvss);
+ bl->elvss = NULL;
+}
+
+void dsi_panel_debugfs_create_dynamic_elvss(struct dentry *parent,
+ struct dynamic_elvss_data *elvss)
+{
+ if (!parent || !elvss)
+ return;
+
+ debugfs_create_bool("enable_dynamic_elvss", 0600,
+ parent, &elvss->enable_dynamic_elvss);
+
+}
+
+static int dsi_panel_bl_parse_dynamic_elvss(struct device *parent,
+ struct dsi_backlight_config *bl, struct device_node *of_node)
+{
+ struct device_node *elvss_ranges_np;
+ struct device_node *child_np;
+ u32 rc, i = 0, num_ranges;
+
+ elvss_ranges_np = of_get_child_by_name(of_node, "google,elvss-ranges");
+ if (!elvss_ranges_np) {
+ pr_info("ELVSS modes list not found\n");
+ return 0;
+ }
+
+ num_ranges = of_get_child_count(elvss_ranges_np);
+ if (!num_ranges || (num_ranges > DYNAMIC_ELVSS_RANGE_MAX)) {
+ pr_err("Invalid number of ELVSS modes: %d\n", num_ranges);
+ return -EINVAL;
+ }
+
+ bl->elvss = devm_kzalloc(parent, sizeof(struct dynamic_elvss_data),
+ GFP_KERNEL);
+ if (bl->elvss == NULL) {
+ pr_err("Failed to allocate memory for dynamic elvss data\n");
+ return -ENOMEM;
+ }
+
+ rc = dsi_panel_parse_dt_cmd_set(elvss_ranges_np,
+ "google,dsi-elvss-range-off-command",
+ "google,dsi-elvss-range-commands-state",
+ &bl->elvss->disable_dynamic_elvss_cmd);
+ if (rc) {
+ devm_kfree(parent, bl->elvss);
+ bl->elvss = NULL;
+ pr_err("Unable to parse dsi-elvss-range-off-command\n");
+ return -EINVAL;
+ }
+
+ bl->elvss->num_ranges = num_ranges;
+
+ for_each_child_of_node(elvss_ranges_np, child_np) {
+ rc = dsi_panel_bl_parse_elvss_node(parent, bl,
+ child_np, bl->elvss->nodes + i);
+ if (rc) {
+ bl->elvss->num_ranges = i;
+ pr_err("Failed to parse ELVSS range %d\n", i);
+ goto exit_free;
+ }
+ i++;
+ }
+
+ for (i = 0; i < num_ranges - 1; i++) {
+ /* Make sure ranges are sorted and not overlapping */
+ if (bl->elvss->nodes[i].brightness_threshold >=
+ bl->elvss->nodes[i + 1].brightness_threshold) {
+ pr_err("ELVSS nodes must be sorted by elvss-brightness-threshold\n");
+ rc = -EINVAL;
+ goto exit_free;
+ }
+ }
+
+ dsi_panel_bl_elvss_clean_flag(bl);
+
+ bl->elvss->enable_dynamic_elvss = true;
+
+ return 0;
+
+exit_free:
+ dsi_panel_bl_elvss_free(parent, bl);
+ return rc;
+}
+
+static int dsi_backlight_update_status(struct backlight_device *bd)
+{
+ struct dsi_backlight_config *bl = bl_get_data(bd);
+ struct dsi_panel *panel = container_of(bl, struct dsi_panel, bl_config);
+ struct dsi_display *display;
+ int brightness = bd->props.brightness;
+ int bl_lvl;
+ int rc = 0;
+ bool need_notify = false;
+
+ mutex_lock(&panel->panel_lock);
+ mutex_lock(&bl->state_lock);
+ if ((bd->props.state & (BL_CORE_FBBLANK | BL_CORE_SUSPENDED)) ||
+ (bd->props.power != FB_BLANK_UNBLANK))
+ brightness = 0;
+
+ bl_lvl = dsi_backlight_calculate(bl, brightness);
+ if (bl_lvl == bl->bl_actual && bl->last_state == bd->props.state)
+ goto done;
+
+ if (!bl->allow_bl_update) {
+ bl->bl_update_pending = true;
+ goto done;
+ }
+
+ dsi_backlight_hbm_dimming_restart(bl);
+
+ if (dsi_panel_initialized(panel) && bl->update_bl) {
+ pr_info("req:%d bl:%d state:0x%x\n",
+ bd->props.brightness, bl_lvl, bd->props.state);
+
+ dsi_panel_bl_elvss_update(bd, ELVSS_PRE_UPDATE);
+
+ rc = bl->update_bl(bl, bl_lvl);
+ if (rc) {
+ pr_err("unable to set backlight (%d)\n", rc);
+ goto done;
+ }
+
+ dsi_panel_bl_elvss_update(bd, ELVSS_POST_UPDATE);
+
+ bl->bl_update_pending = false;
+ need_notify = true;
+ if (bl->bl_notifier && is_on_mode(bd->props.state)
+ && !(dsi_panel_get_hbm(panel))) {
+ u32 target_range = 0;
+
+ rc = dsi_panel_bl_find_range(bl, brightness, &target_range);
+ if (rc) {
+ pr_err("unable to find range from the backlight table (%d)\n", rc);
+ } else if (bl->bl_notifier->cur_range != target_range) {
+ bl->bl_notifier->cur_range = target_range;
+ sysfs_notify(&bd->dev.kobj, NULL, "brightness");
+ pr_debug("cur_range = %d, brightness = %d\n",
+ bl->bl_notifier->cur_range, brightness);
+ }
+ }
+ }
+ bl->bl_actual = bl_lvl;
+ bl->last_state = bd->props.state;
+
+done:
+ mutex_unlock(&bl->state_lock);
+ mutex_unlock(&panel->panel_lock);
+
+ /* skip notifying user space if bl is 0 */
+ if (likely(need_notify && brightness)) {
+ display = dev_get_drvdata(panel->parent);
+ if (unlikely(!display))
+ return rc;
+
+ sde_connector_event_notify(display->drm_conn,
+ DRM_EVENT_SYS_BACKLIGHT, sizeof(u32), brightness);
+ }
+
+ return rc;
+}
+
+static int dsi_backlight_get_brightness(struct backlight_device *bd)
+{
+ struct dsi_backlight_config *bl = bl_get_data(bd);
+
+ return bl->bl_actual;
+}
+
+static const struct backlight_ops dsi_backlight_ops = {
+ .update_status = dsi_backlight_update_status,
+ .get_brightness = dsi_backlight_get_brightness,
+};
+
+static ssize_t alpm_mode_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct backlight_device *bd = to_backlight_device(dev);
+ struct dsi_backlight_config *bl = bl_get_data(bd);
+ struct dsi_panel *panel = container_of(bl, struct dsi_panel, bl_config);
+ int rc, alpm_mode;
+ const unsigned int lp_state = bl->bl_device->props.state &
+ (BL_STATE_LP | BL_STATE_LP2);
+
+ rc = kstrtoint(buf, 0, &alpm_mode);
+ if (rc)
+ return rc;
+
+ if (bl->bl_device->props.state & BL_CORE_FBBLANK) {
+ return -EINVAL;
+ } else if ((alpm_mode == 1) && (lp_state != BL_STATE_LP)) {
+ pr_info("activating lp1 mode\n");
+ dsi_panel_set_lp1(panel);
+ } else if ((alpm_mode > 1) && !(lp_state & BL_STATE_LP2)) {
+ pr_info("activating lp2 mode\n");
+ dsi_panel_set_lp2(panel);
+ } else if (!alpm_mode && lp_state) {
+ pr_info("activating normal mode\n");
+ dsi_panel_set_nolp(panel);
+ }
+
+ return count;
+}
+
+static ssize_t alpm_mode_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct backlight_device *bd = to_backlight_device(dev);
+ int alpm_mode;
+
+ if (bd->props.state & BL_STATE_LP2)
+ alpm_mode = 2;
+ else
+ alpm_mode = (bd->props.state & BL_STATE_LP) != 0;
+
+ return sprintf(buf, "%d\n", alpm_mode);
+}
+static DEVICE_ATTR_RW(alpm_mode);
+
+static ssize_t hbm_mode_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct backlight_device *bd = NULL;
+ struct dsi_backlight_config *bl = NULL;
+ struct dsi_panel *panel = NULL;
+ int rc = 0;
+ int hbm_mode = 0;
+
+ /* dev is non-NULL, enforced by sysfs_create_file_ns */
+ bd = to_backlight_device(dev);
+ bl = bl_get_data(bd);
+
+ if (!bl->hbm)
+ return -ENOTSUPP;
+
+ rc = kstrtoint(buf, 10, &hbm_mode);
+ if (rc)
+ return rc;
+
+ panel = container_of(bl, struct dsi_panel, bl_config);
+ rc = dsi_panel_update_hbm(panel, hbm_mode);
+ if (rc) {
+ pr_err("hbm_mode store failed: %d\n", rc);
+ return rc;
+ }
+ pr_debug("hbm_mode set to %d\n", panel->hbm_mode);
+
+ return count;
+}
+
+static ssize_t hbm_mode_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct backlight_device *bd = NULL;
+ struct dsi_backlight_config *bl = NULL;
+ struct dsi_panel *panel = NULL;
+ int hbm_mode = false;
+
+ /* dev is non-NULL, enforced by sysfs_create_file_ns */
+ bd = to_backlight_device(dev);
+ bl = bl_get_data(bd);
+
+ if (!bl->hbm)
+ return snprintf(buf, PAGE_SIZE, "unsupported\n");
+
+ panel = container_of(bl, struct dsi_panel, bl_config);
+ hbm_mode = dsi_panel_get_hbm(panel);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", hbm_mode);
+}
+
+static DEVICE_ATTR_RW(hbm_mode);
+
+static ssize_t hbm_sv_enabled_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct backlight_device *bd;
+ struct dsi_backlight_config *bl;
+ struct dsi_panel *panel;
+ int rc = 0;
+ bool hbm_sv_enabled = false;
+
+ /* dev is non-NULL, enforced by sysfs_create_file_ns */
+ bd = to_backlight_device(dev);
+ bl = bl_get_data(bd);
+
+ if (!bl->hbm)
+ return -ENOTSUPP;
+
+ rc = kstrtobool(buf, &hbm_sv_enabled);
+ if (rc)
+ return rc;
+
+ panel = container_of(bl, struct dsi_panel, bl_config);
+ if (!hbm_sv_enabled && panel->hbm_mode == HBM_MODE_SV)
+ return -EBUSY;
+
+ panel->hbm_sv_enabled = hbm_sv_enabled;
+
+ return count;
+}
+
+static ssize_t hbm_sv_enabled_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct backlight_device *bd;
+ struct dsi_backlight_config *bl;
+ struct dsi_panel *panel;
+
+ /* dev is non-NULL, enforced by sysfs_create_file_ns */
+ bd = to_backlight_device(dev);
+ bl = bl_get_data(bd);
+
+ if (!bl->hbm)
+ return snprintf(buf, PAGE_SIZE, "unsupported\n");
+
+ panel = container_of(bl, struct dsi_panel, bl_config);
+ return snprintf(buf, PAGE_SIZE, "%s\n",
+ panel->hbm_sv_enabled ? "true" : "false");
+}
+
+static DEVICE_ATTR_RW(hbm_sv_enabled);
+
+static ssize_t state_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct backlight_device *bd = to_backlight_device(dev);
+ struct dsi_backlight_config *bl = bl_get_data(bd);
+ struct dsi_panel *panel = container_of(bl,
+ struct dsi_panel, bl_config);
+ bool show_mode = false;
+ char *statestr;
+ int rc;
+
+ mutex_lock(&bl->state_lock);
+ if (is_standby_mode(bd->props.state)) {
+ statestr = "Off";
+ } else if (is_lp_mode(bd->props.state)) {
+ statestr = "LP";
+ } else {
+ show_mode = true;
+ if (dsi_panel_get_hbm(panel))
+ statestr = "HBM";
+ else
+ statestr = "On";
+ }
+ mutex_unlock(&bl->state_lock);
+
+ if (show_mode) {
+ const struct dsi_display_mode *mode =
+ get_panel_display_mode(panel);
+
+ if (unlikely(!mode))
+ return -ENODEV;
+
+ rc = snprintf(buf, PAGE_SIZE, "%s: %dx%d@%d\n", statestr,
+ mode->timing.h_active, mode->timing.v_active,
+ mode->timing.refresh_rate);
+ } else {
+ rc = snprintf(buf, PAGE_SIZE, "%s\n", statestr);
+ }
+
+ return rc;
+}
+
+static DEVICE_ATTR_RO(state);
+
+int parse_u32_buf(char *src, size_t src_len, u32 *out, size_t out_len)
+{
+ int rc = 0, cnt = 0;
+ char *str;
+ const char *delim = " ";
+
+ if (unlikely(!src || !src_len || !out || !out_len))
+ return -EINVAL;
+
+ /* src_len is the length of src including null character '\0' */
+ if (strnlen(src, src_len) == src_len)
+ return -EINVAL;
+
+ for (str = strsep(&src, delim); str != NULL; str = strsep(&src, delim)) {
+ rc = kstrtou32(str, 0, out + cnt);
+ if (rc)
+ return -EINVAL;
+
+ cnt++;
+
+ if (out_len == cnt)
+ break;
+ }
+
+ return cnt;
+}
+
+static ssize_t als_table_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+
+ struct backlight_device *bd = to_backlight_device(dev);
+ struct dsi_backlight_config *bl = bl_get_data(bd);
+ ssize_t als_count, buf_dup_len;
+ u8 i;
+ u32 ranges[BL_RANGE_MAX] = {0};
+ char *buf_dup;
+
+ if (unlikely(!bl || !bl->bl_notifier))
+ return -EINVAL;
+
+ if (count == 0)
+ return -EINVAL;
+
+ buf_dup = kstrndup(buf, count, GFP_KERNEL);
+ if (!buf_dup)
+ return -ENOMEM;
+
+ buf_dup_len = strlen(buf_dup) + 1;
+
+ als_count = parse_u32_buf(buf_dup, buf_dup_len, ranges, BL_RANGE_MAX);
+ if ((als_count < 0) || (als_count > BL_RANGE_MAX)) {
+ kfree(buf_dup);
+ pr_warn("als: incorrect parameters from als table node\n");
+ return -EINVAL;
+ }
+
+ mutex_lock(&bl->state_lock);
+
+ bl->bl_notifier->num_ranges = als_count;
+ for (i = 0; i < bl->bl_notifier->num_ranges; i++)
+ bl->bl_notifier->ranges[i] = ranges[i];
+
+ mutex_unlock(&bl->state_lock);
+
+ kfree(buf_dup);
+
+ return count;
+}
+
+static ssize_t als_table_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct backlight_device *bd = to_backlight_device(dev);
+ struct dsi_backlight_config *bl = bl_get_data(bd);
+ ssize_t rc = 0;
+ size_t len = 0;
+ u32 i = 0;
+
+ if (unlikely(!bl || !bl->bl_notifier))
+ return -EINVAL;
+
+ mutex_lock(&bl->state_lock);
+
+ for (i = 0; i < bl->bl_notifier->num_ranges; i++) {
+ rc = scnprintf((buf + len), PAGE_SIZE - len,
+ "%u ", bl->bl_notifier->ranges[i]);
+ if (rc < 0) {
+ pr_warn("als: incorrect bl_notifier ranges\n");
+ mutex_unlock(&bl->state_lock);
+ return -EINVAL;
+ }
+ len = len + rc;
+ }
+
+ mutex_unlock(&bl->state_lock);
+
+ len += scnprintf(buf + len, PAGE_SIZE - len, "\n");
+
+ return len;
+}
+static DEVICE_ATTR_RW(als_table);
+
+static struct attribute *bl_device_attrs[] = {
+ &dev_attr_alpm_mode.attr,
+ &dev_attr_hbm_mode.attr,
+ &dev_attr_hbm_sv_enabled.attr,
+ &dev_attr_state.attr,
+ &dev_attr_als_table.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(bl_device);
+
+static int dsi_backlight_register(struct dsi_backlight_config *bl)
+{
+ static int display_count;
+ char bl_node_name[BL_NODE_NAME_SIZE];
+ struct backlight_properties props = {
+ .type = BACKLIGHT_RAW,
+ .power = FB_BLANK_UNBLANK,
+ };
+ struct dsi_panel *panel = container_of(bl, struct dsi_panel, bl_config);
+ struct regulator *reg;
+
+ props.max_brightness = bl->brightness_max_level;
+ props.brightness = bl->brightness_max_level / 2;
+
+ snprintf(bl_node_name, BL_NODE_NAME_SIZE, "panel%u-backlight",
+ display_count);
+ bl->bl_device = devm_backlight_device_register(panel->parent,
+ bl_node_name, panel->parent, bl,
+ &dsi_backlight_ops, &props);
+ if (IS_ERR_OR_NULL(bl->bl_device)) {
+ bl->bl_device = NULL;
+ return -ENODEV;
+ }
+
+ if (sysfs_create_groups(&bl->bl_device->dev.kobj, bl_device_groups))
+ pr_warn("unable to create device groups\n");
+
+ reg = regulator_get_optional(panel->parent, "lab");
+ if (!PTR_ERR_OR_ZERO(reg)) {
+ pr_info("LAB regulator found\n");
+ panel->bl_config.lab_vreg = reg;
+ }
+
+ display_count++;
+ return 0;
+}
+
+static unsigned long get_state_after_dpms(struct dsi_backlight_config *bl,
+ int power_mode)
+{
+ struct backlight_device *bd = bl->bl_device;
+ unsigned long state = bd->props.state;
+
+ switch (power_mode) {
+ case SDE_MODE_DPMS_ON:
+ state &= ~(BL_CORE_FBBLANK | BL_STATE_LP | BL_STATE_LP2);
+ break;
+ case SDE_MODE_DPMS_OFF:
+ state &= ~(BL_STATE_LP | BL_STATE_LP2);
+ state |= BL_CORE_FBBLANK;
+ break;
+ case SDE_MODE_DPMS_LP1:
+ state |= BL_STATE_LP;
+ state &= ~BL_STATE_LP2;
+ break;
+ case SDE_MODE_DPMS_LP2:
+ state |= BL_STATE_LP | BL_STATE_LP2;
+ break;
+ }
+
+ return state;
+}
+
+static int dsi_backlight_update_regulator(struct dsi_backlight_config *bl,
+ unsigned int state)
+{
+ int rc = 0;
+
+ if (bl->lab_vreg) {
+ const unsigned int mode = regulator_mode_from_state(state);
+ const unsigned int last_mode =
+ regulator_mode_from_state(bl->last_state);
+
+ if (last_mode != mode) {
+ pr_debug("set lab vreg mode: 0x%0x\n", mode);
+ rc = regulator_set_mode(bl->lab_vreg, mode);
+ }
+ }
+
+ return rc;
+}
+
+int dsi_backlight_early_dpms(struct dsi_backlight_config *bl, int power_mode)
+{
+ struct backlight_device *bd = bl->bl_device;
+ unsigned long state;
+ int rc = 0;
+
+ if (!bd)
+ return 0;
+
+ pr_info("power_mode:%d state:0x%0x\n", power_mode, bd->props.state);
+
+ mutex_lock(&bl->state_lock);
+ state = get_state_after_dpms(bl, power_mode);
+
+ if (is_lp_mode(state)) {
+ rc = dsi_backlight_update_regulator(bl, state);
+ if (rc)
+ pr_warn("Error updating regulator state: 0x%x (%d)\n",
+ state, rc);
+ }
+ mutex_unlock(&bl->state_lock);
+
+ return rc;
+}
+
+int dsi_backlight_late_dpms(struct dsi_backlight_config *bl, int power_mode)
+{
+ struct backlight_device *bd = bl->bl_device;
+ unsigned long state;
+
+ if (!bd)
+ return 0;
+
+ pr_debug("power_mode:%d state:0x%0x\n", power_mode, bd->props.state);
+
+ mutex_lock(&bl->state_lock);
+ state = get_state_after_dpms(bl, power_mode);
+
+ if (!is_lp_mode(state)) {
+ const int rc = dsi_backlight_update_regulator(bl, state);
+
+ if (rc)
+ pr_warn("Error updating regulator state: 0x%x (%d)\n",
+ state, rc);
+ }
+
+ bd->props.power = state & BL_CORE_FBBLANK ? FB_BLANK_POWERDOWN :
+ FB_BLANK_UNBLANK;
+ bd->props.state = state;
+
+ /* The dynamic elvss register will be restored to
+ * the default OTP's value automatically when the
+ * panel is power off(HW behavior). We need to set
+ * the variables to default value for this kind of
+ * case. When the device comes back from panel off
+ * to other modes, the dynamic elvss will be updated.
+ */
+ if (bd->props.power == FB_BLANK_POWERDOWN)
+ dsi_panel_bl_elvss_clean_flag(bl);
+
+ mutex_unlock(&bl->state_lock);
+ backlight_update_status(bd);
+ sysfs_notify(&bd->dev.kobj, NULL, "state");
+
+ pr_info("sysfs_notify state:0x%0x\n", bd->props.state);
+
+ return 0;
+}
+
+#define MAX_BINNED_BL_MODES 10
+
+struct binned_lp_node {
+ struct list_head head;
+ const char *name;
+ u32 bl_threshold;
+ struct dsi_panel_cmd_set dsi_cmd;
+ struct dentry *mode_dir;
+};
+
+struct binned_lp_data {
+ struct list_head mode_list;
+ struct binned_lp_node *last_lp_mode;
+ struct binned_lp_node priv_pool[MAX_BINNED_BL_MODES];
+};
+
+static int dsi_panel_te2_lp_mode_update(struct dsi_panel *panel,
+ struct binned_lp_node *node)
+{
+ int rc;
+
+ if (unlikely(!panel->funcs || !panel->funcs->update_te2))
+ return -EINVAL;
+
+ if (unlikely(!panel->te2_config.te2_ready))
+ return -EINVAL;
+
+ if (node->bl_threshold > panel->te2_config.lp_threshold)
+ panel->te2_config.current_type = TE2_EDGE_LP_HIGH;
+ else
+ panel->te2_config.current_type = TE2_EDGE_LP_LOW;
+
+ rc = panel->funcs->update_te2(panel);
+ if (rc < 0)
+ pr_warn("TE2: LP '%s' mode failed, rc=%d\n", node->name, rc);
+
+ return rc;
+}
+
+static int dsi_panel_binned_bl_update(struct dsi_backlight_config *bl,
+ u32 bl_lvl)
+{
+ struct dsi_panel *panel = container_of(bl, struct dsi_panel, bl_config);
+ struct binned_lp_data *lp_data = bl->priv;
+ struct binned_lp_node *node = NULL;
+ struct backlight_properties *props = &bl->bl_device->props;
+
+ if (is_lp_mode(props->state)) {
+ struct binned_lp_node *tmp;
+
+ list_for_each_entry(tmp, &lp_data->mode_list, head) {
+ if (props->brightness <= tmp->bl_threshold) {
+ node = tmp;
+ break;
+ }
+ }
+ WARN(!node, "unable to find lp node for bl_lvl: %d\n",
+ props->brightness);
+ }
+
+ if (node != lp_data->last_lp_mode) {
+ lp_data->last_lp_mode = node;
+ if (node) {
+ pr_debug("switching display lp mode: %s (%d)\n",
+ node->name, props->brightness);
+ dsi_panel_cmd_set_transfer(panel, &node->dsi_cmd);
+
+ dsi_panel_te2_lp_mode_update(panel, node);
+ } else {
+ /* ensure update after lpm */
+ bl->bl_actual = -1;
+ }
+ }
+
+ /* no need to send backlight command if HLPM active */
+ if (node)
+ return 0;
+
+ return dsi_backlight_update_dcs(bl, bl_lvl);
+}
+
+static int _dsi_panel_binned_lp_parse(struct device_node *np,
+ struct binned_lp_node *node)
+{
+ int rc;
+ u32 val = 0;
+
+ rc = of_property_read_u32(np, "google,dsi-lp-brightness-threshold",
+ &val);
+ /* treat lack of property as max threshold */
+ node->bl_threshold = !rc ? val : UINT_MAX;
+
+ rc = dsi_panel_parse_dt_cmd_set(np, "google,dsi-lp-command",
+ "google,dsi-lp-command-state",
+ &node->dsi_cmd);
+ if (rc) {
+ pr_err("Unable to parse dsi-lp-command\n");
+ return rc;
+ }
+
+ of_property_read_string(np, "label", &node->name);
+
+ pr_debug("Successfully parsed lp mode: %s threshold: %d\n",
+ node->name, node->bl_threshold);
+
+ return 0;
+}
+
+static int _dsi_panel_binned_bl_cmp(void *priv, struct list_head *lha,
+ struct list_head *lhb)
+{
+ struct binned_lp_node *a = list_entry(lha, struct binned_lp_node, head);
+ struct binned_lp_node *b = list_entry(lhb, struct binned_lp_node, head);
+
+ return a->bl_threshold - b->bl_threshold;
+}
+
+void dsi_panel_debugfs_create_binned_bl(struct dentry *parent,
+ struct dsi_backlight_config *bl)
+{
+ struct dentry *r, *file;
+ struct binned_lp_data *lp_data;
+ struct binned_lp_node *tmp;
+ struct dsi_panel *panel = container_of(bl, struct dsi_panel, bl_config);
+
+ r = debugfs_create_dir("lp_modes", parent);
+ if (IS_ERR(r)) {
+ pr_err("create debugfs lp_modes failed\n");
+ return;
+ }
+
+ lp_data = bl->priv;
+
+ list_for_each_entry(tmp, &lp_data->mode_list, head) {
+ tmp->mode_dir = debugfs_create_dir(tmp->name, r);
+ if (IS_ERR(tmp->mode_dir)) {
+ pr_err("create debugfs binned_bl failed\n");
+ goto error;
+ }
+
+ file = debugfs_create_u32("threshold", 0600,
+ tmp->mode_dir,
+ &tmp->bl_threshold);
+ if (IS_ERR_OR_NULL(file)) {
+ pr_err("debugfs create threshold file failed\n");
+ goto error;
+ }
+
+ if (dsi_panel_debugfs_create_cmdset(tmp->mode_dir, "cmd",
+ panel, &tmp->dsi_cmd)) {
+ pr_err("debugfs create cmd file failed\n");
+ goto error;
+ }
+ }
+
+ return;
+
+error:
+ debugfs_remove_recursive(r);
+}
+
+void dsi_panel_bl_elvss_debugfs_init(struct dentry *parent,
+ struct dsi_panel *panel)
+{
+ struct dsi_backlight_config *bl = &panel->bl_config;
+ struct dynamic_elvss_data *elvss;
+
+ if (!parent || !bl->elvss)
+ return;
+
+ elvss = bl->elvss;
+
+ dsi_panel_debugfs_create_dynamic_elvss(parent, elvss);
+}
+
+static int dsi_panel_binned_lp_register(struct dsi_backlight_config *bl)
+{
+ struct dsi_panel *panel = container_of(bl, struct dsi_panel, bl_config);
+ struct binned_lp_data *lp_data;
+ struct device_node *lp_modes_np, *child_np;
+ struct binned_lp_node *lp_node;
+ int num_modes;
+ int rc = -ENOTSUPP;
+
+ lp_data = kzalloc(sizeof(*lp_data), GFP_KERNEL);
+ if (!lp_data)
+ return -ENOMEM;
+
+ lp_modes_np = of_get_child_by_name(panel->panel_of_node,
+ "google,lp-modes");
+
+ if (!lp_modes_np) {
+ kfree(lp_data);
+ return rc;
+ }
+
+ num_modes = of_get_child_count(lp_modes_np);
+ if (!num_modes || (num_modes > MAX_BINNED_BL_MODES)) {
+ pr_err("Invalid binned brightness modes: %d\n", num_modes);
+ goto exit;
+ }
+
+ INIT_LIST_HEAD(&lp_data->mode_list);
+ lp_node = lp_data->priv_pool;
+
+ for_each_child_of_node(lp_modes_np, child_np) {
+ rc = _dsi_panel_binned_lp_parse(child_np, lp_node);
+ if (rc)
+ goto exit;
+
+ list_add_tail(&lp_node->head, &lp_data->mode_list);
+ lp_node++;
+ if (lp_node > &lp_data->priv_pool[MAX_BINNED_BL_MODES - 1]) {
+ pr_err("Too many LP modes\n");
+ rc = -ENOTSUPP;
+ goto exit;
+ }
+ }
+ list_sort(NULL, &lp_data->mode_list, _dsi_panel_binned_bl_cmp);
+
+ bl->update_bl = dsi_panel_binned_bl_update;
+ bl->unregister = dsi_panel_bl_free_unregister;
+ bl->debugfs_init = dsi_panel_debugfs_create_binned_bl;
+ bl->priv = lp_data;
+
+exit:
+ of_node_put(lp_modes_np);
+ if (rc)
+ kfree(lp_data);
+
+ return rc;
+}
+
+static const struct of_device_id dsi_backlight_dt_match[] = {
+ {
+ .compatible = "google,dsi_binned_lp",
+ .data = dsi_panel_binned_lp_register,
+ },
+ {}
+};
+
+void dsi_panel_bl_debugfs_init(struct dentry *parent, struct dsi_panel *panel)
+{
+ struct dsi_backlight_config *bl = &panel->bl_config;
+
+ if (bl->debugfs_init)
+ bl->debugfs_init(parent, bl);
+}
+
+int dsi_backlight_get_dpms(struct dsi_backlight_config *bl)
+{
+ struct backlight_device *bd = bl->bl_device;
+ int power = 0;
+ int state = 0;
+
+ mutex_lock(&bl->state_lock);
+ power = bd->props.power;
+ state = bd->props.state;
+ mutex_unlock(&bl->state_lock);
+
+ if (power == FB_BLANK_POWERDOWN)
+ return SDE_MODE_DPMS_OFF;
+ else if (state & BL_STATE_LP2)
+ return SDE_MODE_DPMS_LP2;
+ else if (state & BL_STATE_LP)
+ return SDE_MODE_DPMS_LP1;
+ else
+ return SDE_MODE_DPMS_ON;
+}
+
+static int dsi_panel_bl_parse_hbm_node(struct device *parent,
+ struct dsi_backlight_config *bl, struct device_node *np,
+ struct hbm_range *range)
+{
+ int rc;
+ u32 val = 0;
+
+ rc = of_property_read_u32(np,
+ "google,dsi-hbm-range-brightness-threshold", &val);
+ if (rc) {
+ pr_err("Unable to parse dsi-hbm-range-brightness-threshold");
+ return rc;
+ }
+ if (val > bl->brightness_max_level) {
+ pr_err("hbm-brightness-threshold exceeds max userspace brightness\n");
+ return rc;
+ }
+ range->user_bri_start = val;
+
+ rc = of_property_read_u32(np, "google,dsi-hbm-range-bl-min-level",
+ &val);
+ if (rc) {
+ pr_err("dsi-hbm-range-bl-min-level unspecified\n");
+ return rc;
+ }
+ range->panel_bri_start = val;
+
+ rc = of_property_read_u32(np, "google,dsi-hbm-range-bl-max-level",
+ &val);
+ if (rc) {
+ pr_err("dsi-hbm-range-bl-max-level unspecified\n");
+ return rc;
+ }
+ if (val < range->panel_bri_start) {
+ pr_err("Invalid HBM panel brightness range: bl-hbm-max-level < bl-hbm-min-level\n");
+ return rc;
+ }
+ range->panel_bri_end = val;
+
+ rc = dsi_panel_parse_dt_cmd_set(np,
+ "google,dsi-hbm-range-entry-command",
+ "google,dsi-hbm-range-commands-state", &range->entry_cmd);
+ if (rc)
+ pr_info("Unable to parse optional dsi-hbm-range-entry-command\n");
+
+ rc = of_property_read_u32(np,
+ "google,dsi-hbm-range-num-dimming-frames", &val);
+ if (rc) {
+ pr_debug("Unable to parse optional hbm-range-entry-num-dimming-frames\n");
+ range->num_dimming_frames = 0;
+ } else {
+ range->num_dimming_frames = val;
+ }
+
+ rc = dsi_panel_parse_dt_cmd_set(np,
+ "google,dsi-hbm-range-dimming-stop-command",
+ "google,dsi-hbm-range-commands-state",
+ &range->dimming_stop_cmd);
+ if (rc)
+ pr_debug("Unable to parse optional dsi-hbm-range-dimming-stop-command\n");
+
+ if ((range->dimming_stop_cmd.count && !range->num_dimming_frames) ||
+ (!range->dimming_stop_cmd.count && range->num_dimming_frames)) {
+ pr_err("HBM dimming requires both stop command and number of frames.\n");
+ return -EINVAL;
+ }
+ return 0;
+}
+
+int dsi_panel_bl_register(struct dsi_panel *panel)
+{
+ int rc = 0;
+ struct dsi_backlight_config *bl = &panel->bl_config;
+ const struct of_device_id *match;
+ int (*register_func)(struct dsi_backlight_config *) = NULL;
+
+ mutex_init(&bl->state_lock);
+ match = of_match_node(dsi_backlight_dt_match, panel->panel_of_node);
+ if (match && match->data) {
+ register_func = match->data;
+ rc = register_func(bl);
+ }
+
+ if (!register_func || (rc == -ENOTSUPP)) {
+ switch (bl->type) {
+ case DSI_BACKLIGHT_WLED:
+ break;
+ case DSI_BACKLIGHT_DCS:
+ bl->update_bl = dsi_backlight_update_dcs;
+ break;
+ case DSI_BACKLIGHT_PWM:
+ register_func = dsi_panel_pwm_bl_register;
+ break;
+ default:
+ pr_err("Backlight type(%d) not supported\n", bl->type);
+ rc = -ENOTSUPP;
+ break;
+ }
+
+ if (register_func)
+ rc = register_func(bl);
+ }
+
+ if (!rc)
+ rc = dsi_backlight_register(bl);
+
+ return rc;
+}
+
+
+int dsi_panel_bl_unregister(struct dsi_panel *panel)
+{
+ struct dsi_backlight_config *bl = &panel->bl_config;
+
+ mutex_destroy(&bl->state_lock);
+ if (bl->unregister)
+ bl->unregister(bl);
+
+ if (bl->bl_device)
+ sysfs_remove_groups(&bl->bl_device->dev.kobj, bl_device_groups);
+
+ dsi_panel_bl_hbm_free(panel->parent, bl);
+ dsi_panel_bl_elvss_free(panel->parent, bl);
+ dsi_panel_bl_notifier_free(panel->parent, bl);
+
+ return 0;
+}
+
+static int dsi_panel_bl_parse_pwm_config(struct dsi_panel *panel,
+ struct dsi_backlight_pwm_config *config)
+{
+ int rc = 0;
+ u32 val;
+ struct dsi_parser_utils *utils = &panel->utils;
+
+ rc = utils->read_u32(utils->data, "qcom,bl-pmic-pwm-period-usecs",
+ &val);
+ if (rc) {
+ pr_err("bl-pmic-pwm-period-usecs is not defined, rc=%d\n", rc);
+ goto error;
+ }
+ config->pwm_period_usecs = val;
+
+error:
+ return rc;
+}
+
+static void dsi_panel_pwm_bl_unregister(struct dsi_backlight_config *bl)
+{
+ struct dsi_panel *panel = container_of(bl, struct dsi_panel, bl_config);
+ struct dsi_backlight_pwm_config *pwm_cfg = bl->priv;
+
+ devm_pwm_put(panel->parent, pwm_cfg->pwm_bl);
+ kfree(pwm_cfg);
+}
+
+static int dsi_panel_pwm_bl_register(struct dsi_backlight_config *bl)
+{
+ struct dsi_panel *panel = container_of(bl, struct dsi_panel, bl_config);
+ struct dsi_backlight_pwm_config *pwm_cfg;
+ int rc = 0;
+
+ pwm_cfg = kzalloc(sizeof(*pwm_cfg), GFP_KERNEL);
+ if (!pwm_cfg)
+ return -ENOMEM;
+
+ pwm_cfg->pwm_bl = devm_of_pwm_get(panel->parent, panel->panel_of_node, NULL);
+ if (IS_ERR_OR_NULL(pwm_cfg->pwm_bl)) {
+ rc = PTR_ERR(pwm_cfg->pwm_bl);
+ pr_err("[%s] failed to request pwm, rc=%d\n", panel->name,
+ rc);
+ kfree(pwm_cfg);
+ return rc;
+ }
+
+ rc = dsi_panel_bl_parse_pwm_config(panel, pwm_cfg);
+ if (rc) {
+ pr_err("[%s] failed to parse pwm config, rc=%d\n",
+ panel->name, rc);
+ dsi_panel_pwm_bl_unregister(bl);
+ return rc;
+ }
+
+ bl->priv = pwm_cfg;
+ bl->unregister = dsi_panel_pwm_bl_unregister;
+
+ return 0;
+}
+
+static int dsi_panel_bl_parse_lut(struct device *parent,
+ struct device_node *of_node, const char *bl_lut_prop_name,
+ u32 brightness_max_level, u16 **lut)
+{
+ u32 len = 0;
+ u32 i = 0;
+ u32 rc = 0;
+ const __be32 *val = 0;
+ struct property *prop = NULL;
+ u32 lut_length = brightness_max_level + 1;
+ u16 *bl_lut_tmp = NULL;
+
+ if (!of_node || !bl_lut_prop_name || !lut)
+ return -EINVAL;
+
+ if (*lut) {
+ pr_warn("LUT for %s already exists, freeing before reparsing\n",
+ bl_lut_prop_name);
+ devm_kfree(parent, *lut);
+ *lut = NULL;
+ }
+
+ prop = of_find_property(of_node, bl_lut_prop_name, &len);
+ if (!prop)
+ goto done; /* LUT is unspecified */
+
+ len /= sizeof(u32);
+ if (len != lut_length) {
+ pr_warn("%s length %d doesn't match brightness_max_level + 1 %d\n",
+ bl_lut_prop_name, len, lut_length);
+ goto done;
+ }
+
+ pr_debug("%s length %d\n", bl_lut_prop_name, lut_length);
+ bl_lut_tmp = devm_kmalloc(parent, sizeof(u16) * lut_length, GFP_KERNEL);
+ if (bl_lut_tmp == NULL) {
+ rc = -ENOMEM;
+ goto done;
+ }
+
+ val = prop->value;
+ for (i = 0; i < len; i++)
+ bl_lut_tmp[i] = (u16)(be32_to_cpup(val++) & 0xffff);
+
+ *lut = bl_lut_tmp;
+
+done:
+ return rc;
+}
+
+static void dsi_panel_bl_hbm_free(struct device *dev,
+ struct dsi_backlight_config *bl)
+{
+ u32 i = 0;
+ struct hbm_data *hbm = bl->hbm;
+
+ if (!hbm)
+ return;
+
+ if (hbm->dimming_workq) {
+ dsi_backlight_hbm_dimming_stop(bl);
+ flush_workqueue(hbm->dimming_workq);
+ destroy_workqueue(hbm->dimming_workq);
+ }
+
+ dsi_panel_destroy_cmd_packets(&hbm->exit_cmd);
+ dsi_panel_dealloc_cmd_packets(&hbm->exit_cmd);
+ dsi_panel_destroy_cmd_packets(&hbm->exit_dimming_stop_cmd);
+ dsi_panel_dealloc_cmd_packets(&hbm->exit_dimming_stop_cmd);
+
+ dsi_panel_destroy_cmd_packets(&hbm->irc_unlock_cmd);
+ dsi_panel_destroy_cmd_packets(&hbm->irc_lock_cmd);
+ kfree(hbm->irc_data);
+
+ for (i = 0; i < hbm->num_ranges; i++) {
+ dsi_panel_destroy_cmd_packets(&hbm->ranges[i].entry_cmd);
+ dsi_panel_dealloc_cmd_packets(&hbm->ranges[i].entry_cmd);
+
+ dsi_panel_destroy_cmd_packets(&hbm->ranges[i].dimming_stop_cmd);
+ dsi_panel_dealloc_cmd_packets(&hbm->ranges[i].dimming_stop_cmd);
+ }
+
+ devm_kfree(dev, hbm);
+ bl->hbm = NULL;
+}
+
+static int dsi_panel_bl_parse_hbm(struct device *parent,
+ struct dsi_backlight_config *bl, struct device_node *of_node)
+{
+ struct device_node *hbm_ranges_np;
+ struct device_node *child_np;
+ struct dsi_panel *panel = container_of(bl, struct dsi_panel, bl_config);
+ u32 rc = 0;
+ u32 i = 0;
+ u32 num_ranges = 0;
+ u32 val = 0;
+ bool dimming_used = false;
+
+ panel->hbm_mode = HBM_MODE_OFF;
+
+ if (bl->hbm) {
+ pr_warn("HBM data already parsed, freeing before reparsing\n");
+ dsi_panel_bl_hbm_free(parent, bl);
+ }
+
+ hbm_ranges_np = of_get_child_by_name(of_node, "google,hbm-ranges");
+ if (!hbm_ranges_np) {
+ pr_info("HBM modes list not found\n");
+ return 0;
+ }
+
+ num_ranges = of_get_child_count(hbm_ranges_np);
+ if (!num_ranges || (num_ranges > HBM_RANGE_MAX)) {
+ pr_err("Invalid number of HBM modes: %d\n", num_ranges);
+ return -EINVAL;
+ }
+
+ bl->hbm = devm_kzalloc(parent, sizeof(struct hbm_data), GFP_KERNEL);
+ if (bl->hbm == NULL) {
+ pr_err("Failed to allocate memory for HBM data\n");
+ return -ENOMEM;
+ }
+
+ rc = dsi_panel_parse_dt_cmd_set(hbm_ranges_np,
+ "google,dsi-hbm-exit-command",
+ "google,dsi-hbm-commands-state", &bl->hbm->exit_cmd);
+ if (rc)
+ pr_info("Unable to parse optional dsi-hbm-exit-command\n");
+
+ bl->hbm->num_ranges = num_ranges;
+
+ rc = of_property_read_u32(hbm_ranges_np,
+ "google,dsi-hbm-exit-num-dimming-frames", &val);
+ if (rc) {
+ pr_debug("Unable to parse optional num-dimming-frames\n");
+ bl->hbm->exit_num_dimming_frames = 0;
+ } else {
+ bl->hbm->exit_num_dimming_frames = val;
+ }
+
+ rc = dsi_panel_parse_dt_cmd_set(hbm_ranges_np,
+ "google,dsi-hbm-exit-dimming-stop-command",
+ "google,dsi-hbm-commands-state",
+ &bl->hbm->exit_dimming_stop_cmd);
+ if (rc)
+ pr_debug("Unable to parse optional dsi-hbm-exit-dimming-stop-command\n");
+
+ if ((bl->hbm->exit_dimming_stop_cmd.count &&
+ !bl->hbm->exit_num_dimming_frames) ||
+ (!bl->hbm->exit_dimming_stop_cmd.count &&
+ bl->hbm->exit_num_dimming_frames)) {
+ pr_err("HBM dimming requires both stop command and number of frames.\n");
+ goto exit_free;
+ }
+
+ rc = of_property_read_u32(hbm_ranges_np,
+ "google,dsi-irc-addr", &val);
+ if (rc) {
+ pr_debug("Unable to parse dsi-irc-addr\n");
+ bl->hbm->irc_addr = 0;
+ } else {
+ bl->hbm->irc_addr = val;
+ rc = of_property_read_u32(hbm_ranges_np,
+ "google,dsi-irc-bit-offset", &val);
+ if (rc) {
+ bl->hbm->irc_bit_offset = 0;
+ bl->hbm->irc_addr = 0;
+ pr_warn("Unable to parse dsi-irc-bit-offset\n");
+ } else {
+ bl->hbm->irc_bit_offset = val;
+ }
+
+ rc = dsi_panel_parse_dt_cmd_set(hbm_ranges_np,
+ "google,dsi-irc-unlock-command",
+ "google,dsi-irc-unlock-commands-state",
+ &bl->hbm->irc_unlock_cmd);
+ if (rc)
+ pr_debug("Unable to parse optional dsi-irc-unlock-command\n");
+
+ rc = dsi_panel_parse_dt_cmd_set(hbm_ranges_np,
+ "google,dsi-irc-lock-command",
+ "google,dsi-irc-lock-commands-state",
+ &bl->hbm->irc_lock_cmd);
+ if (rc)
+ pr_debug("Unable to parse optional dsi-irc-lock-command\n");
+
+ if (!bl->hbm->irc_unlock_cmd.count != !bl->hbm->irc_lock_cmd.count) {
+ dsi_panel_destroy_cmd_packets(&bl->hbm->irc_unlock_cmd);
+ dsi_panel_destroy_cmd_packets(&bl->hbm->irc_lock_cmd);
+ bl->hbm->irc_addr = 0;
+ pr_warn("Unable to get a pair of dsi-irc-unlock/lock command\n");
+ }
+ }
+
+ for_each_child_of_node(hbm_ranges_np, child_np) {
+ rc = dsi_panel_bl_parse_hbm_node(parent, bl,
+ child_np, bl->hbm->ranges + i);
+ if (rc) {
+ pr_err("Failed to parse HBM range %d of %d\n",
+ i + 1, num_ranges);
+ goto exit_free;
+ }
+ i++;
+ }
+
+ for (i = 0; i < num_ranges - 1; i++) {
+ /* Make sure ranges are sorted and not overlapping */
+ if (bl->hbm->ranges[i].user_bri_start >=
+ bl->hbm->ranges[i + 1].user_bri_start) {
+ pr_err("HBM ranges must be sorted by hbm-brightness-threshold\n");
+ rc = -EINVAL;
+ goto exit_free;
+ }
+
+ if (bl->hbm->ranges[i].num_dimming_frames)
+ dimming_used = true;
+
+ /* Fill in user_bri_end for each range */
+ bl->hbm->ranges[i].user_bri_end =
+ bl->hbm->ranges[i + 1].user_bri_start - 1;
+ }
+
+ if (bl->hbm->ranges[num_ranges - 1].num_dimming_frames ||
+ bl->hbm->exit_num_dimming_frames)
+ dimming_used = true;
+
+
+ if (dimming_used) {
+ bl->hbm->dimming_workq =
+ create_singlethread_workqueue("dsi_dimming_workq");
+ if (!bl->hbm->dimming_workq)
+ pr_err("failed to create hbm dimming workq!\n");
+ else
+ INIT_WORK(&bl->hbm->dimming_work,
+ dsi_backlight_hbm_dimming_work);
+ }
+
+ bl->hbm->ranges[i].user_bri_end = bl->brightness_max_level;
+ bl->hbm->cur_range = HBM_RANGE_MAX;
+ bl->hbm->dimming_active = false;
+ bl->hbm->dimming_frames_total = 0;
+ bl->hbm->dimming_frames_left = 0;
+ bl->hbm->panel = panel;
+
+ return 0;
+
+exit_free:
+ dsi_panel_bl_hbm_free(parent, bl);
+ return rc;
+}
+
+static int dsi_panel_bl_find_range(struct dsi_backlight_config *bl,
+ int brightness, u32 *range)
+{
+ u32 i;
+
+ if (!bl || !bl->bl_notifier || !range)
+ return -EINVAL;
+
+ for (i = 0; i < bl->bl_notifier->num_ranges; i++) {
+ if (brightness <= bl->bl_notifier->ranges[i]) {
+ *range = i;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static void dsi_panel_bl_notifier_free(struct device *dev,
+ struct dsi_backlight_config *bl)
+{
+ struct bl_notifier_data *bl_notifier = bl->bl_notifier;
+
+ if (!bl_notifier)
+ return;
+
+ devm_kfree(dev, bl_notifier);
+ bl->bl_notifier = NULL;
+}
+
+static int dsi_panel_bl_parse_ranges(struct device *parent,
+ struct dsi_backlight_config *bl, struct device_node *of_node)
+{
+ int num_ranges = 0;
+
+ bl->bl_notifier = devm_kzalloc(parent, sizeof(struct bl_notifier_data), GFP_KERNEL);
+ if (bl->bl_notifier == NULL) {
+ pr_err("Failed to allocate memory for bl_notifier_data\n");
+ return -ENOMEM;
+ }
+
+ num_ranges = of_property_read_variable_u32_array(of_node,
+ "qcom,mdss-dsi-bl-notifier-ranges",
+ (u32 *)&bl->bl_notifier->ranges,
+ 0,
+ BL_RANGE_MAX);
+ if (num_ranges >= 0) {
+ bl->bl_notifier->num_ranges = num_ranges;
+ } else {
+ dsi_panel_bl_notifier_free(parent, bl);
+ pr_debug("Unable to parse optional backlight ranges (%d)\n", num_ranges);
+ return num_ranges;
+ }
+
+ return 0;
+}
+
+int dsi_panel_bl_parse_config(struct device *parent, struct dsi_backlight_config *bl)
+{
+ struct dsi_panel *panel = container_of(bl, struct dsi_panel, bl_config);
+ int rc = 0;
+ u32 val = 0;
+ const char *bl_type;
+ const char *data;
+ struct dsi_parser_utils *utils = &panel->utils;
+ char *bl_name;
+
+ if (!strcmp(panel->type, "primary"))
+ bl_name = "qcom,mdss-dsi-bl-pmic-control-type";
+ else
+ bl_name = "qcom,mdss-dsi-sec-bl-pmic-control-type";
+
+ bl_type = utils->get_property(utils->data, bl_name, NULL);
+ if (!bl_type) {
+ bl->type = DSI_BACKLIGHT_UNKNOWN;
+ } else if (!strcmp(bl_type, "bl_ctrl_pwm")) {
+ bl->type = DSI_BACKLIGHT_PWM;
+ } else if (!strcmp(bl_type, "bl_ctrl_wled")) {
+ bl->type = DSI_BACKLIGHT_WLED;
+ } else if (!strcmp(bl_type, "bl_ctrl_dcs")) {
+ bl->type = DSI_BACKLIGHT_DCS;
+ } else if (!strcmp(bl_type, "bl_ctrl_external")) {
+ bl->type = DSI_BACKLIGHT_EXTERNAL;
+ } else {
+ pr_debug("[%s] bl-pmic-control-type unknown-%s\n",
+ panel->name, bl_type);
+ bl->type = DSI_BACKLIGHT_UNKNOWN;
+ }
+ data = utils->get_property(utils->data, "qcom,bl-update-flag", NULL);
+ if (!data) {
+ panel->bl_config.bl_update = BL_UPDATE_NONE;
+ } else if (!strcmp(data, "delay_until_first_frame")) {
+ panel->bl_config.bl_update = BL_UPDATE_DELAY_UNTIL_FIRST_FRAME;
+ } else {
+ pr_debug("[%s] No valid bl-update-flag: %s\n",
+ panel->name, data);
+ panel->bl_config.bl_update = BL_UPDATE_NONE;
+ }
+ panel->bl_config.bl_scale = MAX_BL_SCALE_LEVEL;
+ panel->bl_config.bl_scale_sv = MAX_SV_BL_SCALE_LEVEL;
+ rc = utils->read_u32(utils->data, "qcom,mdss-dsi-bl-min-level", &val);
+ if (rc) {
+ pr_debug("[%s] bl-min-level unspecified, defaulting to zero\n",
+ panel->name);
+ bl->bl_min_level = 0;
+ } else {
+ bl->bl_min_level = val;
+ }
+
+ rc = utils->read_u32(utils->data, "qcom,mdss-dsi-bl-max-level", &val);
+ if (rc) {
+ pr_debug("[%s] bl-max-level unspecified, defaulting to max level\n",
+ panel->name);
+ bl->bl_max_level = MAX_BL_LEVEL;
+ } else {
+ bl->bl_max_level = val;
+ }
+
+ rc = utils->read_u32(utils->data, "qcom,mdss-brightness-max-level",
+ &val);
+ if (rc) {
+ pr_debug("[%s] brigheness-max-level unspecified, defaulting to 255\n",
+ panel->name);
+ bl->brightness_max_level = 255;
+ } else {
+ bl->brightness_max_level = val;
+ }
+
+ rc = dsi_panel_bl_parse_lut(parent, utils->data, "qcom,mdss-dsi-bl-lut",
+ bl->brightness_max_level, &bl->lut);
+ if (rc) {
+ pr_err("[%s] failed to create backlight LUT, rc=%d\n",
+ panel->name, rc);
+ goto error;
+ }
+ pr_debug("[%s] bl-lut %sused\n", panel->name, bl->lut ? "" : "un");
+
+ bl->dimming_mode = utils->read_bool(utils->data,
+ "google,dsi-panel-dimming-enable");
+
+ rc = dsi_panel_bl_parse_hbm(parent, bl, utils->data);
+ if (rc)
+ pr_err("[%s] error while parsing high brightness mode (hbm) details, rc=%d\n",
+ panel->name, rc);
+
+ rc = dsi_panel_bl_parse_dynamic_elvss(parent, bl, utils->data);
+ if (rc)
+ pr_err("[%s] error while parsing dynamic elvss details, rc=%d\n",
+ panel->name, rc);
+
+ rc = dsi_panel_bl_parse_ranges(parent, bl, utils->data);
+ if (rc)
+ pr_debug("[%s] error while parsing backlight ranges, rc=%d\n",
+ panel->name, rc);
+
+ rc = utils->read_u32(utils->data, "google,dsi-bl-cmd-high-byte-offset",
+ &val);
+ if (rc) {
+ pr_debug("[%s] dsi-bl-cmd-high-byte-offset unspecified, defaulting to 8\n",
+ panel->name);
+ bl->high_byte_offset = 8;
+ } else {
+ bl->high_byte_offset = val;
+ }
+
+ bl->en_gpio = utils->get_named_gpio(utils->data,
+ "qcom,platform-bklight-en-gpio",
+ 0);
+ if (!gpio_is_valid(bl->en_gpio)) {
+ if (bl->en_gpio == -EPROBE_DEFER) {
+ pr_debug("[%s] failed to get bklt gpio, rc=%d\n",
+ panel->name, rc);
+ rc = -EPROBE_DEFER;
+ goto error;
+ } else {
+ pr_debug("[%s] failed to get bklt gpio, rc=%d\n",
+ panel->name, rc);
+ rc = 0;
+ goto error;
+ }
+ }
+
+error:
+ return rc;
+}
+
+static int dsi_panel_bl_read_brightness(struct dsi_panel *panel,
+ struct dsi_backlight_config *bl_cfg, int *lvl)
+{
+ u32 rc;
+ u8 buf[BL_BRIGHTNESS_BUF_SIZE];
+
+ rc = mipi_dsi_dcs_read(&panel->mipi_device,
+ MIPI_DCS_GET_DISPLAY_BRIGHTNESS, buf, BL_BRIGHTNESS_BUF_SIZE);
+
+ if (rc <= 0 || rc > BL_BRIGHTNESS_BUF_SIZE) {
+ pr_err("mipi_dsi_dcs_read error: %d\n", rc);
+ return -EIO;
+ }
+
+ if (rc == 1)
+ *lvl = buf[0];
+ else if (rc == 2)
+ *lvl = be16_to_cpu(*(const __be16 *)buf);
+ else {
+ pr_err("unexpected buffer size: %d\n", rc);
+ return -EIO;
+ }
+
+ /* Some panels may not clear non-functional bits. */
+ *lvl &= (1 << fls(bl_cfg->bl_max_level)) - 1;
+
+ return 0;
+}
+int dsi_panel_bl_brightness_handoff(struct dsi_panel *panel)
+{
+ struct dsi_backlight_config *bl_cfg;
+ struct backlight_device *bl_device;
+ int bl_lvl = 0, brightness, rc;
+
+ if (!panel || !panel->bl_config.bl_device)
+ return -EINVAL;
+
+ bl_cfg = &panel->bl_config;
+ bl_device = bl_cfg->bl_device;
+
+ rc = dsi_panel_bl_read_brightness(panel, bl_cfg, &bl_lvl);
+ if (rc) {
+ pr_err("Failed to read brightness from panel.\n");
+ return rc;
+ }
+
+ rc = dsi_backlight_lerp(bl_cfg->bl_min_level, bl_cfg->bl_max_level, 1,
+ bl_cfg->brightness_max_level, bl_lvl, &brightness);
+ if (rc) {
+ pr_err("Failed to map brightness to user space.\n");
+ return rc;
+ }
+
+ pr_debug("brightness 0x%x to user space %d\n", bl_lvl, brightness);
+ bl_device->props.brightness = brightness;
+
+ return rc;
+}
+
+int dsi_panel_bl_update_irc(struct dsi_backlight_config *bl, bool enable)
+{
+ struct hbm_data *hbm = bl->hbm;
+ int rc = 0;
+ u32 byte_offset;
+ u32 bit_mask;
+ u32 irc_data_size;
+
+ if (!hbm || hbm->irc_addr == 0)
+ return -EOPNOTSUPP;
+
+ byte_offset = hbm->irc_bit_offset / BITS_PER_BYTE;
+ bit_mask = BIT(hbm->irc_bit_offset % BITS_PER_BYTE);
+ irc_data_size = byte_offset + 1;
+
+ pr_info("irc update: %d\n", enable);
+ dsi_panel_cmd_set_transfer(hbm->panel, &hbm->irc_unlock_cmd);
+ if (hbm->irc_data == NULL) {
+ hbm->irc_data = kzalloc(irc_data_size, GFP_KERNEL);
+ if (hbm->irc_data == NULL) {
+ pr_err("failed to alloc irc_data.\n");
+ goto done;
+ }
+
+ rc = mipi_dsi_dcs_read(&hbm->panel->mipi_device, hbm->irc_addr,
+ hbm->irc_data, irc_data_size);
+ if (rc != irc_data_size) {
+ pr_err("failed to read irc.\n");
+ goto done;
+ }
+ pr_info("Read back irc initial configuration\n");
+ }
+
+ if (enable)
+ hbm->irc_data[byte_offset] |= bit_mask;
+ else
+ hbm->irc_data[byte_offset] &= ~bit_mask;
+
+ rc = mipi_dsi_dcs_write(&hbm->panel->mipi_device,
+ hbm->irc_addr, hbm->irc_data, irc_data_size);
+
+ if (rc)
+ pr_err("failed to send irc cmd.\n");
+done:
+ dsi_panel_cmd_set_transfer(hbm->panel, &hbm->irc_lock_cmd);
+ return rc;
+}
diff --git a/msm/dsi/dsi_clk.h b/msm/dsi/dsi_clk.h
index fcd352d..ccc1ba7 100644
--- a/msm/dsi/dsi_clk.h
+++ b/msm/dsi/dsi_clk.h
@@ -268,8 +268,7 @@
*
* return: error code in case of failure or 0 for success.
*/
-int dsi_display_clk_ctrl(void *handle,
- enum dsi_clk_type clk_type, enum dsi_clk_state clk_state);
+int dsi_display_clk_ctrl(void *handle, u32 clk_type, u32 clk_state);
/**
* dsi_clk_set_link_frequencies() - set frequencies for link clks
diff --git a/msm/dsi/dsi_clk_manager.c b/msm/dsi/dsi_clk_manager.c
index ec2df96..7983ff0 100644
--- a/msm/dsi/dsi_clk_manager.c
+++ b/msm/dsi/dsi_clk_manager.c
@@ -1291,8 +1291,7 @@
return rc;
}
-int dsi_display_clk_ctrl(void *handle,
- enum dsi_clk_type clk_type, enum dsi_clk_state clk_state)
+int dsi_display_clk_ctrl(void *handle, u32 clk_type, u32 clk_state)
{
int rc = 0;
diff --git a/msm/dsi/dsi_ctrl.c b/msm/dsi/dsi_ctrl.c
index c92bfbc..54d5dbf 100644
--- a/msm/dsi/dsi_ctrl.c
+++ b/msm/dsi/dsi_ctrl.c
@@ -1053,36 +1053,25 @@
return rc;
}
-static int dsi_ctrl_copy_and_pad_cmd(struct dsi_ctrl *dsi_ctrl,
- const struct mipi_dsi_packet *packet,
- u8 **buffer,
- u32 *size)
+static int dsi_ctrl_copy_and_pad_cmd(const struct mipi_dsi_packet *packet,
+ u8 *buf, size_t len)
{
int rc = 0;
- u8 *buf = NULL;
- u32 len, i;
u8 cmd_type = 0;
- len = packet->size;
- len += 0x3; len &= ~0x03; /* Align to 32 bits */
+ if (unlikely(len < packet->size))
+ return -EINVAL;
- buf = devm_kzalloc(&dsi_ctrl->pdev->dev, len * sizeof(u8), GFP_KERNEL);
- if (!buf)
- return -ENOMEM;
-
- for (i = 0; i < len; i++) {
- if (i >= packet->size)
- buf[i] = 0xFF;
- else if (i < sizeof(packet->header))
- buf[i] = packet->header[i];
- else
- buf[i] = packet->payload[i - sizeof(packet->header)];
- }
+ memcpy(buf, packet->header, sizeof(packet->header));
+ if (packet->payload_length)
+ memcpy(buf + sizeof(packet->header), packet->payload,
+ packet->payload_length);
+ if (packet->size < len)
+ memset(buf + packet->size, 0xFF, len - packet->size);
if (packet->payload_length > 0)
buf[3] |= BIT(6);
-
/* send embedded BTA for read commands */
cmd_type = buf[2] & 0x3f;
if ((cmd_type == MIPI_DSI_DCS_READ) ||
@@ -1091,9 +1080,6 @@
(cmd_type == MIPI_DSI_GENERIC_READ_REQUEST_2_PARAM))
buf[3] |= BIT(5);
- *buffer = buf;
- *size = len;
-
return rc;
}
@@ -1215,11 +1201,13 @@
DSI_CTRL_ERR(dsi_ctrl, "Cannot transfer,size is greater than 4096\n");
return -ENOTSUPP;
}
- }
+ } else if (*flags & DSI_CTRL_CMD_FETCH_MEMORY) {
+ const size_t transfer_size = dsi_ctrl->cmd_len + cmd_len + 4;
- if (*flags & DSI_CTRL_CMD_FETCH_MEMORY) {
- if ((dsi_ctrl->cmd_len + cmd_len + 4) > SZ_4K) {
- DSI_CTRL_ERR(dsi_ctrl, "Cannot transfer,size is greater than 4096\n");
+ if (transfer_size > DSI_EMBEDDED_MODE_DMA_MAX_SIZE_BYTES) {
+ DSI_CTRL_ERR(dsi_ctrl,"Cannot transfer, size: %zu is greater than %d\n",
+ transfer_size,
+ DSI_EMBEDDED_MODE_DMA_MAX_SIZE_BYTES);
return -ENOTSUPP;
}
}
@@ -1370,6 +1358,28 @@
*flags &= ~DSI_CTRL_CMD_ASYNC_WAIT;
}
+static int dsi_msm_create_packet(struct mipi_dsi_packet *packet,
+ const struct mipi_dsi_msg *msg)
+{
+ int rc;
+ u8 tmp;
+ if (!packet)
+ return -EINVAL;
+
+ rc = mipi_dsi_create_packet(packet, msg);
+ if (rc)
+ return rc;
+
+ /* MSM chipsets swap the Data ID with the Word Count. So we need to shift
+ * the header over. */
+ tmp = packet->header[0];
+ packet->header[0] = packet->header[1];
+ packet->header[1] = packet->header[2];
+ packet->header[2] = tmp;
+
+ return 0;
+}
+
static int dsi_message_tx(struct dsi_ctrl *dsi_ctrl,
const struct mipi_dsi_msg *msg,
u32 *flags)
@@ -1378,9 +1388,8 @@
struct mipi_dsi_packet packet;
struct dsi_ctrl_cmd_dma_fifo_info cmd;
struct dsi_ctrl_cmd_dma_info cmd_mem;
- u32 length = 0;
+ u32 length;
u8 *buffer = NULL;
- u32 cnt = 0;
u8 *cmdbuf;
/* Select the tx mode to transfer the command */
@@ -1400,6 +1409,10 @@
if (dsi_ctrl->dma_wait_queued)
dsi_ctrl_flush_cmd_dma_queue(dsi_ctrl);
+ DSI_CTRL_DEBUG(dsi_ctrl, "cmd tx type=%02x cmd=%02x len=%d last=%d\n",
+ msg->type, msg->tx_len ? *((u8 *)msg->tx_buf) : 0, msg->tx_len,
+ (msg->flags & MIPI_DSI_MSG_LASTCOMMAND) != 0);
+
if (*flags & DSI_CTRL_CMD_NON_EMBEDDED_MODE) {
cmd_mem.offset = dsi_ctrl->cmd_buffer_iova;
cmd_mem.en_broadcast = (*flags & DSI_CTRL_CMD_BROADCAST) ?
@@ -1420,26 +1433,29 @@
goto kickoff;
}
- rc = mipi_dsi_create_packet(&packet, msg);
+ rc = dsi_msm_create_packet(&packet, msg);
if (rc) {
DSI_CTRL_ERR(dsi_ctrl, "Failed to create message packet, rc=%d\n",
rc);
goto error;
}
- rc = dsi_ctrl_copy_and_pad_cmd(dsi_ctrl,
- &packet,
- &buffer,
- &length);
- if (rc) {
- DSI_CTRL_ERR(dsi_ctrl, "failed to copy message, rc=%d\n", rc);
- goto error;
- }
+ length = ALIGN(packet.size, 4);
if ((msg->flags & MIPI_DSI_MSG_LASTCOMMAND))
- buffer[3] |= BIT(7);//set the last cmd bit in header.
+ packet.header[3] |= BIT(7);//set the last cmd bit in header.
if (*flags & DSI_CTRL_CMD_FETCH_MEMORY) {
+ msm_gem_sync(dsi_ctrl->tx_cmd_buf);
+ cmdbuf = dsi_ctrl->vaddr + dsi_ctrl->cmd_len;
+
+ rc = dsi_ctrl_copy_and_pad_cmd(&packet, cmdbuf, length);
+ if (rc) {
+ DSI_CTRL_ERR(dsi_ctrl, "failed to copy message, rc=%d\n",
+ rc);
+ goto error;
+ }
+
/* Embedded mode config is selected */
cmd_mem.offset = dsi_ctrl->cmd_buffer_iova;
cmd_mem.en_broadcast = (*flags & DSI_CTRL_CMD_BROADCAST) ?
@@ -1449,12 +1465,6 @@
cmd_mem.use_lpm = (msg->flags & MIPI_DSI_MSG_USE_LPM) ?
true : false;
- cmdbuf = (u8 *)(dsi_ctrl->vaddr);
-
- msm_gem_sync(dsi_ctrl->tx_cmd_buf);
- for (cnt = 0; cnt < length; cnt++)
- cmdbuf[dsi_ctrl->cmd_len + cnt] = buffer[cnt];
-
dsi_ctrl->cmd_len += length;
if (!(msg->flags & MIPI_DSI_MSG_LASTCOMMAND)) {
@@ -1465,6 +1475,20 @@
}
} else if (*flags & DSI_CTRL_CMD_FIFO_STORE) {
+ buffer = devm_kzalloc(&dsi_ctrl->pdev->dev, length,
+ GFP_KERNEL);
+ if (!buffer) {
+ rc = -ENOMEM;
+ goto error;
+ }
+
+ rc = dsi_ctrl_copy_and_pad_cmd(&packet, buffer, length);
+ if (rc) {
+ DSI_CTRL_ERR(dsi_ctrl, "failed to copy message, rc=%d\n",
+ rc);
+ goto error;
+ }
+
cmd.command = (u32 *)buffer;
cmd.size = length;
cmd.en_broadcast = (*flags & DSI_CTRL_CMD_BROADCAST) ?
@@ -1568,14 +1592,16 @@
const struct mipi_dsi_msg *msg,
u32 *flags)
{
- int rc = 0;
+ int rc = 0, i = 0;
u32 rd_pkt_size, total_read_len, hw_read_cnt;
u32 current_read_len = 0, total_bytes_read = 0;
bool short_resp = false;
bool read_done = false;
u32 dlen, diff, rlen;
- unsigned char *buff;
+ unsigned char *buff = NULL;
char cmd;
+ u32 buffer_sz = 0, header_offset = 0;
+ u8 *head = NULL;
if (!msg) {
DSI_CTRL_ERR(dsi_ctrl, "Invalid msg\n");
@@ -1588,6 +1614,13 @@
short_resp = true;
rd_pkt_size = msg->rx_len;
total_read_len = 4;
+
+ /*
+ * buffer size: header + data
+ * No 32 bits alignment issue, thus offset is 0
+ */
+ buffer_sz = 4;
+
} else {
short_resp = false;
current_read_len = 10;
@@ -1597,8 +1630,30 @@
rd_pkt_size = current_read_len;
total_read_len = current_read_len + 6;
+ /*
+ * buffer size: header + data + footer, rounded up to 4 bytes
+ * Out of bound can occurs is rx_len is not aligned to size 4.
+ * We are reading 32 bits registers, and converting
+ * the data to CPU endianness, thus inserting garbage data
+ * at the beginning of buffer.
+ */
+ buffer_sz = ALIGN(4 + msg->rx_len + 2, 4);
+ if (buffer_sz < 16)
+ buffer_sz = 16;
}
- buff = msg->rx_buf;
+
+ DSI_CTRL_DEBUG(dsi_ctrl, "short_resp %d, msg->rx_len %zd, rd_pkt_size %u\n",
+ short_resp, msg->rx_len, rd_pkt_size);
+
+ DSI_CTRL_DEBUG(dsi_ctrl, "total_read_len %u, buffer_sz %u\n",
+ total_read_len, buffer_sz);
+
+ buff = kzalloc(buffer_sz, GFP_KERNEL);
+ if (!buff) {
+ rc = -ENOMEM;
+ goto error;
+ }
+ head = buff;
while (!read_done) {
rc = dsi_set_max_return_size(dsi_ctrl, msg, rd_pkt_size);
@@ -1656,13 +1711,22 @@
}
}
+ buff = head;
+ for (i = 0; i < buffer_sz; i += 4)
+ DSI_CTRL_DEBUG(dsi_ctrl, "buffer[%d-%d] = %08x\n",
+ i, i + 3, *((u32 *)&buff[i]));
+
if (hw_read_cnt < 16 && !short_resp)
- buff = msg->rx_buf + (16 - hw_read_cnt);
+ header_offset = (16 - hw_read_cnt);
else
- buff = msg->rx_buf;
+ header_offset = 0;
+
+ DSI_CTRL_DEBUG(dsi_ctrl, "hw_read_cnt %d, header_offset %d\n",
+ hw_read_cnt, header_offset);
/* parse the data read from panel */
- cmd = buff[0];
+ cmd = buff[header_offset];
+ DSI_CTRL_DEBUG(dsi_ctrl, "response type %d\n", cmd);
switch (cmd) {
case MIPI_DSI_RX_ACKNOWLEDGE_AND_ERROR_REPORT:
DSI_CTRL_ERR(dsi_ctrl, "Rx ACK_ERROR 0x%x\n", cmd);
@@ -1670,15 +1734,15 @@
break;
case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_1BYTE:
case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_1BYTE:
- rc = dsi_parse_short_read1_resp(msg, buff);
+ rc = dsi_parse_short_read1_resp(msg, &buff[header_offset]);
break;
case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_2BYTE:
case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_2BYTE:
- rc = dsi_parse_short_read2_resp(msg, buff);
+ rc = dsi_parse_short_read2_resp(msg, &buff[header_offset]);
break;
case MIPI_DSI_RX_GENERIC_LONG_READ_RESPONSE:
case MIPI_DSI_RX_DCS_LONG_READ_RESPONSE:
- rc = dsi_parse_long_read_resp(msg, buff);
+ rc = dsi_parse_long_read_resp(msg, &buff[header_offset]);
break;
default:
DSI_CTRL_WARN(dsi_ctrl, "Invalid response: 0x%x\n", cmd);
@@ -1686,6 +1750,7 @@
}
error:
+ kfree(buff);
return rc;
}
@@ -3219,9 +3284,11 @@
if (*flags & DSI_CTRL_CMD_READ) {
rc = dsi_message_rx(dsi_ctrl, msg, flags);
- if (rc <= 0)
- DSI_CTRL_ERR(dsi_ctrl, "read message failed read length, rc=%d\n",
+ if (rc < 0)
+ DSI_CTRL_ERR(dsi_ctrl, "read message failed read, rc=%d\n",
rc);
+ else if (rc == 0)
+ DSI_CTRL_WARN(dsi_ctrl, "empty message back\n");
} else {
rc = dsi_message_tx(dsi_ctrl, msg, flags);
if (rc)
diff --git a/msm/dsi/dsi_defs.h b/msm/dsi/dsi_defs.h
index 5179edf..476a84e 100644
--- a/msm/dsi/dsi_defs.h
+++ b/msm/dsi/dsi_defs.h
@@ -106,7 +106,7 @@
DSI_MODE_FLAG_VRR = BIT(4),
DSI_MODE_FLAG_POMS = BIT(5),
DSI_MODE_FLAG_DYN_CLK = BIT(6),
- DSI_MODE_FLAG_DMS_FPS = BIT(7),
+ DSI_MODE_FLAG_DMS_FPS = BIT(7),
};
/**
@@ -301,6 +301,7 @@
DSI_CMD_SET_LP1,
DSI_CMD_SET_LP2,
DSI_CMD_SET_NOLP,
+ DSI_CMD_SET_POST_NOLP,
DSI_CMD_SET_PPS,
DSI_CMD_SET_ROI,
DSI_CMD_SET_TIMING_SWITCH,
@@ -625,7 +626,10 @@
struct msm_display_topology topology;
struct msm_display_dsc_info dsc;
bool dsc_enabled;
+ bool pps_created;
struct msm_roi_caps roi_caps;
+
+ void *switch_data;
};
/**
diff --git a/msm/dsi/dsi_display.c b/msm/dsi/dsi_display.c
index acb1f7d..2cf726b 100644
--- a/msm/dsi/dsi_display.c
+++ b/msm/dsi/dsi_display.c
@@ -20,6 +20,7 @@
#include "dsi_pwr.h"
#include "sde_dbg.h"
#include "dsi_parser.h"
+#include "sde_trace.h"
#define to_dsi_display(x) container_of(x, struct dsi_display, host)
#define INT_BASE_10 10
@@ -45,6 +46,12 @@
{}
};
+static inline bool is_lp_mode(int power_mode)
+{
+ return power_mode == SDE_MODE_DPMS_LP1 ||
+ power_mode == SDE_MODE_DPMS_LP2;
+}
+
static void dsi_display_mask_ctrl_error_interrupts(struct dsi_display *display,
u32 mask, bool enable)
{
@@ -190,62 +197,6 @@
}
}
-int dsi_display_set_backlight(struct drm_connector *connector,
- void *display, u32 bl_lvl)
-{
- struct dsi_display *dsi_display = display;
- struct dsi_panel *panel;
- u32 bl_scale, bl_scale_sv;
- u64 bl_temp;
- int rc = 0;
-
- if (dsi_display == NULL || dsi_display->panel == NULL)
- return -EINVAL;
-
- panel = dsi_display->panel;
-
- mutex_lock(&panel->panel_lock);
- if (!dsi_panel_initialized(panel)) {
- rc = -EINVAL;
- goto error;
- }
-
- panel->bl_config.bl_level = bl_lvl;
-
- /* scale backlight */
- bl_scale = panel->bl_config.bl_scale;
- bl_temp = bl_lvl * bl_scale / MAX_BL_SCALE_LEVEL;
-
- bl_scale_sv = panel->bl_config.bl_scale_sv;
- bl_temp = (u32)bl_temp * bl_scale_sv / MAX_SV_BL_SCALE_LEVEL;
-
- DSI_DEBUG("bl_scale = %u, bl_scale_sv = %u, bl_lvl = %u\n",
- bl_scale, bl_scale_sv, (u32)bl_temp);
- rc = dsi_display_clk_ctrl(dsi_display->dsi_clk_handle,
- DSI_CORE_CLK, DSI_CLK_ON);
- if (rc) {
- DSI_ERR("[%s] failed to enable DSI core clocks, rc=%d\n",
- dsi_display->name, rc);
- goto error;
- }
-
- rc = dsi_panel_set_backlight(panel, (u32)bl_temp);
- if (rc)
- DSI_ERR("unable to set backlight\n");
-
- rc = dsi_display_clk_ctrl(dsi_display->dsi_clk_handle,
- DSI_CORE_CLK, DSI_CLK_OFF);
- if (rc) {
- DSI_ERR("[%s] failed to disable DSI core clocks, rc=%d\n",
- dsi_display->name, rc);
- goto error;
- }
-
-error:
- mutex_unlock(&panel->panel_lock);
- return rc;
-}
-
static int dsi_display_cmd_engine_enable(struct dsi_display *display)
{
int rc = 0;
@@ -392,36 +343,59 @@
static irqreturn_t dsi_display_panel_te_irq_handler(int irq, void *data)
{
struct dsi_display *display = (struct dsi_display *)data;
+ struct dsi_display_te_listener *tl;
- /*
- * This irq handler is used for sole purpose of identifying
- * ESD attacks on panel and we can safely assume IRQ_HANDLED
- * in case of display not being initialized yet
- */
- if (!display)
+ if (unlikely(!display))
return IRQ_HANDLED;
SDE_EVT32(SDE_EVTLOG_FUNC_CASE1);
- complete_all(&display->esd_te_gate);
+
+ spin_lock(&display->te_lock);
+ list_for_each_entry(tl, &display->te_listeners, head)
+ tl->handler(tl);
+ spin_unlock(&display->te_lock);
+
return IRQ_HANDLED;
}
-static void dsi_display_change_te_irq_status(struct dsi_display *display,
- bool enable)
+int dsi_display_add_te_listener(struct dsi_display *display,
+ struct dsi_display_te_listener *tl)
{
- if (!display) {
- DSI_ERR("Invalid params\n");
- return;
- }
+ unsigned long flags;
+ bool needs_enable;
- /* Handle unbalanced irq enable/disable calls */
- if (enable && !display->is_te_irq_enabled) {
+ if (!display || !tl)
+ return -ENOENT;
+
+ spin_lock_irqsave(&display->te_lock, flags);
+ needs_enable = list_empty(&display->te_listeners);
+ list_add(&tl->head, &display->te_listeners);
+ spin_unlock_irqrestore(&display->te_lock, flags);
+
+ if (needs_enable)
enable_irq(gpio_to_irq(display->disp_te_gpio));
- display->is_te_irq_enabled = true;
- } else if (!enable && display->is_te_irq_enabled) {
- disable_irq(gpio_to_irq(display->disp_te_gpio));
- display->is_te_irq_enabled = false;
- }
+
+ return 0;
+}
+
+int dsi_display_remove_te_listener(struct dsi_display *display,
+ struct dsi_display_te_listener *tl)
+{
+ unsigned long flags;
+ unsigned int te_irq;
+
+ if (!display || !tl)
+ return -ENOENT;
+
+ te_irq = gpio_to_irq(display->disp_te_gpio);
+
+ spin_lock_irqsave(&display->te_lock, flags);
+ list_del(&tl->head);
+ if (list_empty(&display->te_listeners))
+ disable_irq_nosync(te_irq);
+ spin_unlock_irqrestore(&display->te_lock, flags);
+
+ return 0;
}
static void dsi_display_register_te_irq(struct dsi_display *display)
@@ -448,15 +422,16 @@
goto error;
}
- init_completion(&display->esd_te_gate);
te_irq = gpio_to_irq(display->disp_te_gpio);
+ spin_lock_init(&display->te_lock);
+ INIT_LIST_HEAD(&display->te_listeners);
+
/* Avoid deferred spurious irqs with disable_irq() */
irq_set_status_flags(te_irq, IRQ_DISABLE_UNLAZY);
rc = devm_request_irq(dev, te_irq, dsi_display_panel_te_irq_handler,
- IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
- "TE_GPIO", display);
+ IRQF_TRIGGER_RISING, "TE_GPIO", display);
if (rc) {
DSI_ERR("TE request_irq failed for ESD rc:%d\n", rc);
irq_clear_status_flags(te_irq, IRQ_DISABLE_UNLAZY);
@@ -464,7 +439,6 @@
}
disable_irq(te_irq);
- display->is_te_irq_enabled = false;
return;
@@ -758,21 +732,31 @@
return rc;
}
+static void _handle_esd_te(struct dsi_display_te_listener *tl)
+{
+ struct completion *esd_te_gate = tl->data;
+
+ complete(esd_te_gate);
+}
+
static int dsi_display_status_check_te(struct dsi_display *display)
{
int rc = 1;
int const esd_te_timeout = msecs_to_jiffies(3*20);
+ DECLARE_COMPLETION(esd_te_gate);
+ struct dsi_display_te_listener tl = {
+ .handler = _handle_esd_te,
+ .data = &esd_te_gate,
+ };
- dsi_display_change_te_irq_status(display, true);
+ dsi_display_add_te_listener(display, &tl);
- reinit_completion(&display->esd_te_gate);
- if (!wait_for_completion_timeout(&display->esd_te_gate,
- esd_te_timeout)) {
+ if (!wait_for_completion_timeout(&esd_te_gate, esd_te_timeout)) {
DSI_ERR("TE check failed\n");
rc = -EINVAL;
}
- dsi_display_change_te_irq_status(display, false);
+ dsi_display_remove_te_listener(display, &tl);
return rc;
}
@@ -1060,8 +1044,7 @@
rc = dsi_panel_set_lp2(display->panel);
break;
case SDE_MODE_DPMS_ON:
- if ((display->panel->power_mode == SDE_MODE_DPMS_LP1) ||
- (display->panel->power_mode == SDE_MODE_DPMS_LP2))
+ if (is_lp_mode(display->panel->power_mode))
rc = dsi_panel_set_nolp(display->panel);
break;
case SDE_MODE_DPMS_OFF:
@@ -1080,75 +1063,53 @@
}
#ifdef CONFIG_DEBUG_FS
-static bool dsi_display_is_te_based_esd(struct dsi_display *display)
+static int debugfs_dump_info_read(struct seq_file *seq, void *data)
{
- u32 status_mode = 0;
-
- if (!display->panel) {
- DSI_ERR("Invalid panel data\n");
- return false;
- }
-
- status_mode = display->panel->esd_config.status_mode;
-
- if (status_mode == ESD_MODE_PANEL_TE &&
- gpio_is_valid(display->disp_te_gpio))
- return true;
- return false;
-}
-
-static ssize_t debugfs_dump_info_read(struct file *file,
- char __user *user_buf,
- size_t user_len,
- loff_t *ppos)
-{
- struct dsi_display *display = file->private_data;
- char *buf;
- u32 len = 0;
+ struct dsi_display *display = seq->private;
+ struct drm_connector *conn;
+ struct drm_display_mode *mode;
int i;
if (!display)
return -ENODEV;
- if (*ppos)
- return 0;
+ conn = display->drm_conn;
- buf = kzalloc(SZ_4K, GFP_KERNEL);
- if (!buf)
- return -ENOMEM;
-
- len += snprintf(buf + len, (SZ_4K - len), "name = %s\n", display->name);
- len += snprintf(buf + len, (SZ_4K - len),
- "\tResolution = %dx%d\n",
+ seq_printf(seq, "name = %s\n", display->name);
+ seq_printf(seq, "\tResolution = %dx%d Fps = %d\n",
display->config.video_timing.h_active,
- display->config.video_timing.v_active);
+ display->config.video_timing.v_active,
+ display->config.video_timing.refresh_rate);
+ seq_printf(seq, "\tclk rate = %d\n",
+ display->config.video_timing.clk_rate_hz);
display_for_each_ctrl(i, display) {
- len += snprintf(buf + len, (SZ_4K - len),
- "\tCTRL_%d:\n\t\tctrl = %s\n\t\tphy = %s\n",
- i, display->ctrl[i].ctrl->name,
- display->ctrl[i].phy->name);
+ seq_printf(seq, "\tCTRL_%d:\n\t\tctrl = %s\n\t\tphy = %s\n",
+ i, display->ctrl[i].ctrl->name,
+ display->ctrl[i].phy->name);
}
- len += snprintf(buf + len, (SZ_4K - len),
- "\tPanel = %s\n", display->panel->name);
-
- len += snprintf(buf + len, (SZ_4K - len),
- "\tClock master = %s\n",
+ seq_printf(seq, "\tPanel = %s\n", display->panel->name);
+ seq_printf(seq, "\tClock master = %s\n",
display->ctrl[display->clk_master_idx].ctrl->name);
- if (len > user_len)
- len = user_len;
+ seq_puts(seq, "\tList of modes:\n");
+ list_for_each_entry(mode, &conn->modes, head) {
+ struct dsi_display_mode_priv_info *priv_info;
- if (copy_to_user(user_buf, buf, len)) {
- kfree(buf);
- return -EFAULT;
+ priv_info = (typeof(priv_info)) mode->private;
+
+ seq_printf(seq, "\t\t%dx%d@%d drm_clk:%u dsi_clk:%llu\n",
+ mode->htotal, mode->vtotal, mode->vrefresh,
+ mode->clock, priv_info->clk_rate_hz);
}
- *ppos += len;
+ return 0;
+}
- kfree(buf);
- return len;
+static int debugfs_dump_info_open(struct inode *inode, struct file *f)
+{
+ return single_open(f, debugfs_dump_info_read, inode->i_private);
}
static ssize_t debugfs_misr_setup(struct file *file,
@@ -1403,7 +1364,6 @@
}
DSI_INFO("ESD check is switched to TE mode by user\n");
esd_config->status_mode = ESD_MODE_PANEL_TE;
- dsi_display_change_te_irq_status(display, true);
}
if (!strcmp(buf, "reg_read\n")) {
@@ -1416,8 +1376,6 @@
goto error;
}
esd_config->status_mode = ESD_MODE_REG_READ;
- if (dsi_display_is_te_based_esd(display))
- dsi_display_change_te_irq_status(display, false);
}
if (!strcmp(buf, "esd_sw_sim_success\n"))
@@ -1508,8 +1466,10 @@
}
static const struct file_operations dump_info_fops = {
- .open = simple_open,
- .read = debugfs_dump_info_read,
+ .open = debugfs_dump_info_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
};
static const struct file_operations misr_data_fops = {
@@ -2934,15 +2894,18 @@
} else {
int ctrl_idx = (msg->flags & MIPI_DSI_MSG_UNICAST) ?
msg->ctrl : 0;
- u32 cmd_flags = DSI_CTRL_CMD_FETCH_MEMORY;
+ u32 flags = DSI_CTRL_CMD_FETCH_MEMORY;
+
+ if (msg->rx_buf)
+ flags |= DSI_CTRL_CMD_READ;
if (display->queue_cmd_waits ||
msg->flags & MIPI_DSI_MSG_ASYNC_OVERRIDE)
- cmd_flags |= DSI_CTRL_CMD_ASYNC_WAIT;
+ flags |= DSI_CTRL_CMD_ASYNC_WAIT;
rc = dsi_ctrl_cmd_transfer(display->ctrl[ctrl_idx].ctrl, msg,
- &cmd_flags);
- if (rc) {
+ &flags);
+ if (rc < 0) {
DSI_ERR("[%s] cmd transfer failed, rc=%d\n",
display->name, rc);
goto error_disable_cmd_engine;
@@ -2956,12 +2919,10 @@
display->name, ret);
}
error_disable_clks:
- ret = dsi_display_clk_ctrl(display->dsi_clk_handle,
- DSI_ALL_CLKS, DSI_CLK_OFF);
- if (ret) {
- DSI_ERR("[%s] failed to disable all DSI clocks, rc=%d\n",
- display->name, ret);
- }
+ if (dsi_display_clk_ctrl(display->dsi_clk_handle,
+ DSI_ALL_CLKS, DSI_CLK_OFF))
+ DSI_ERR("[%s] failed to disable all DSI clocks\n",
+ display->name);
error:
return rc;
}
@@ -4619,30 +4580,6 @@
return rc;
}
-static void dsi_display_validate_dms_fps(struct dsi_display_mode *cur_mode,
- struct dsi_display_mode *to_mode)
-{
- u32 cur_fps, to_fps;
- u32 cur_h_active, to_h_active;
- u32 cur_v_active, to_v_active;
-
- cur_fps = cur_mode->timing.refresh_rate;
- to_fps = to_mode->timing.refresh_rate;
- cur_h_active = cur_mode->timing.h_active;
- cur_v_active = cur_mode->timing.v_active;
- to_h_active = to_mode->timing.h_active;
- to_v_active = to_mode->timing.v_active;
-
- if ((cur_h_active == to_h_active) && (cur_v_active == to_v_active) &&
- (cur_fps != to_fps)) {
- to_mode->dsi_mode_flags |= DSI_MODE_FLAG_DMS_FPS;
- DSI_DEBUG("DMS Modeset with FPS change\n");
- } else {
- to_mode->dsi_mode_flags &= ~DSI_MODE_FLAG_DMS_FPS;
- }
-}
-
-
static int dsi_display_set_mode_sub(struct dsi_display *display,
struct dsi_display_mode *mode,
u32 flags)
@@ -4753,8 +4690,6 @@
/* No need to set clkrate pending flag if clocks are same */
if ((!cur_bitclk && !to_bitclk) || (cur_bitclk != to_bitclk))
atomic_set(&display->clkrate_change_pending, 1);
-
- dsi_display_validate_dms_fps(display->panel->cur_mode, mode);
}
if (priv_info->phy_timing_len) {
@@ -4900,9 +4835,6 @@
dsi_config_host_engine_state_for_cont_splash(display);
mutex_unlock(&display->display_lock);
- /* Set the current brightness level */
- dsi_panel_bl_handoff(display->panel);
-
return rc;
clks_disabled:
@@ -5187,6 +5119,8 @@
goto error_host_deinit;
}
+ dsi_panel_debugfs_init(display->panel, display->root);
+
DSI_INFO("Successfully bind display panel '%s'\n", display->name);
display->drm_dev = drm;
@@ -6512,6 +6446,23 @@
return rc;
}
+void dsi_display_set_idle_hint(void *dsi_display, bool is_idle)
+{
+ const struct dsi_display *display = dsi_display;
+
+ if (unlikely(!display || !display->panel))
+ return;
+
+ if (!dsi_panel_initialized(display->panel) ||
+ is_lp_mode(display->panel->power_mode))
+ return;
+
+ if (is_idle)
+ dsi_panel_idle(display->panel);
+ else
+ dsi_panel_wakeup(display->panel);
+}
+
int dsi_display_get_qsync_min_fps(void *display_dsi, u32 mode_fps)
{
struct dsi_display *display = (struct dsi_display *)display_dsi;
@@ -6645,6 +6596,11 @@
adj_mode->timing.h_front_porch,
cur_mode->timing.v_front_porch,
adj_mode->timing.v_front_porch);
+ } else {
+ DSI_DEBUG("Switching to %d FPS with mode switch\n",
+ adj_mode->timing.refresh_rate);
+ adj_mode->dsi_mode_flags |= DSI_MODE_FLAG_DMS_FPS;
+ goto error;
}
}
@@ -7281,6 +7237,14 @@
goto error_ctrl_link_off;
}
}
+ } else {
+ /* Keep the screen brightness for continuous splash. */
+ rc = dsi_panel_bl_brightness_handoff(display->panel);
+ if (rc) {
+ pr_warn("[%s] failed to handoff brightness, rc = %d\n",
+ display->panel->name, rc);
+ /* Ignore error and use default brightness */
+ }
}
goto error;
@@ -7500,6 +7464,9 @@
mutex_unlock(&display->display_lock);
}
+ if (display->panel->funcs && display->panel->funcs->pre_kickoff)
+ display->panel->funcs->pre_kickoff(display->panel);
+
return rc;
}
@@ -7597,6 +7564,7 @@
}
display->panel->panel_initialized = true;
+ display->panel->power_mode = SDE_MODE_DPMS_ON;
DSI_DEBUG("cont splash enabled, display enable not required\n");
return 0;
}
@@ -7623,7 +7591,7 @@
}
/* Block sending pps command if modeset is due to fps difference */
- if ((mode->priv_info->dsc_enabled) &&
+ if (mode->priv_info->dsc_enabled &&
!(mode->dsi_mode_flags & DSI_MODE_FLAG_DMS_FPS)) {
mode->priv_info->dsc.pic_width *= display->ctrl_count;
rc = dsi_panel_update_pps(display->panel);
@@ -7677,15 +7645,30 @@
int dsi_display_post_enable(struct dsi_display *display)
{
- int rc = 0;
+ int rc = 0, err;
- if (!display) {
+ if (!display || !display->panel) {
DSI_ERR("Invalid params\n");
return -EINVAL;
}
mutex_lock(&display->display_lock);
+ /* Some features may need panel extinfo in post_enable */
+ if (!display->panel->vendor_info.extinfo_read) {
+ err = dsi_panel_get_vendor_extinfo(display->panel);
+ if (err)
+ DSI_ERR("[%s] failed to get extinfo, err=%d\n",
+ display->name, err);
+ }
+
+ if (!display->panel->vendor_info.is_sn) {
+ err = dsi_panel_get_sn(display->panel);
+ if (err)
+ DSI_ERR("[%s] failed to get SN, err=%d\n",
+ display->name, err);
+ }
+
if (display->panel->cur_mode->dsi_mode_flags & DSI_MODE_FLAG_POMS) {
if (display->config.panel_mode == DSI_OP_CMD_MODE)
dsi_panel_mode_switch_to_cmd(display->panel);
@@ -7918,7 +7901,7 @@
return rc;
}
-static int __init dsi_display_register(void)
+int __init dsi_display_register(void)
{
dsi_phy_drv_register();
dsi_ctrl_drv_register();
@@ -7928,7 +7911,7 @@
return platform_driver_register(&dsi_display_driver);
}
-static void __exit dsi_display_unregister(void)
+void __exit dsi_display_unregister(void)
{
platform_driver_unregister(&dsi_display_driver);
dsi_ctrl_drv_unregister();
@@ -7942,5 +7925,7 @@
0600);
MODULE_PARM_DESC(dsi_display1,
"msm_drm.dsi_display1=<display node>:<configX> where <display node> is 'secondary dsi display node name' and <configX> where x represents index in the topology list");
+#ifndef CONFIG_DRM_MSM_MODULE
module_init(dsi_display_register);
module_exit(dsi_display_unregister);
+#endif
diff --git a/msm/dsi/dsi_display.h b/msm/dsi/dsi_display.h
index d254dfe..f7d6448 100644
--- a/msm/dsi/dsi_display.h
+++ b/msm/dsi/dsi_display.h
@@ -147,8 +147,8 @@
* @sw_te_using_wd: Is software te enabled
* @display_lock: Mutex for dsi_display interface.
* @disp_te_gpio: GPIO for panel TE interrupt.
- * @is_te_irq_enabled:bool to specify whether TE interrupt is enabled.
- * @esd_te_gate: completion gate to signal TE interrupt.
+ * @te_listeners: List of listeners registered for TE callbacks.
+ * @te_lock: Lock protecting te_listeners list.
* @ctrl_count: Number of DSI interfaces required by panel.
* @ctrl: Controller information for DSI display.
* @panel: Handle to DSI panel.
@@ -202,8 +202,8 @@
bool sw_te_using_wd;
struct mutex display_lock;
int disp_te_gpio;
- bool is_te_irq_enabled;
- struct completion esd_te_gate;
+ struct list_head te_listeners;
+ spinlock_t te_lock;
u32 ctrl_count;
struct dsi_display_ctrl ctrl[MAX_DSI_CTRLS_PER_DISPLAY];
@@ -212,6 +212,7 @@
struct dsi_panel *panel;
struct device_node *panel_node;
struct device_node *parser_node;
+ struct device *panel_info_dev;
/* external bridge */
struct dsi_display_ext_bridge ext_bridge[MAX_DSI_CTRLS_PER_DISPLAY];
@@ -276,6 +277,48 @@
struct workqueue_struct *dma_cmd_workq;
};
+/**
+ * struct dsi_display_te_listener - data for TE listener
+ * @head: List node pointer.
+ * @handler: TE callback function, called in atomic context.
+ * @data: Private data that is not modified by add/remove API
+ */
+struct dsi_display_te_listener {
+ struct list_head head;
+ void (*handler)(struct dsi_display_te_listener *);
+ void *data;
+};
+
+/**
+ * dsi_display_add_te_listener - adds a new listener for TE events
+ * @display: Handle to display
+ * @tl: TE listener struct
+ *
+ * Adds a new TE listener and enables TE irq if there are no other listeners.
+ * Upon TE interrupt, the handler passed in will be called back in atomic
+ * context.
+ *
+ * Note: caller is responsible for lifetime of @tl which should be available
+ * until dsi_display_remove_te_listener() is called.
+ *
+ * Returns: 0 on success, otherwise errno on failure
+ */
+int dsi_display_add_te_listener(struct dsi_display *display,
+ struct dsi_display_te_listener *tl);
+
+/**
+ * dsi_display_add_te_listener - removes listener for TE events
+ * @display: Handle to display
+ * @tl: TE listener struct
+ *
+ * Removes TE listener and disables TE irq if there are no other listeners.
+ *
+ * Returns: 0 on success, otherwise errno on failure
+ */
+int dsi_display_remove_te_listener(struct dsi_display *display,
+ struct dsi_display_te_listener *tl);
+
+
int dsi_display_dev_probe(struct platform_device *pdev);
int dsi_display_dev_remove(struct platform_device *pdev);
@@ -735,4 +778,11 @@
int dsi_display_get_panel_vfp(void *display,
int h_active, int v_active);
+/**
+ * dsi_display_set_idle_hint - gives hint to display whether display is idle
+ * @display: Pointer to private display handle
+ * @is_idle: true if display is idle, false otherwise
+ */
+void dsi_display_set_idle_hint(void *display, bool is_idle);
+
#endif /* _DSI_DISPLAY_H_ */
diff --git a/msm/dsi/dsi_drm.c b/msm/dsi/dsi_drm.c
index 0457c57..f1f14a5 100644
--- a/msm/dsi/dsi_drm.c
+++ b/msm/dsi/dsi_drm.c
@@ -69,6 +69,8 @@
dsi_mode->dsi_mode_flags |= DSI_MODE_FLAG_VBLANK_PRE_MODESET;
if (msm_is_mode_seamless_dms(drm_mode))
dsi_mode->dsi_mode_flags |= DSI_MODE_FLAG_DMS;
+ if (msm_is_mode_seamless_dms_fps(drm_mode))
+ dsi_mode->dsi_mode_flags |= DSI_MODE_FLAG_DMS_FPS;
if (msm_is_mode_seamless_vrr(drm_mode))
dsi_mode->dsi_mode_flags |= DSI_MODE_FLAG_VRR;
if (msm_is_mode_seamless_poms(drm_mode))
@@ -122,6 +124,8 @@
drm_mode->private_flags |= MSM_MODE_FLAG_VBLANK_PRE_MODESET;
if (dsi_mode->dsi_mode_flags & DSI_MODE_FLAG_DMS)
drm_mode->private_flags |= MSM_MODE_FLAG_SEAMLESS_DMS;
+ if (dsi_mode->dsi_mode_flags & DSI_MODE_FLAG_DMS_FPS)
+ drm_mode->private_flags |= MSM_MODE_FLAG_SEAMLESS_DMS_FPS;
if (dsi_mode->dsi_mode_flags & DSI_MODE_FLAG_VRR)
drm_mode->private_flags |= MSM_MODE_FLAG_SEAMLESS_VRR;
if (dsi_mode->dsi_mode_flags & DSI_MODE_FLAG_POMS)
@@ -218,6 +222,9 @@
if (rc)
DSI_ERR("Continuous splash pipeline cleanup failed, rc=%d\n",
rc);
+
+ if (c_bridge->display && c_bridge->display->drm_conn)
+ sde_connector_helper_bridge_pre_enable(c_bridge->display->drm_conn);
}
static void dsi_bridge_enable(struct drm_bridge *bridge)
@@ -244,8 +251,8 @@
DSI_ERR("[%d] DSI display post enabled failed, rc=%d\n",
c_bridge->id, rc);
- if (display && display->drm_conn) {
- sde_connector_helper_bridge_enable(display->drm_conn);
+ if (c_bridge->display && c_bridge->display->drm_conn) {
+ sde_connector_helper_bridge_post_enable(c_bridge->display->drm_conn);
if (c_bridge->dsi_mode.dsi_mode_flags & DSI_MODE_FLAG_POMS)
sde_connector_schedule_status_work(display->drm_conn,
true);
diff --git a/msm/dsi/dsi_panel.c b/msm/dsi/dsi_panel.c
index e1f231f..f7f40b0 100644
--- a/msm/dsi/dsi_panel.c
+++ b/msm/dsi/dsi_panel.c
@@ -4,11 +4,11 @@
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
*/
+#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
-#include <linux/pwm.h>
#include <video/mipi_display.h>
#include "dsi_panel.h"
@@ -26,7 +26,9 @@
#define MAX_TOPOLOGY 5
#define DSI_PANEL_DEFAULT_LABEL "Default dsi panel"
+#define DSI_PANEL_VENDOR_DEFAULT_LABEL "Undefined vendor"
+#define HBM_SV_MAX_MS (10 * 60 * 1000) /* 10 min */
#define DEFAULT_PANEL_JITTER_NUMERATOR 2
#define DEFAULT_PANEL_JITTER_DENOMINATOR 1
#define DEFAULT_PANEL_JITTER_ARRAY_SIZE 2
@@ -96,6 +98,9 @@
static char dsi_dsc_rc_range_bpg_offset[] = {2, 0, 0, -2, -4, -6, -8, -8,
-8, -10, -10, -12, -12, -12, -12};
+static int dsi_panel_update_hbm_locked(struct dsi_panel *panel,
+ enum hbm_mode_type hbm_mode);
+
int dsi_dsc_create_pps_buf_cmd(struct msm_display_dsc_info *dsc, char *buf,
int pps_id)
{
@@ -452,13 +457,6 @@
{
int rc = 0;
- rc = dsi_pwr_enable_regulator(&panel->power_info, true);
- if (rc) {
- DSI_ERR("[%s] failed to enable vregs, rc=%d\n",
- panel->name, rc);
- goto exit;
- }
-
rc = dsi_panel_set_pinctrl_state(panel, true);
if (rc) {
DSI_ERR("[%s] failed to set pinctrl, rc=%d\n", panel->name, rc);
@@ -523,47 +521,33 @@
return rc;
}
-static int dsi_panel_tx_cmd_set(struct dsi_panel *panel,
- enum dsi_cmd_set_type type)
+
+int dsi_panel_cmd_set_transfer(struct dsi_panel *panel,
+ struct dsi_panel_cmd_set *cmd)
{
int rc = 0, i = 0;
ssize_t len;
struct dsi_cmd_desc *cmds;
- u32 count;
- enum dsi_cmd_set_state state;
- struct dsi_display_mode *mode;
const struct mipi_dsi_host_ops *ops = panel->host->ops;
- if (!panel || !panel->cur_mode)
- return -EINVAL;
+ cmds = cmd->cmds;
- mode = panel->cur_mode;
-
- cmds = mode->priv_info->cmd_sets[type].cmds;
- count = mode->priv_info->cmd_sets[type].count;
- state = mode->priv_info->cmd_sets[type].state;
- SDE_EVT32(type, state, count);
-
- if (count == 0) {
- DSI_DEBUG("[%s] No commands to be sent for state(%d)\n",
- panel->name, type);
+ if (cmd->count == 0) {
+ DSI_DEBUG("[%s] No commands to be sent\n", panel->name);
goto error;
}
- for (i = 0; i < count; i++) {
- if (state == DSI_CMD_SET_STATE_LP)
+ for (i = 0; i < cmd->count; i++) {
+ if (cmd->state == DSI_CMD_SET_STATE_LP)
cmds->msg.flags |= MIPI_DSI_MSG_USE_LPM;
if (cmds->last_command)
cmds->msg.flags |= MIPI_DSI_MSG_LASTCOMMAND;
- if (type == DSI_CMD_SET_VID_TO_CMD_SWITCH)
- cmds->msg.flags |= MIPI_DSI_MSG_ASYNC_OVERRIDE;
-
len = ops->transfer(panel->host, &cmds->msg);
if (len < 0) {
rc = len;
- DSI_ERR("failed to set cmds(%d), rc=%d\n", type, rc);
+ DSI_ERR("failed to set cmds, rc=%d\n", rc);
goto error;
}
if (cmds->post_wait_ms)
@@ -623,262 +607,6 @@
return rc;
}
-static int dsi_panel_wled_register(struct dsi_panel *panel,
- struct dsi_backlight_config *bl)
-{
- struct backlight_device *bd;
-
- bd = backlight_device_get_by_type(BACKLIGHT_RAW);
- if (!bd) {
- DSI_ERR("[%s] fail raw backlight register\n", panel->name);
- return -EPROBE_DEFER;
- }
-
- bl->raw_bd = bd;
- return 0;
-}
-
-static int dsi_panel_dcs_set_display_brightness_c2(struct mipi_dsi_device *dsi,
- u32 bl_lvl)
-{
- u16 brightness = (u16)bl_lvl;
- u8 first_byte = brightness & 0xff;
- u8 second_byte = brightness >> 8;
- u8 payload[8] = {second_byte, first_byte,
- second_byte, first_byte,
- second_byte, first_byte,
- second_byte, first_byte};
-
- return mipi_dsi_dcs_write(dsi, 0xC2, payload, sizeof(payload));
-}
-
-
-
-static int dsi_panel_update_backlight(struct dsi_panel *panel,
- u32 bl_lvl)
-{
- int rc = 0;
- struct mipi_dsi_device *dsi;
- struct dsi_backlight_config *bl;
-
- if (!panel || (bl_lvl > 0xffff)) {
- DSI_ERR("invalid params\n");
- return -EINVAL;
- }
-
- dsi = &panel->mipi_device;
- bl = &panel->bl_config;
-
- if (panel->bl_config.bl_inverted_dbv)
- bl_lvl = (((bl_lvl & 0xff) << 8) | (bl_lvl >> 8));
-
- if (panel->bl_config.bl_dcs_subtype == 0xc2)
- rc = dsi_panel_dcs_set_display_brightness_c2(dsi, bl_lvl);
- else
- rc = mipi_dsi_dcs_set_display_brightness(dsi, bl_lvl);
-
- if (rc < 0)
- DSI_ERR("failed to update dcs backlight:%d\n", bl_lvl);
-
- return rc;
-}
-
-static int dsi_panel_update_pwm_backlight(struct dsi_panel *panel,
- u32 bl_lvl)
-{
- int rc = 0;
- u32 duty = 0;
- u32 period_ns = 0;
- struct dsi_backlight_config *bl;
-
- if (!panel) {
- DSI_ERR("Invalid Params\n");
- return -EINVAL;
- }
-
- bl = &panel->bl_config;
- if (!bl->pwm_bl) {
- DSI_ERR("pwm device not found\n");
- return -EINVAL;
- }
-
- period_ns = bl->pwm_period_usecs * NSEC_PER_USEC;
- duty = bl_lvl * period_ns;
- duty /= bl->bl_max_level;
-
- rc = pwm_config(bl->pwm_bl, duty, period_ns);
- if (rc) {
- DSI_ERR("[%s] failed to change pwm config, rc=\n", panel->name,
- rc);
- goto error;
- }
-
- if (bl_lvl == 0 && bl->pwm_enabled) {
- pwm_disable(bl->pwm_bl);
- bl->pwm_enabled = false;
- return 0;
- }
-
- if (!bl->pwm_enabled) {
- rc = pwm_enable(bl->pwm_bl);
- if (rc) {
- DSI_ERR("[%s] failed to enable pwm, rc=\n", panel->name,
- rc);
- goto error;
- }
-
- bl->pwm_enabled = true;
- }
-
-error:
- return rc;
-}
-
-int dsi_panel_set_backlight(struct dsi_panel *panel, u32 bl_lvl)
-{
- int rc = 0;
- struct dsi_backlight_config *bl = &panel->bl_config;
-
- if (panel->host_config.ext_bridge_mode)
- return 0;
-
- DSI_DEBUG("backlight type:%d lvl:%d\n", bl->type, bl_lvl);
- switch (bl->type) {
- case DSI_BACKLIGHT_WLED:
- rc = backlight_device_set_brightness(bl->raw_bd, bl_lvl);
- break;
- case DSI_BACKLIGHT_DCS:
- rc = dsi_panel_update_backlight(panel, bl_lvl);
- break;
- case DSI_BACKLIGHT_EXTERNAL:
- break;
- case DSI_BACKLIGHT_PWM:
- rc = dsi_panel_update_pwm_backlight(panel, bl_lvl);
- break;
- default:
- DSI_ERR("Backlight type(%d) not supported\n", bl->type);
- rc = -ENOTSUPP;
- }
-
- return rc;
-}
-
-static u32 dsi_panel_get_brightness(struct dsi_backlight_config *bl)
-{
- u32 cur_bl_level;
- struct backlight_device *bd = bl->raw_bd;
-
- /* default the brightness level to 50% */
- cur_bl_level = bl->bl_max_level >> 1;
-
- switch (bl->type) {
- case DSI_BACKLIGHT_WLED:
- /* Try to query the backlight level from the backlight device */
- if (bd->ops && bd->ops->get_brightness)
- cur_bl_level = bd->ops->get_brightness(bd);
- break;
- case DSI_BACKLIGHT_DCS:
- case DSI_BACKLIGHT_EXTERNAL:
- case DSI_BACKLIGHT_PWM:
- default:
- /*
- * Ideally, we should read the backlight level from the
- * panel. For now, just set it default value.
- */
- break;
- }
-
- DSI_DEBUG("cur_bl_level=%d\n", cur_bl_level);
- return cur_bl_level;
-}
-
-void dsi_panel_bl_handoff(struct dsi_panel *panel)
-{
- struct dsi_backlight_config *bl = &panel->bl_config;
-
- bl->bl_level = dsi_panel_get_brightness(bl);
-}
-
-static int dsi_panel_pwm_register(struct dsi_panel *panel)
-{
- int rc = 0;
- struct dsi_backlight_config *bl = &panel->bl_config;
-
- bl->pwm_bl = devm_of_pwm_get(panel->parent, panel->panel_of_node, NULL);
- if (IS_ERR_OR_NULL(bl->pwm_bl)) {
- rc = PTR_ERR(bl->pwm_bl);
- DSI_ERR("[%s] failed to request pwm, rc=%d\n", panel->name,
- rc);
- return rc;
- }
-
- return 0;
-}
-
-static int dsi_panel_bl_register(struct dsi_panel *panel)
-{
- int rc = 0;
- struct dsi_backlight_config *bl = &panel->bl_config;
-
- if (panel->host_config.ext_bridge_mode)
- return 0;
-
- switch (bl->type) {
- case DSI_BACKLIGHT_WLED:
- rc = dsi_panel_wled_register(panel, bl);
- break;
- case DSI_BACKLIGHT_DCS:
- break;
- case DSI_BACKLIGHT_EXTERNAL:
- break;
- case DSI_BACKLIGHT_PWM:
- rc = dsi_panel_pwm_register(panel);
- break;
- default:
- DSI_ERR("Backlight type(%d) not supported\n", bl->type);
- rc = -ENOTSUPP;
- goto error;
- }
-
-error:
- return rc;
-}
-
-static void dsi_panel_pwm_unregister(struct dsi_panel *panel)
-{
- struct dsi_backlight_config *bl = &panel->bl_config;
-
- devm_pwm_put(panel->parent, bl->pwm_bl);
-}
-
-static int dsi_panel_bl_unregister(struct dsi_panel *panel)
-{
- int rc = 0;
- struct dsi_backlight_config *bl = &panel->bl_config;
-
- if (panel->host_config.ext_bridge_mode)
- return 0;
-
- switch (bl->type) {
- case DSI_BACKLIGHT_WLED:
- break;
- case DSI_BACKLIGHT_DCS:
- break;
- case DSI_BACKLIGHT_EXTERNAL:
- break;
- case DSI_BACKLIGHT_PWM:
- dsi_panel_pwm_unregister(panel);
- break;
- default:
- DSI_ERR("Backlight type(%d) not supported\n", bl->type);
- rc = -ENOTSUPP;
- goto error;
- }
-
-error:
- return rc;
-}
-
static int dsi_panel_parse_timing(struct dsi_mode_info *mode,
struct dsi_parser_utils *utils)
{
@@ -1281,6 +1009,377 @@
split_link->split_link_enabled = true;
}
+static int dsi_panel_create_sn_buf(struct dsi_panel *panel)
+{
+ struct dsi_panel_vendor_info *const vendor_info = &panel->vendor_info;
+ struct dsi_panel_sn_location *const location = &vendor_info->location;
+ ssize_t rc = 0;
+ u32 sn_str_size;
+ u8 *tmp_sn_buf;
+
+ if (!panel)
+ return -EINVAL;
+
+ if (!location->addr || !location->sn_length) {
+ DSI_ERR("[%s] invalid location\n", __func__);
+ return -EINVAL;
+ }
+
+ // prepare buffer for bin2hex()
+ // e.g. buf[0] = 0x01 -> sn[0] = '0' and sn[1] = '1'
+ sn_str_size = location->sn_length * 2;
+ tmp_sn_buf = kmalloc(sn_str_size + 1, GFP_KERNEL);
+ if (!tmp_sn_buf)
+ return -ENOMEM;
+
+ mutex_lock(&panel->panel_lock);
+ if (!vendor_info->sn) {
+ vendor_info->is_sn = false;
+ vendor_info->sn = tmp_sn_buf;
+ } else
+ kfree(tmp_sn_buf);
+
+ mutex_unlock(&panel->panel_lock);
+ return rc;
+}
+
+static int dsi_panel_release_sn_buf(struct dsi_panel *panel)
+{
+ struct dsi_panel_vendor_info *const vendor_info = &panel->vendor_info;
+ ssize_t rc = 0;
+
+ if (!panel)
+ return -EINVAL;
+
+ mutex_lock(&panel->panel_lock);
+ kfree(vendor_info->sn);
+ vendor_info->sn = NULL;
+ vendor_info->is_sn = false;
+ mutex_unlock(&panel->panel_lock);
+ return rc;
+}
+
+int dsi_panel_get_sn(struct dsi_panel *panel)
+{
+ struct dsi_panel_vendor_info *const vendor_info = &panel->vendor_info;
+ struct dsi_panel_sn_location *const location = &vendor_info->location;
+ ssize_t rc = 0;
+ u32 read_size, read_back_size, sn_str_size;
+ u8 *buf;
+
+ if (!panel || !panel->panel_initialized) {
+ DSI_ERR("panel is not ready\n");
+ return -EINVAL;
+ }
+
+ read_size = location->start_byte + location->sn_length;
+ buf = kmalloc(read_size, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ mutex_lock(&panel->panel_lock);
+
+ if (!panel->vendor_info.sn) {
+ rc = -EINVAL;
+ goto out_mutex;
+ }
+
+ read_back_size = mipi_dsi_dcs_read(&panel->mipi_device, location->addr,
+ buf, read_size);
+ if (read_back_size == read_size) {
+ bin2hex(vendor_info->sn, &buf[location->start_byte],
+ location->sn_length);
+ // e.g. buf[0] = 0x01 -> sn[0] = '0' and sn[1] = '1'
+ sn_str_size = location->sn_length * 2;
+ vendor_info->sn[sn_str_size] = '\0';
+ vendor_info->is_sn = true;
+ } else {
+ DSI_ERR("failed to read: addr=0x%X, read_back_size=%d, read_size=%d\n",
+ location->addr, read_back_size, read_size);
+ rc = -EINVAL;
+ }
+
+ kfree(buf);
+out_mutex:
+ mutex_unlock(&panel->panel_lock);
+ return rc;
+}
+
+static int dsi_panel_parse_sn_location(struct dsi_panel *panel,
+ struct device_node *of_node)
+{
+ struct dsi_panel_vendor_info *const vendor_info = &panel->vendor_info;
+ int rc = 0, len = 0;
+ u32 array[3] = {0};
+
+ if (!panel)
+ return -EINVAL;
+
+ len = of_property_count_u32_elems(of_node,
+ "google,mdss-dsi-panel-sn-location");
+ if (len != 3) {
+ DSI_ERR("[%s] invalid format\n", __func__);
+ rc = -EINVAL;
+ goto error;
+ }
+
+ rc = of_property_read_u32_array(of_node,
+ "google,mdss-dsi-panel-sn-location", array, len);
+ if (rc || !array[0] || !array[2]) {
+ DSI_ERR("[%s] invalid format\n", __func__);
+ rc = -EINVAL;
+ goto error;
+ }
+
+ vendor_info->location.addr = array[0];
+ vendor_info->location.start_byte = array[1];
+ vendor_info->location.sn_length = array[2];
+
+ DSI_DEBUG("addr=0x%x, start=%d, length=%d",
+ vendor_info->location.addr, vendor_info->location.start_byte,
+ vendor_info->location.sn_length);
+error:
+ return rc;
+}
+
+int dsi_panel_get_vendor_extinfo(struct dsi_panel *panel)
+{
+ struct dsi_panel_vendor_info *const vendor_info = &panel->vendor_info;
+ char buffer[128];
+ size_t bytes = 0;
+ u32 read_addr, read_start, read_len, dsi_read_len;
+ int i, rc = 0;
+
+ mutex_lock(&panel->panel_lock);
+
+ /* Transmit the commands as specified. */
+ for (i = 0; i < vendor_info->extinfo_loc_length; i += 3) {
+ read_addr = vendor_info->extinfo_loc[i];
+ read_start = vendor_info->extinfo_loc[i + 1];
+ read_len = vendor_info->extinfo_loc[i + 2];
+
+ /* Compute size of entire DSI read, starting from byte zero. */
+ dsi_read_len = read_start + read_len;
+
+ if (dsi_read_len > sizeof(buffer)) {
+ DSI_ERR("Read is too large for buffer.\n");
+ rc = -EINVAL;
+ goto error;
+ } else if (read_len > vendor_info->extinfo_length - bytes) {
+ DSI_ERR("Ran out of space reading extinfo data.\n");
+ rc = -EINVAL;
+ goto error;
+ }
+
+ rc = mipi_dsi_dcs_read(&panel->mipi_device,
+ read_addr,
+ buffer,
+ dsi_read_len);
+ if (rc < 0) {
+ DSI_ERR("mipi_dsi_dcs_read failed with rc=%d.\n", rc);
+ goto error;
+ }
+
+ /* Copy only the specified portion of the DSI read, which is
+ * read_len bytes starting at read_start.
+ */
+ memcpy(&vendor_info->extinfo[bytes], &buffer[read_start],
+ read_len);
+ bytes += read_len;
+ }
+
+ vendor_info->extinfo_read = bytes;
+ rc = 0;
+
+error:
+
+ if (rc < 0) {
+ DSI_ERR("Failed to read vendor extinfo. Freeing extinfo memory.\n");
+
+ vendor_info->extinfo_loc_length = 0;
+ vendor_info->extinfo_length = 0;
+ vendor_info->extinfo_read = 0;
+
+ kfree(vendor_info->extinfo_loc);
+ vendor_info->extinfo_loc = NULL;
+
+ kfree(vendor_info->extinfo);
+ vendor_info->extinfo = NULL;
+ }
+
+ mutex_unlock(&panel->panel_lock);
+ return rc;
+}
+
+static int dsi_panel_release_vendor_extinfo(struct dsi_panel *panel)
+{
+ struct dsi_panel_vendor_info *const vendor_info = &panel->vendor_info;
+
+ mutex_lock(&panel->panel_lock);
+
+ vendor_info->extinfo_loc_length = 0;
+ vendor_info->extinfo_length = 0;
+ vendor_info->extinfo_read = 0;
+
+ kfree(vendor_info->extinfo_loc);
+ vendor_info->extinfo_loc = NULL;
+
+ kfree(vendor_info->extinfo);
+ vendor_info->extinfo = NULL;
+
+ mutex_unlock(&panel->panel_lock);
+ return 0;
+}
+
+static int dsi_panel_parse_vendor_extinfo_location(struct dsi_panel *panel,
+ struct device_node *of_node)
+{
+ u32 extinfo_location[24];
+ int size_bytes, loc_length;
+ int extinfo_length = 0;
+ int i, rc = 0;
+
+ if (!of_property_read_bool(
+ of_node, "google,mdss-dsi-panel-vendor-extinfo-loc")) {
+ pr_info("Could not find optional vendor extinfo.\n");
+ rc = 0;
+ goto error;
+ }
+
+ if (IS_ERR(of_get_property(of_node,
+ "google,mdss-dsi-panel-vendor-extinfo-loc",
+ &size_bytes))) {
+ DSI_ERR("Failed to read google,mdss-dsi-panel-vendor-extinfo-loc.\n");
+ goto error;
+ }
+ loc_length = size_bytes / 4;
+
+ if (size_bytes > sizeof(extinfo_location)) {
+ DSI_ERR("Length (%d) is larger than allocated (%d).\n",
+ size_bytes, sizeof(extinfo_location));
+ rc = -EINVAL;
+ goto error;
+ } else if (size_bytes % 4 != 0 || loc_length % 3 != 0) {
+ DSI_ERR("Size (%d) is not 32-bit aligned and/or the length (%d) is not divisible by 3.\n",
+ size_bytes, loc_length);
+ rc = -EINVAL;
+ goto error;
+ }
+
+ rc = of_property_read_u32_array(
+ of_node, "google,mdss-dsi-panel-vendor-extinfo-loc",
+ extinfo_location, loc_length);
+ if (rc) {
+ DSI_ERR("Error (%d) reading extinfo location.\n", rc);
+ goto error;
+ }
+
+ /* Compute expected length of extinfo */
+ for (i = 0; i < loc_length; i += 3) {
+ /* Each register read consists of three u32 values specifying
+ * the register address, start byte of the target range, and the
+ * length of the target range. Length is the 3rd u32.
+ */
+ extinfo_length += extinfo_location[i + 2];
+ }
+
+ panel->vendor_info.extinfo_loc = kzalloc(size_bytes, GFP_KERNEL);
+ panel->vendor_info.extinfo = kzalloc(extinfo_length, GFP_KERNEL);
+ if (!panel->vendor_info.extinfo_loc || !panel->vendor_info.extinfo) {
+ DSI_ERR("Failed to allocate extinfo buffer memory.\n");
+
+ kfree(panel->vendor_info.extinfo_loc);
+ panel->vendor_info.extinfo_loc = NULL;
+
+ kfree(panel->vendor_info.extinfo);
+ panel->vendor_info.extinfo = NULL;
+
+ rc = -ENOMEM;
+ goto error;
+ }
+ memcpy(panel->vendor_info.extinfo_loc, extinfo_location, size_bytes);
+ panel->vendor_info.extinfo_loc_length = loc_length;
+ panel->vendor_info.extinfo_length = extinfo_length;
+ rc = 0;
+
+error:
+ return rc;
+}
+
+/*
+ * Copy the display panel extended info to a buffer provided by the caller.
+ *
+ * panel - pointer to a drm_panel
+ * buffer - destination for extinfo data
+ * len - size of destination buffer
+ *
+ * If there is no extinfo to read, returns 0
+ * If provided buffer is NULL, returns the size of the extinfo in bytes
+ * If len is less than the extinfo length, returns -ENOSPC
+ * If there is extinfo pending read, returns -EBUSY
+ * Otherwise, copies extinfo to buffer and returns the number of bytes copied
+ */
+int dsi_panel_read_vendor_extinfo(struct drm_panel *panel, char *buffer,
+ size_t len)
+{
+ struct dsi_panel *p = container_of(panel, struct dsi_panel, drm_panel);
+ struct dsi_panel_vendor_info *const vendor_info = &p->vendor_info;
+
+ if (!panel)
+ return -EINVAL;
+
+ /* Return zero if there is no extinfo to read */
+ if (vendor_info->extinfo_loc_length == 0)
+ return 0;
+
+ /* If buffer is null, this is a request for the length of extinfo */
+ if (!buffer)
+ return vendor_info->extinfo_length;
+
+ /* Verify adequate buffer space */
+ if (len < vendor_info->extinfo_length)
+ return -ENOSPC;
+
+ /* Return -EBUSY if there is extinfo to read, but read is pending */
+ if (vendor_info->extinfo_read == 0)
+ return -EBUSY;
+
+ memcpy(buffer, vendor_info->extinfo, vendor_info->extinfo_length);
+ return vendor_info->extinfo_length;
+}
+EXPORT_SYMBOL(dsi_panel_read_vendor_extinfo);
+
+static int dsi_panel_parse_vendor_info(struct dsi_panel *panel,
+ struct device_node *of_node)
+{
+ int rc = 0;
+ struct dsi_panel_vendor_info *const vendor_info = &panel->vendor_info;
+
+ if (!panel)
+ return -EINVAL;
+
+ vendor_info->name = of_get_property(of_node,
+ "google,mdss-dsi-panel-vendor", NULL);
+
+ if (!vendor_info->name) {
+ vendor_info->name = DSI_PANEL_VENDOR_DEFAULT_LABEL;
+ rc = -EINVAL;
+ goto error;
+ }
+ rc = dsi_panel_parse_sn_location(panel, of_node);
+ if (rc) {
+ DSI_ERR("[%s] failed to parse the parameter for SN, rc=%d\n",
+ panel->name, rc);
+ }
+ rc = dsi_panel_parse_vendor_extinfo_location(panel, of_node);
+ if (rc) {
+ DSI_ERR("[%s] failed to parse the extended panel info, rc=%d\n",
+ panel->name, rc);
+ }
+error:
+ return rc;
+}
+
static int dsi_panel_parse_host_config(struct dsi_panel *panel)
{
int rc = 0;
@@ -1824,6 +1923,7 @@
"qcom,mdss-dsi-lp1-command",
"qcom,mdss-dsi-lp2-command",
"qcom,mdss-dsi-nolp-command",
+ "qcom,mdss-dsi-post-nolp-command",
"PPS not parsed from DTSI, generated dynamically",
"ROI not parsed from DTSI, generated dynamically",
"qcom,mdss-dsi-timing-switch-command",
@@ -1850,6 +1950,7 @@
"qcom,mdss-dsi-lp1-command-state",
"qcom,mdss-dsi-lp2-command-state",
"qcom,mdss-dsi-nolp-command-state",
+ "qcom,mdss-dsi-post-nolp-command-state",
"PPS not parsed from DTSI, generated dynamically",
"ROI not parsed from DTSI, generated dynamically",
"qcom,mdss-dsi-timing-switch-command-state",
@@ -1858,6 +1959,28 @@
"qcom,mdss-dsi-qsync-off-commands-state",
};
+static int dsi_panel_tx_cmd_set(struct dsi_panel *panel,
+ enum dsi_cmd_set_type type)
+{
+ struct dsi_display_mode *mode;
+ struct dsi_panel_cmd_set *cmd;
+
+ if (!panel || !panel->cur_mode)
+ return -EINVAL;
+
+ mode = panel->cur_mode;
+ cmd = &mode->priv_info->cmd_sets[type];
+
+ if ((cmd->count) &&
+ (type >= 0) && (type < DSI_CMD_SET_MAX))
+ pr_debug("send cmdset %s\n", cmd_set_prop_map[type]);
+
+ if (type == DSI_CMD_SET_VID_TO_CMD_SWITCH)
+ cmd->cmds->msg.flags |= MIPI_DSI_MSG_ASYNC_OVERRIDE;
+
+ return dsi_panel_cmd_set_transfer(panel, cmd);
+}
+
static int dsi_panel_get_cmd_pkt_count(const char *data, u32 length, u32 *cnt)
{
const u32 cmd_set_min_size = 7;
@@ -1927,7 +2050,7 @@
return rc;
}
-static void dsi_panel_destroy_cmd_packets(struct dsi_panel_cmd_set *set)
+void dsi_panel_destroy_cmd_packets(struct dsi_panel_cmd_set *set)
{
u32 i = 0;
struct dsi_cmd_desc *cmd;
@@ -1938,7 +2061,7 @@
}
}
-static void dsi_panel_dealloc_cmd_packets(struct dsi_panel_cmd_set *set)
+void dsi_panel_dealloc_cmd_packets(struct dsi_panel_cmd_set *set)
{
kfree(set->cmds);
}
@@ -1958,36 +2081,18 @@
}
static int dsi_panel_parse_cmd_sets_sub(struct dsi_panel_cmd_set *cmd,
- enum dsi_cmd_set_type type,
- struct dsi_parser_utils *utils)
+ const char *data,
+ size_t length)
{
int rc = 0;
- u32 length = 0;
- const char *data;
- const char *state;
u32 packet_count = 0;
- data = utils->get_property(utils->data, cmd_set_prop_map[type],
- &length);
- if (!data) {
- DSI_DEBUG("%s commands not defined\n", cmd_set_prop_map[type]);
- rc = -ENOTSUPP;
- goto error;
- }
-
- DSI_DEBUG("type=%d, name=%s, length=%d\n", type,
- cmd_set_prop_map[type], length);
-
- print_hex_dump_debug("", DUMP_PREFIX_NONE,
- 8, 1, data, length, false);
-
rc = dsi_panel_get_cmd_pkt_count(data, length, &packet_count);
if (rc) {
DSI_ERR("commands failed, rc=%d\n", rc);
goto error;
}
- DSI_DEBUG("[%s] packet-count=%d, %d\n", cmd_set_prop_map[type],
- packet_count, length);
+ DSI_DEBUG("packet-count=%d, %d\n", packet_count, length);
rc = dsi_panel_alloc_cmd_packets(cmd, packet_count);
if (rc) {
@@ -2002,24 +2107,62 @@
goto error_free_mem;
}
- state = utils->get_property(utils->data, cmd_set_state_map[type], NULL);
- if (!state || !strcmp(state, "dsi_lp_mode")) {
- cmd->state = DSI_CMD_SET_STATE_LP;
- } else if (!strcmp(state, "dsi_hs_mode")) {
- cmd->state = DSI_CMD_SET_STATE_HS;
- } else {
- DSI_ERR("[%s] command state unrecognized-%s\n",
- cmd_set_state_map[type], state);
- goto error_free_mem;
- }
-
return rc;
error_free_mem:
kfree(cmd->cmds);
cmd->cmds = NULL;
error:
return rc;
+}
+int dsi_panel_parse_dt_cmd_set(struct device_node *of_node,
+ const char *cmd_str,
+ const char *cmd_state_str,
+ struct dsi_panel_cmd_set *cmd)
+{
+ const char *data;
+ const char *state;
+ enum dsi_cmd_set_state st;
+ u32 length = 0;
+ int rc;
+
+ data = of_get_property(of_node, cmd_str, &length);
+ if (!data) {
+ pr_debug("%s commands not defined\n", cmd_str);
+ return -ENOTSUPP;
+ }
+
+ pr_debug("name=%s, length=%d\n", cmd_str, length);
+
+ print_hex_dump_debug("", DUMP_PREFIX_NONE,
+ 8, 1, data, length, false);
+
+ state = of_get_property(of_node, cmd_state_str, NULL);
+ if (!state || !strcmp(state, "dsi_lp_mode")) {
+ st = DSI_CMD_SET_STATE_LP;
+ } else if (!strcmp(state, "dsi_hs_mode")) {
+ st = DSI_CMD_SET_STATE_HS;
+ } else {
+ DSI_ERR("[%s] command state unrecognized-%s\n",
+ cmd_state_str, state);
+ return -ENOTSUPP;
+ }
+
+ rc = dsi_panel_parse_cmd_sets_sub(cmd, data, length);
+ if (rc)
+ return rc;
+
+ cmd->state = st;
+
+ return 0;
+}
+
+static int dsi_panel_parse_cmd_sets_dt(struct dsi_panel_cmd_set *cmd,
+ enum dsi_cmd_set_type type,
+ struct dsi_parser_utils *utils)
+{
+ return dsi_panel_parse_dt_cmd_set(utils->data, cmd_set_prop_map[type],
+ cmd_set_state_map[type], cmd);
}
static int dsi_panel_parse_cmd_sets(
@@ -2047,7 +2190,7 @@
i, rc);
set->state = DSI_CMD_SET_STATE_LP;
} else {
- rc = dsi_panel_parse_cmd_sets_sub(set, i, utils);
+ rc = dsi_panel_parse_cmd_sets_dt(set, i, utils);
if (rc)
DSI_DEBUG("failed to parse set %d\n", i);
}
@@ -2100,7 +2243,8 @@
rc = utils->read_u32_array(utils->data, "qcom,mdss-dsi-reset-sequence",
arr_32, length);
if (rc) {
- DSI_ERR("[%s] cannot read dso-reset-seqience\n", panel->name);
+ DSI_ERR("[%s] cannot read qcom,mdss-dsi-reset-sequence\n",
+ panel->name);
goto error_free_arr_32;
}
@@ -2131,6 +2275,7 @@
static int dsi_panel_parse_misc_features(struct dsi_panel *panel)
{
struct dsi_parser_utils *utils = &panel->utils;
+ u32 val;
panel->ulps_feature_enabled =
utils->read_bool(utils->data, "qcom,ulps-enabled");
@@ -2156,6 +2301,15 @@
panel->reset_gpio_always_on = utils->read_bool(utils->data,
"qcom,platform-reset-gpio-always-on");
+ if (!utils->read_u32(utils->data,
+ "qcom,mdss-dsi-init-delay-us",
+ &val)) {
+ pr_debug("Panel init delay specified: %d\n", val);
+ panel->init_delay_us = val;
+ } else {
+ panel->init_delay_us = 0;
+ }
+
return 0;
}
@@ -2314,141 +2468,6 @@
return rc;
}
-static int dsi_panel_parse_bl_pwm_config(struct dsi_panel *panel)
-{
- int rc = 0;
- u32 val;
- struct dsi_backlight_config *config = &panel->bl_config;
- struct dsi_parser_utils *utils = &panel->utils;
-
- rc = utils->read_u32(utils->data, "qcom,bl-pmic-pwm-period-usecs",
- &val);
- if (rc) {
- DSI_ERR("bl-pmic-pwm-period-usecs is not defined, rc=%d\n", rc);
- goto error;
- }
- config->pwm_period_usecs = val;
-
-error:
- return rc;
-}
-
-static int dsi_panel_parse_bl_config(struct dsi_panel *panel)
-{
- int rc = 0;
- u32 val = 0;
- const char *bl_type;
- const char *data;
- struct dsi_parser_utils *utils = &panel->utils;
- char *bl_name;
-
- if (!strcmp(panel->type, "primary"))
- bl_name = "qcom,mdss-dsi-bl-pmic-control-type";
- else
- bl_name = "qcom,mdss-dsi-sec-bl-pmic-control-type";
-
- bl_type = utils->get_property(utils->data, bl_name, NULL);
- if (!bl_type) {
- panel->bl_config.type = DSI_BACKLIGHT_UNKNOWN;
- } else if (!strcmp(bl_type, "bl_ctrl_pwm")) {
- panel->bl_config.type = DSI_BACKLIGHT_PWM;
- } else if (!strcmp(bl_type, "bl_ctrl_wled")) {
- panel->bl_config.type = DSI_BACKLIGHT_WLED;
- } else if (!strcmp(bl_type, "bl_ctrl_dcs")) {
- panel->bl_config.type = DSI_BACKLIGHT_DCS;
- } else if (!strcmp(bl_type, "bl_ctrl_external")) {
- panel->bl_config.type = DSI_BACKLIGHT_EXTERNAL;
- } else {
- DSI_DEBUG("[%s] bl-pmic-control-type unknown-%s\n",
- panel->name, bl_type);
- panel->bl_config.type = DSI_BACKLIGHT_UNKNOWN;
- }
-
- data = utils->get_property(utils->data, "qcom,bl-update-flag", NULL);
- if (!data) {
- panel->bl_config.bl_update = BL_UPDATE_NONE;
- } else if (!strcmp(data, "delay_until_first_frame")) {
- panel->bl_config.bl_update = BL_UPDATE_DELAY_UNTIL_FIRST_FRAME;
- } else {
- DSI_DEBUG("[%s] No valid bl-update-flag: %s\n",
- panel->name, data);
- panel->bl_config.bl_update = BL_UPDATE_NONE;
- }
-
- panel->bl_config.bl_scale = MAX_BL_SCALE_LEVEL;
- panel->bl_config.bl_scale_sv = MAX_SV_BL_SCALE_LEVEL;
-
- rc = utils->read_u32(utils->data, "qcom,mdss-dsi-bl-min-level", &val);
- if (rc) {
- DSI_DEBUG("[%s] bl-min-level unspecified, defaulting to zero\n",
- panel->name);
- panel->bl_config.bl_min_level = 0;
- } else {
- panel->bl_config.bl_min_level = val;
- }
-
- rc = utils->read_u32(utils->data, "qcom,mdss-dsi-bl-max-level", &val);
- if (rc) {
- DSI_DEBUG("[%s] bl-max-level unspecified, defaulting to max level\n",
- panel->name);
- panel->bl_config.bl_max_level = MAX_BL_LEVEL;
- } else {
- panel->bl_config.bl_max_level = val;
- }
-
- rc = utils->read_u32(utils->data, "qcom,mdss-brightness-max-level",
- &val);
- if (rc) {
- DSI_DEBUG("[%s] brigheness-max-level unspecified, defaulting to 255\n",
- panel->name);
- panel->bl_config.brightness_max_level = 255;
- } else {
- panel->bl_config.brightness_max_level = val;
- }
-
- rc = utils->read_u32(utils->data, "qcom,mdss-dsi-bl-ctrl-dcs-subtype",
- &val);
- if (rc) {
- DSI_DEBUG("[%s] bl-ctrl-dcs-subtype, defautling to zero\n",
- panel->name);
- panel->bl_config.bl_dcs_subtype = 0;
- } else {
- panel->bl_config.bl_dcs_subtype = val;
- }
-
- panel->bl_config.bl_inverted_dbv = utils->read_bool(utils->data,
- "qcom,mdss-dsi-bl-inverted-dbv");
-
- if (panel->bl_config.type == DSI_BACKLIGHT_PWM) {
- rc = dsi_panel_parse_bl_pwm_config(panel);
- if (rc) {
- DSI_ERR("[%s] failed to parse pwm config, rc=%d\n",
- panel->name, rc);
- goto error;
- }
- }
-
- panel->bl_config.en_gpio = utils->get_named_gpio(utils->data,
- "qcom,platform-bklight-en-gpio",
- 0);
- if (!gpio_is_valid(panel->bl_config.en_gpio)) {
- if (panel->bl_config.en_gpio == -EPROBE_DEFER) {
- DSI_DEBUG("[%s] failed to get bklt gpio, rc=%d\n",
- panel->name, rc);
- rc = -EPROBE_DEFER;
- goto error;
- } else {
- DSI_DEBUG("[%s] failed to get bklt gpio, rc=%d\n",
- panel->name, rc);
- rc = 0;
- goto error;
- }
- }
-
-error:
- return rc;
-}
-
void dsi_dsc_pclk_param_calc(struct msm_display_dsc_info *dsc, int intf_width)
{
int slice_per_pkt, slice_per_intf;
@@ -2481,7 +2500,6 @@
dsc->pkt_per_line = slice_per_intf / slice_per_pkt;
}
-
int dsi_dsc_populate_static_param(struct msm_display_dsc_info *dsc)
{
int bpp, bpc;
@@ -2702,10 +2720,13 @@
priv_info = mode->priv_info;
priv_info->dsc_enabled = false;
+ mode->timing.dsc_enabled = false;
compression = utils->get_property(utils->data,
"qcom,compression-mode", NULL);
- if (compression && !strcmp(compression, "dsc"))
+ if (compression && !strcmp(compression, "dsc")) {
priv_info->dsc_enabled = true;
+ mode->timing.dsc_enabled = true;
+ }
if (!priv_info->dsc_enabled) {
DSI_DEBUG("dsc compression is not enabled for the mode\n");
@@ -2715,7 +2736,6 @@
rc = utils->read_u32(utils->data, "qcom,mdss-dsc-version", &data);
if (rc) {
priv_info->dsc.version = 0x11;
- rc = 0;
} else {
priv_info->dsc.version = data & 0xff;
/* only support DSC 1.1 rev */
@@ -2730,7 +2750,6 @@
rc = utils->read_u32(utils->data, "qcom,mdss-dsc-scr-version", &data);
if (rc) {
priv_info->dsc.scr_rev = 0x0;
- rc = 0;
} else {
priv_info->dsc.scr_rev = data & 0xff;
/* only one scr rev supported */
@@ -2809,10 +2828,13 @@
dsi_dsc_populate_static_param(&priv_info->dsc);
dsi_dsc_pclk_param_calc(&priv_info->dsc, intf_width);
- mode->timing.dsc_enabled = true;
mode->timing.dsc = &priv_info->dsc;
+ return 0;
+
error:
+ priv_info->dsc_enabled = false;
+ mode->timing.dsc_enabled = false;
return rc;
}
@@ -3176,7 +3198,7 @@
if (!esd_config)
return -EINVAL;
- dsi_panel_parse_cmd_sets_sub(&esd_config->status_cmd,
+ dsi_panel_parse_cmd_sets_dt(&esd_config->status_cmd,
DSI_CMD_SET_PANEL_STATUS, utils);
if (!esd_config->status_cmd.count) {
DSI_ERR("panel status command parsing failed\n");
@@ -3343,6 +3365,61 @@
return rc;
}
+static int dsi_panel_parse_te2_config(struct dsi_panel *panel)
+{
+ u8 i;
+ int rc;
+ u32 length, number_of_te2, val;
+ const char *data;
+ struct dsi_parser_utils *utils;
+ u32 arr_32[TE2_EDGE_MAX * 2] = {0};
+
+ if (unlikely(!panel))
+ return -EINVAL;
+
+ utils = &panel->utils;
+ data = utils->get_property(utils->data,
+ "google,mdss-dsi-te2-info", &length);
+ if (!data) {
+ DSI_INFO("[%s] dsi-te2-info not found\n", panel->name);
+ return -EINVAL;
+ }
+
+ number_of_te2 = length / sizeof(u32);
+
+ if (number_of_te2 != (TE2_EDGE_MAX * 2)) {
+ DSI_INFO("[%s] dsi-te2-info invalid parameters, number:%u\n",
+ panel->name, number_of_te2);
+ return -EINVAL;
+ }
+
+ rc = utils->read_u32_array(utils->data, "google,mdss-dsi-te2-info",
+ arr_32, number_of_te2);
+ if (rc) {
+ DSI_INFO("[%s] cannot read google,mdss-dsi-te2-info\n",
+ panel->name);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < TE2_EDGE_MAX; i++) {
+ panel->te2_config.te2_edge[i].rising = arr_32[i * 2];
+ panel->te2_config.te2_edge[i].falling = arr_32[i * 2 + 1];
+ }
+
+ rc = utils->read_u32(utils->data, "google,mdss-dsi-te2-lp-threshold",
+ &val);
+ if (rc) {
+ DSI_INFO("[%s] google,mdss-dsi-te2-lp-threshold unspecified\n",
+ panel->name);
+ return -EINVAL;
+ }
+
+ panel->te2_config.lp_threshold = val;
+ panel->te2_config.te2_ready = true;
+
+ return 0;
+}
+
static void dsi_panel_update_util(struct dsi_panel *panel,
struct device_node *parser_node)
{
@@ -3363,6 +3440,16 @@
utils->node = panel->panel_of_node;
}
+static void dsi_panel_hbmsv_hanghandler_work(struct work_struct *work)
+{
+ struct dsi_panel *panel =
+ container_of(work, struct dsi_panel, hanghandler_work.work);
+
+ pr_warn("hbmsv hang handler\n");
+ panel->hbm_sv_enabled = false;
+ dsi_panel_update_hbm(panel, HBM_MODE_OFF);
+}
+
struct dsi_panel *dsi_panel_get(struct device *parent,
struct device_node *of_node,
struct device_node *parser_node,
@@ -3404,6 +3491,9 @@
rc);
goto error;
}
+ rc = dsi_panel_parse_vendor_info(panel, of_node);
+ if (rc)
+ DSI_ERR("failed to parse vendor information, rc=%d\n", rc);
rc = dsi_panel_parse_panel_mode(panel);
if (rc) {
@@ -3441,7 +3531,7 @@
if (rc)
DSI_ERR("failed to parse power config, rc=%d\n", rc);
- rc = dsi_panel_parse_bl_config(panel);
+ rc = dsi_panel_bl_parse_config(parent, &panel->bl_config);
if (rc) {
DSI_ERR("failed to parse backlight config, rc=%d\n", rc);
if (rc == -EPROBE_DEFER)
@@ -3470,6 +3560,10 @@
if (rc)
DSI_DEBUG("failed to parse esd config, rc=%d\n", rc);
+ rc = dsi_panel_parse_te2_config(panel);
+ if (rc)
+ DSI_DEBUG("failed to parse te2 config, rc=%d\n", rc);
+
panel->power_mode = SDE_MODE_DPMS_OFF;
drm_panel_init(&panel->drm_panel);
panel->drm_panel.dev = &panel->mipi_device.dev;
@@ -3481,6 +3575,14 @@
mutex_init(&panel->panel_lock);
+ rc = dsi_panel_create_sn_buf(panel);
+ if (rc)
+ DSI_ERR("failed to create buffer for SN, rc=%d\n", rc);
+
+ panel->hbm_sv_enabled = true;
+ INIT_DELAYED_WORK(&panel->hanghandler_work,
+ dsi_panel_hbmsv_hanghandler_work);
+
return panel;
error:
kfree(panel);
@@ -3489,6 +3591,12 @@
void dsi_panel_put(struct dsi_panel *panel)
{
+ cancel_delayed_work_sync(&panel->hanghandler_work);
+
+ dsi_panel_release_sn_buf(panel);
+
+ dsi_panel_release_vendor_extinfo(panel);
+
drm_panel_remove(&panel->drm_panel);
/* free resources allocated for ESD check */
@@ -3497,6 +3605,426 @@
kfree(panel);
}
+/* src_len should include the null termination */
+ssize_t parse_byte_buf(u8 *out, size_t out_len, char *src, size_t src_len)
+{
+ const char *skip = "\n ";
+ size_t i = 0;
+ int rc = 0;
+ char *s;
+
+ if (unlikely(!src || !src_len || !out || !out_len))
+ return -EINVAL;
+
+ if (strnlen(src, src_len) == src_len)
+ return -EINVAL;
+
+ while (src && !rc && i < out_len) {
+ s = strsep(&src, skip);
+ if (*s != '\0') {
+ rc = kstrtou8(s, 16, out + i);
+ i++;
+ }
+ }
+
+ return rc ? : i;
+}
+
+ssize_t dsi_panel_debugfs_write_reg(struct file *file,
+ const char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ struct seq_file *seq = file->private_data;
+ struct dsi_panel *panel = seq->private;
+ char *buf;
+ char *payload;
+ size_t len, buf_len;
+ int rc = 0;
+
+ if (!panel || !panel->panel_initialized)
+ return -EPERM;
+
+ buf_len = count + 1;
+ /* calculate length for worst case (1 digit per byte + whitespace) */
+ len = buf_len / 2;
+
+ buf = kmalloc(buf_len, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ payload = kmalloc(len, GFP_KERNEL);
+ if (!payload) {
+ kfree(buf);
+ return -ENOMEM;
+ }
+
+ if (copy_from_user(buf, user_buf, count)) {
+ rc = -EFAULT;
+ goto done;
+ }
+
+ buf[count] = 0; /* terminate end of string */
+
+ rc = parse_byte_buf(payload, len, buf, buf_len);
+ if (rc <= 0) {
+ rc = -EINVAL;
+ goto done;
+ }
+ len = rc;
+
+ pr_debug("writing cmd=%x len=%zu\n", payload[0], len);
+
+ mutex_lock(&panel->panel_lock);
+ rc = mipi_dsi_dcs_write_buffer(&panel->mipi_device, payload, len);
+ mutex_unlock(&panel->panel_lock);
+
+done:
+ kfree(buf);
+ kfree(payload);
+
+ return rc ? : count;
+}
+
+int dsi_panel_debugfs_read_reg(struct seq_file *seq, void *data)
+{
+ struct dsi_panel *panel = seq->private;
+ char *buf;
+ ssize_t rc;
+ size_t len;
+ u8 cmd;
+
+ if (!panel || !panel->panel_initialized)
+ return -EPERM;
+
+ len = panel->debug.reg_read_len;
+ cmd = panel->debug.reg_read_cmd;
+
+ if (len == 0)
+ return -EINVAL;
+
+ buf = kmalloc(max(PAGE_SIZE, len), GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ mutex_lock(&panel->panel_lock);
+ rc = mipi_dsi_dcs_read(&panel->mipi_device, cmd, buf, len);
+ mutex_unlock(&panel->panel_lock);
+
+ if (rc > 0) {
+ int i;
+
+ pr_debug("got %zd bytes back, first: 0x%x\n", rc, buf[0]);
+ for (i = 0; i < rc; i++) {
+ if ((i % 8) > 0)
+ seq_puts(seq, " ");
+ else if (i)
+ seq_puts(seq, "\n");
+ seq_printf(seq, "%02X", buf[i]);
+ }
+ seq_puts(seq, "\n");
+ rc = 0;
+ } else if (rc == 0) {
+ pr_debug("no response back\n");
+ }
+ kfree(buf);
+
+ return rc;
+}
+
+static int dsi_panel_debugfs_open_reg(struct inode *inode, struct file *f)
+{
+ return single_open(f, dsi_panel_debugfs_read_reg, inode->i_private);
+}
+
+static const struct file_operations panel_reg_fops = {
+ .owner = THIS_MODULE,
+ .open = dsi_panel_debugfs_open_reg,
+ .write = dsi_panel_debugfs_write_reg,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+struct debugfs_cmdset_entry {
+ struct dsi_panel *panel;
+ struct dsi_panel_cmd_set *set;
+ enum dsi_cmd_set_type type;
+};
+
+struct {
+ const char *label;
+ enum dsi_cmd_set_type type;
+} cmdset_list[] = {
+ { "pre_on", DSI_CMD_SET_PRE_ON },
+ { "on", DSI_CMD_SET_ON },
+ { "post_on", DSI_CMD_SET_POST_ON },
+ { "pre_off", DSI_CMD_SET_PRE_OFF },
+ { "off", DSI_CMD_SET_OFF },
+ { "post_off", DSI_CMD_SET_POST_OFF },
+ { "panel_status", DSI_CMD_SET_PANEL_STATUS },
+ { "pps", DSI_CMD_SET_PPS },
+ { "lp1", DSI_CMD_SET_LP1 },
+ { "lp2", DSI_CMD_SET_LP2 },
+ { "no_lp", DSI_CMD_SET_NOLP },
+ { "post_nolp", DSI_CMD_SET_POST_NOLP },
+ { "switch", DSI_CMD_SET_TIMING_SWITCH },
+ { "post_switch", DSI_CMD_SET_POST_TIMING_SWITCH },
+};
+
+static inline ssize_t parse_cmdset(struct dsi_panel_cmd_set *set, char *buf,
+ size_t buf_len)
+{
+ char *tmp;
+ size_t len;
+ ssize_t rc;
+
+ /* calculate length for worst case (1 digit per byte + whitespace) */
+ len = buf_len / 2;
+ tmp = kmalloc(len, GFP_KERNEL);
+ if (!tmp)
+ return -ENOMEM;
+
+ rc = parse_byte_buf(tmp, len, buf, buf_len);
+ if (rc <= 0) {
+ rc = -EINVAL;
+ goto done;
+ }
+
+ rc = dsi_panel_parse_cmd_sets_sub(set, tmp, rc);
+done:
+ kfree(tmp);
+
+ return rc;
+}
+
+static struct dsi_panel_cmd_set *
+get_cmdset_locked(struct debugfs_cmdset_entry *entry)
+{
+ struct dsi_panel *panel = entry->panel;
+
+ if (entry->set)
+ return entry->set;
+
+ if (!panel->cur_mode || !panel->cur_mode->priv_info) {
+ pr_err("Invalid mode for panel [%s]\n", panel->name);
+ return NULL;
+ }
+
+ return &panel->cur_mode->priv_info->cmd_sets[entry->type];
+}
+
+ssize_t dsi_panel_debugfs_write_cmdset(struct file *file,
+ const char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ struct seq_file *seq = file->private_data;
+ struct debugfs_cmdset_entry *entry = seq->private;
+ struct dsi_panel *panel = entry->panel;
+ struct dsi_panel_cmd_set tmp_set;
+ struct dsi_panel_cmd_set *set;
+ char *buf = NULL;
+ size_t buf_len;
+ int rc = 0;
+
+ mutex_lock(&panel->panel_lock);
+ set = get_cmdset_locked(entry);
+ if (set == NULL) {
+ mutex_unlock(&panel->panel_lock);
+ return -EINVAL;
+ }
+
+ tmp_set = *set;
+ buf_len = count + 1;
+ buf = kmalloc(buf_len, GFP_KERNEL);
+ if (!buf) {
+ rc = -ENOMEM;
+ goto done;
+ }
+
+ if (copy_from_user(buf, user_buf, count)) {
+ rc = -EFAULT;
+ goto done;
+ }
+ buf[count] = '\0';
+
+ rc = parse_cmdset(&tmp_set, buf, buf_len);
+ if (rc)
+ goto done;
+
+ dsi_panel_destroy_cmd_packets(set);
+ dsi_panel_dealloc_cmd_packets(set);
+ *set = tmp_set;
+
+done:
+ kfree(buf);
+ mutex_unlock(&panel->panel_lock);
+
+ return rc ? : count;
+}
+
+int dsi_panel_debugfs_read_cmdset(struct seq_file *seq, void *data)
+{
+ struct debugfs_cmdset_entry *entry = seq->private;
+ struct dsi_panel *panel = entry->panel;
+ struct dsi_panel_cmd_set *set;
+ int i, j;
+
+ mutex_lock(&panel->panel_lock);
+ set = get_cmdset_locked(entry);
+ if (set == NULL) {
+ mutex_unlock(&panel->panel_lock);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < set->count; i++) {
+ struct dsi_cmd_desc *cmd = set->cmds + i;
+ const char *txbuf = cmd->msg.tx_buf;
+
+ seq_printf(seq, "%02X %02X %02X %02X %02X %02zX %02zX",
+ cmd->msg.type, cmd->last_command, cmd->msg.channel,
+ cmd->msg.flags & MIPI_DSI_MSG_REQ_ACK ? 1 : 0,
+ cmd->post_wait_ms,
+ cmd->msg.tx_len >> 8, cmd->msg.tx_len & 0xff);
+
+ for (j = 0; j < cmd->msg.tx_len; j++)
+ seq_printf(seq, " %02X", txbuf[j]);
+ seq_puts(seq, "\n");
+ }
+ mutex_unlock(&panel->panel_lock);
+
+ return 0;
+}
+
+static int dsi_panel_debugfs_open_cmdset(struct inode *inode, struct file *f)
+{
+ return single_open(f, dsi_panel_debugfs_read_cmdset, inode->i_private);
+}
+
+static const struct file_operations panel_cmdset_fops = {
+ .owner = THIS_MODULE,
+ .open = dsi_panel_debugfs_open_cmdset,
+ .write = dsi_panel_debugfs_write_cmdset,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+int dsi_panel_debugfs_create_cmdset_files(struct dentry *parent,
+ struct debugfs_cmdset_entry *entry,
+ struct dsi_panel *panel,
+ struct dsi_panel_cmd_set *set,
+ const char *label,
+ const size_t size)
+{
+ struct dentry *file;
+ const char *name;
+ int i;
+
+ for (i = 0; i < size; i++, entry++) {
+ if (set) {
+ name = label;
+ entry->set = set;
+ } else {
+ name = cmdset_list[i].label;
+ entry->type = cmdset_list[i].type;
+ }
+ entry->panel = panel;
+
+ file = debugfs_create_file(name, 0600, parent,
+ entry, &panel_cmdset_fops);
+ if (IS_ERR_OR_NULL(file)) {
+ DSI_ERR("debugfs create %s file failed\n", name);
+ return PTR_ERR(file);
+ }
+ }
+
+ return 0;
+}
+
+int dsi_panel_debugfs_create_cmdset(struct dentry *parent,
+ const char *label,
+ struct dsi_panel *panel,
+ struct dsi_panel_cmd_set *set)
+{
+ struct device *dev = panel->parent;
+ struct debugfs_cmdset_entry *entry;
+
+ entry = devm_kzalloc(dev, sizeof(*entry), GFP_KERNEL);
+ if (!entry)
+ return -ENOMEM;
+
+ return dsi_panel_debugfs_create_cmdset_files(parent, entry, panel,
+ set, label, 1);
+}
+
+static void dsi_panel_debugfs_create_cmdsets_from_list(struct dentry *parent,
+ struct dsi_panel *panel)
+{
+ struct device *dev = panel->parent;
+ struct debugfs_cmdset_entry *entry;
+ const size_t cmds_size = ARRAY_SIZE(cmdset_list);
+ struct dentry *r;
+
+ r = debugfs_create_dir("cmd_sets", parent);
+ if (IS_ERR(r)) {
+ DSI_ERR("debugfs create cmd_sets failed\n");
+ return;
+ }
+
+ entry = devm_kzalloc(dev, cmds_size * sizeof(*entry), GFP_KERNEL);
+ if (!entry)
+ goto error;
+
+ if (dsi_panel_debugfs_create_cmdset_files(r, entry, panel,
+ NULL, NULL, cmds_size))
+ goto error;
+
+ return;
+
+error:
+ debugfs_remove_recursive(r);
+}
+
+void dsi_panel_debugfs_init(struct dsi_panel *panel, struct dentry *dir)
+{
+ struct dentry *r, *file;
+ struct dsi_panel_debug *pdbg = &panel->debug;
+
+ r = debugfs_create_dir("panel_reg", dir);
+ if (IS_ERR(r))
+ return;
+
+ /* default read of 2 bytes */
+ pdbg->reg_read_len = 2;
+
+ file = debugfs_create_u8("addr", 0600, r, &pdbg->reg_read_cmd);
+ if (IS_ERR_OR_NULL(file)) {
+ DSI_ERR("debugfs create addr file failed\n");
+ goto error;
+ }
+
+ file = debugfs_create_size_t("len", 0600, r, &pdbg->reg_read_len);
+ if (IS_ERR_OR_NULL(file)) {
+ DSI_ERR("debugfs create len file failed\n");
+ goto error;
+ }
+
+ file = debugfs_create_file("payload", 0600, r, panel, &panel_reg_fops);
+ if (IS_ERR_OR_NULL(file)) {
+ DSI_ERR("debugfs create payload file failed\n");
+ goto error;
+ }
+
+ dsi_panel_debugfs_create_cmdsets_from_list(dir, panel);
+ dsi_panel_bl_debugfs_init(dir, panel);
+ dsi_panel_bl_elvss_debugfs_init(dir, panel);
+
+ return;
+
+error:
+ debugfs_remove_recursive(r);
+}
+
int dsi_panel_drv_init(struct dsi_panel *panel,
struct mipi_dsi_host *host)
{
@@ -3551,6 +4079,8 @@
goto error_gpio_release;
}
+ rc = dsi_panel_switch_init(panel);
+
goto exit;
error_gpio_release:
@@ -3573,6 +4103,8 @@
return -EINVAL;
}
+ dsi_panel_switch_destroy(panel);
+
mutex_lock(&panel->panel_lock);
rc = dsi_panel_bl_unregister(panel);
@@ -3722,12 +4254,15 @@
if (!mode->priv_info)
return;
+ dsi_panel_switch_put_mode(mode);
+
for (i = 0; i < DSI_CMD_SET_MAX; i++) {
dsi_panel_destroy_cmd_packets(&mode->priv_info->cmd_sets[i]);
dsi_panel_dealloc_cmd_packets(&mode->priv_info->cmd_sets[i]);
}
-
+ kfree(mode->priv_info->phy_timing_val);
kfree(mode->priv_info);
+ mode->priv_info = NULL;
}
void dsi_panel_calc_dsi_transfer_time(struct dsi_host_common_cfg *config,
@@ -3776,15 +4311,14 @@
timing->min_dsi_clk_hz = min_bitclk_hz;
- if (timing->clk_rate_hz) {
+ if (mode->priv_info->mdp_transfer_time_us) {
+ timing->dsi_transfer_time_us =
+ mode->priv_info->mdp_transfer_time_us;
+ } else if (timing->clk_rate_hz) {
/* adjust the transfer time proportionately for bit clk*/
dsi_transfer_time_us = frame_time_us * min_bitclk_hz;
do_div(dsi_transfer_time_us, timing->clk_rate_hz);
timing->dsi_transfer_time_us = dsi_transfer_time_us;
-
- } else if (mode->priv_info->mdp_transfer_time_us) {
- timing->dsi_transfer_time_us =
- mode->priv_info->mdp_transfer_time_us;
} else {
min_threshold_us = mult_frac(frame_time_us,
@@ -4003,6 +4537,13 @@
mutex_lock(&panel->panel_lock);
+ rc = dsi_pwr_enable_regulator(&panel->power_info, true);
+ if (rc) {
+ DSI_ERR("[%s] failed to enable vregs, rc=%d\n",
+ panel->name, rc);
+ goto error;
+ }
+
/* If LP11_INIT is set, panel will be powered up during prepare() */
if (panel->lp11_init)
goto error;
@@ -4035,12 +4576,17 @@
set = &priv_info->cmd_sets[DSI_CMD_SET_PPS];
- dsi_dsc_create_pps_buf_cmd(&priv_info->dsc, panel->dsc_pps_cmd, 0);
- rc = dsi_panel_create_cmd_packets(panel->dsc_pps_cmd,
- DSI_CMD_PPS_SIZE, 1, set->cmds);
- if (rc) {
- DSI_ERR("failed to create cmd packets, rc=%d\n", rc);
- goto error;
+ if (!priv_info->pps_created) {
+ dsi_dsc_create_pps_buf_cmd(&priv_info->dsc, panel->dsc_pps_cmd, 0);
+ rc = dsi_panel_create_cmd_packets(panel->dsc_pps_cmd,
+ DSI_CMD_PPS_SIZE, 1, set->cmds);
+ if (rc) {
+ DSI_ERR("failed to create cmd packets, rc=%d\n", rc);
+ goto error;
+ }
+ else {
+ priv_info->pps_created = true;
+ }
}
rc = dsi_panel_tx_cmd_set(panel, DSI_CMD_SET_PPS);
@@ -4049,7 +4595,6 @@
panel->name, rc);
}
- dsi_panel_destroy_cmd_packets(set);
error:
mutex_unlock(&panel->panel_lock);
return rc;
@@ -4064,27 +4609,35 @@
return -EINVAL;
}
+ if (panel->funcs && panel->funcs->pre_lp1)
+ panel->funcs->pre_lp1(panel);
+
+ dsi_backlight_early_dpms(&panel->bl_config, SDE_MODE_DPMS_LP1);
+
mutex_lock(&panel->panel_lock);
if (!panel->panel_initialized)
goto exit;
- /*
- * Consider LP1->LP2->LP1.
- * If the panel is already in LP mode, do not need to
- * set the regulator.
- * IBB and AB power mode would be set at the same time
- * in PMIC driver, so we only call ibb setting that is enough.
- */
- if (dsi_panel_is_type_oled(panel) &&
- panel->power_mode != SDE_MODE_DPMS_LP2)
- dsi_pwr_panel_regulator_mode_set(&panel->power_info,
- "ibb", REGULATOR_MODE_IDLE);
+ rc = dsi_panel_update_hbm_locked(panel, HBM_MODE_OFF);
+ if (rc) {
+ DSI_ERR("[%s] couldn't disable HBM mode for LP1 transition\n",
+ panel->name);
+ mutex_unlock(&panel->panel_lock);
+ return rc;
+ }
+ dsi_backlight_hbm_dimming_stop(&panel->bl_config);
+
rc = dsi_panel_tx_cmd_set(panel, DSI_CMD_SET_LP1);
if (rc)
DSI_ERR("[%s] failed to send DSI_CMD_SET_LP1 cmd, rc=%d\n",
panel->name, rc);
exit:
mutex_unlock(&panel->panel_lock);
+
+ if (!rc)
+ rc = dsi_backlight_late_dpms(&panel->bl_config,
+ SDE_MODE_DPMS_LP1);
+
return rc;
}
@@ -4097,16 +4650,32 @@
return -EINVAL;
}
+ dsi_backlight_early_dpms(&panel->bl_config, SDE_MODE_DPMS_LP2);
+
mutex_lock(&panel->panel_lock);
if (!panel->panel_initialized)
goto exit;
+ rc = dsi_panel_update_hbm_locked(panel, HBM_MODE_OFF);
+ if (rc) {
+ DSI_ERR("[%s] couldn't disable HBM mode for LP2 transition\n",
+ panel->name);
+ mutex_unlock(&panel->panel_lock);
+ return rc;
+ }
+ dsi_backlight_hbm_dimming_stop(&panel->bl_config);
+
rc = dsi_panel_tx_cmd_set(panel, DSI_CMD_SET_LP2);
if (rc)
DSI_ERR("[%s] failed to send DSI_CMD_SET_LP2 cmd, rc=%d\n",
panel->name, rc);
exit:
mutex_unlock(&panel->panel_lock);
+
+ if (!rc)
+ rc = dsi_backlight_late_dpms(&panel->bl_config,
+ SDE_MODE_DPMS_LP2);
+
return rc;
}
@@ -4119,27 +4688,137 @@
return -EINVAL;
}
+ dsi_backlight_early_dpms(&panel->bl_config, SDE_MODE_DPMS_ON);
+
mutex_lock(&panel->panel_lock);
if (!panel->panel_initialized)
goto exit;
- /*
- * Consider about LP1->LP2->NOLP.
- */
- if (dsi_panel_is_type_oled(panel) &&
- (panel->power_mode == SDE_MODE_DPMS_LP1 ||
- panel->power_mode == SDE_MODE_DPMS_LP2))
- dsi_pwr_panel_regulator_mode_set(&panel->power_info,
- "ibb", REGULATOR_MODE_NORMAL);
- rc = dsi_panel_tx_cmd_set(panel, DSI_CMD_SET_NOLP);
- if (rc)
- DSI_ERR("[%s] failed to send DSI_CMD_SET_NOLP cmd, rc=%d\n",
- panel->name, rc);
+ if (panel->funcs && panel->funcs->send_nolp)
+ rc = panel->funcs->send_nolp(panel);
+ else
+ rc = -EOPNOTSUPP;
+
+ if (rc == -EOPNOTSUPP) {
+ rc = dsi_panel_tx_cmd_set(panel, DSI_CMD_SET_NOLP);
+ if (rc)
+ DSI_ERR("[%s] failed to send DSI_CMD_SET_NOLP cmd, rc=%d\n",
+ panel->name, rc);
+ }
exit:
mutex_unlock(&panel->panel_lock);
+
+ if (!rc)
+ rc = dsi_backlight_late_dpms(&panel->bl_config,
+ SDE_MODE_DPMS_ON);
return rc;
}
+static int dsi_panel_update_hbm_locked(struct dsi_panel *panel,
+ enum hbm_mode_type hbm_mode)
+{
+ struct dsi_backlight_config *bl = &panel->bl_config;
+ struct hbm_data *hbm = bl->hbm;
+
+ if (!hbm)
+ return 0;
+
+ if (hbm_mode < 0 || hbm_mode >= HBM_MODE_MAX)
+ return -EINVAL;
+ if (hbm_mode == panel->hbm_mode)
+ return 0;
+
+ if (hbm_mode == HBM_MODE_SV && !panel->hbm_sv_enabled) {
+ pr_warn("hbmsv is disabled\n");
+ return -EINVAL;
+ }
+
+ if (dsi_backlight_get_dpms(bl) != SDE_MODE_DPMS_ON) {
+ DSI_ERR("[%s] Backlight in incompatible state, HBM changes not allowed\n",
+ panel->name);
+ return -EINVAL;
+ }
+
+ if (hbm_mode == HBM_MODE_SV)
+ mod_delayed_work(system_wq, &panel->hanghandler_work,
+ msecs_to_jiffies(HBM_SV_MAX_MS));
+ else
+ cancel_delayed_work(&panel->hanghandler_work);
+
+ panel->hbm_pending_irc_on =
+ (panel->hbm_mode == HBM_MODE_SV && hbm_mode == HBM_MODE_OFF);
+
+ hbm->cur_range = HBM_RANGE_MAX;
+
+ if (hbm_mode == HBM_MODE_SV) {
+ int rc = dsi_panel_bl_update_irc(bl, false);
+
+ if (rc != 0 && rc != -EOPNOTSUPP)
+ pr_err("[%s] failed to disable IRC, rc=%d\n",
+ panel->name, rc);
+ } else if (hbm_mode == HBM_MODE_ON && panel->hbm_mode == HBM_MODE_SV) {
+ int rc = dsi_panel_bl_update_irc(bl, true);
+
+ if (rc != 0 && rc != -EOPNOTSUPP)
+ pr_err("[%s] failed to enable IRC, rc=%d\n",
+ panel->name, rc);
+ }
+
+ panel->hbm_mode = hbm_mode;
+
+ /* When HBM exit is requested, send HBM exit commands
+ * immediately to avoid conflict with subsequent backlight ops.
+ */
+ if (hbm_mode == HBM_MODE_OFF) {
+ int rc = 0;
+
+ dsi_backlight_hbm_dimming_start(bl,
+ hbm->exit_num_dimming_frames,
+ &hbm->exit_dimming_stop_cmd);
+ rc = dsi_panel_switch_update_hbm(panel);
+ if (rc == -EOPNOTSUPP)
+ rc = dsi_panel_cmd_set_transfer(panel, &hbm->exit_cmd);
+ if (rc)
+ pr_err("[%s] failed to send HBM exit cmd, rc=%d\n",
+ panel->name, rc);
+ }
+
+ if (bl->bl_device)
+ sysfs_notify(&bl->bl_device->dev.kobj, NULL,
+ "state");
+
+ return 0;
+}
+
+int dsi_panel_update_hbm(struct dsi_panel *panel, enum hbm_mode_type hbm_mode)
+{
+ int rc = 0;
+
+ if (!panel)
+ return -EINVAL;
+
+ if (!panel->bl_config.hbm)
+ return 0;
+
+ mutex_lock(&panel->panel_lock);
+ rc = dsi_panel_update_hbm_locked(panel, hbm_mode);
+ mutex_unlock(&panel->panel_lock);
+ if (rc)
+ return rc;
+
+ return backlight_update_status(panel->bl_config.bl_device);
+}
+
+enum hbm_mode_type dsi_panel_get_hbm(struct dsi_panel *panel)
+{
+ if (!panel) {
+ DSI_ERR("invalid params\n");
+ return false;
+ }
+
+ return panel->hbm_mode;
+}
+
int dsi_panel_prepare(struct dsi_panel *panel)
{
int rc = 0;
@@ -4149,8 +4828,13 @@
return -EINVAL;
}
+ dsi_backlight_early_dpms(&panel->bl_config, SDE_MODE_DPMS_ON);
+
mutex_lock(&panel->panel_lock);
+ if (panel->init_delay_us)
+ usleep_range(panel->init_delay_us, panel->init_delay_us);
+
if (panel->lp11_init) {
rc = dsi_panel_power_on(panel);
if (rc) {
@@ -4169,6 +4853,11 @@
error:
mutex_unlock(&panel->panel_lock);
+
+ if (!rc)
+ rc = dsi_backlight_late_dpms(&panel->bl_config,
+ SDE_MODE_DPMS_OFF);
+
return rc;
}
@@ -4422,6 +5111,11 @@
return -EINVAL;
}
+ if (panel->funcs && panel->funcs->mode_switch) {
+ rc = panel->funcs->mode_switch(panel);
+ return rc;
+ }
+
mutex_lock(&panel->panel_lock);
rc = dsi_panel_tx_cmd_set(panel, DSI_CMD_SET_TIMING_SWITCH);
@@ -4465,12 +5159,20 @@
mutex_lock(&panel->panel_lock);
rc = dsi_panel_tx_cmd_set(panel, DSI_CMD_SET_ON);
- if (rc)
+ if (rc) {
DSI_ERR("[%s] failed to send DSI_CMD_SET_ON cmds, rc=%d\n",
panel->name, rc);
- else
+ } else {
panel->panel_initialized = true;
+ panel->power_mode = SDE_MODE_DPMS_ON;
+ }
+
mutex_unlock(&panel->panel_lock);
+
+ if (!rc)
+ rc = dsi_backlight_late_dpms(&panel->bl_config,
+ SDE_MODE_DPMS_ON);
+
return rc;
}
@@ -4483,6 +5185,9 @@
return -EINVAL;
}
+ if (panel->funcs && panel->funcs->post_enable)
+ return panel->funcs->post_enable(panel);
+
mutex_lock(&panel->panel_lock);
rc = dsi_panel_tx_cmd_set(panel, DSI_CMD_SET_POST_ON);
@@ -4505,6 +5210,8 @@
return -EINVAL;
}
+ dsi_backlight_early_dpms(&panel->bl_config, SDE_MODE_DPMS_OFF);
+
mutex_lock(&panel->panel_lock);
rc = dsi_panel_tx_cmd_set(panel, DSI_CMD_SET_PRE_OFF);
@@ -4516,6 +5223,10 @@
error:
mutex_unlock(&panel->panel_lock);
+
+ if (!rc && panel->funcs && panel->funcs->pre_disable)
+ rc = panel->funcs->pre_disable(panel);
+
return rc;
}
@@ -4530,17 +5241,14 @@
mutex_lock(&panel->panel_lock);
+ rc = dsi_panel_update_hbm_locked(panel, HBM_MODE_OFF);
+ if (rc)
+ DSI_WARN("[%s] couldn't disable HBM mode to unprepare display\n",
+ panel->name);
+ dsi_backlight_hbm_dimming_stop(&panel->bl_config);
+
/* Avoid sending panel off commands when ESD recovery is underway */
if (!atomic_read(&panel->esd_recovery_pending)) {
- /*
- * Need to set IBB/AB regulator mode to STANDBY,
- * if panel is going off from AOD mode.
- */
- if (dsi_panel_is_type_oled(panel) &&
- (panel->power_mode == SDE_MODE_DPMS_LP1 ||
- panel->power_mode == SDE_MODE_DPMS_LP2))
- dsi_pwr_panel_regulator_mode_set(&panel->power_info,
- "ibb", REGULATOR_MODE_STANDBY);
rc = dsi_panel_tx_cmd_set(panel, DSI_CMD_SET_OFF);
if (rc) {
/*
@@ -4558,6 +5266,11 @@
panel->power_mode = SDE_MODE_DPMS_OFF;
mutex_unlock(&panel->panel_lock);
+
+ if (!rc)
+ rc = dsi_backlight_late_dpms(&panel->bl_config,
+ SDE_MODE_DPMS_OFF);
+
return rc;
}
@@ -4581,6 +5294,10 @@
error:
mutex_unlock(&panel->panel_lock);
+
+ if (panel->bl_config.bl_update == BL_UPDATE_DELAY_UNTIL_FIRST_FRAME)
+ panel->bl_config.allow_bl_update = false;
+
return rc;
}
@@ -4605,3 +5322,25 @@
mutex_unlock(&panel->panel_lock);
return rc;
}
+
+int dsi_panel_idle(struct dsi_panel *panel)
+{
+ if (unlikely(!panel))
+ return -EINVAL;
+
+ if (panel->funcs && panel->funcs->idle)
+ return panel->funcs->idle(panel);
+
+ return 0;
+}
+
+int dsi_panel_wakeup(struct dsi_panel *panel)
+{
+ if (unlikely(!panel))
+ return -EINVAL;
+
+ if (panel->funcs && panel->funcs->wakeup)
+ return panel->funcs->wakeup(panel);
+
+ return 0;
+}
diff --git a/msm/dsi/dsi_panel.h b/msm/dsi/dsi_panel.h
index ee30cd5..3160287 100644
--- a/msm/dsi/dsi_panel.h
+++ b/msm/dsi/dsi_panel.h
@@ -25,9 +25,15 @@
#define MAX_BL_SCALE_LEVEL 1024
#define MAX_SV_BL_SCALE_LEVEL 65535
#define DSI_CMD_PPS_SIZE 135
+#define BL_RANGE_MAX 10
#define DSI_MODE_MAX 32
+#define HBM_RANGE_MAX 4
+#define DYNAMIC_ELVSS_RANGE_MAX 10
+#define BL_STATE_STANDBY BL_CORE_FBBLANK
+#define BL_STATE_LP BL_CORE_LP1
+#define BL_STATE_LP2 BL_CORE_LP2
/*
* Defining custom dsi msg flag,
* continued from drm_mipi_dsi.h
@@ -75,6 +81,13 @@
DSI_DISPLAY_PANEL_TYPE_MAX,
};
+enum hbm_mode_type {
+ HBM_MODE_OFF = 0,
+ HBM_MODE_ON,
+ HBM_MODE_SV,
+ HBM_MODE_MAX,
+};
+
struct dsi_dfps_capabilities {
enum dsi_dfps_type type;
u32 min_refresh_rate;
@@ -111,6 +124,106 @@
enum dsi_panel_rotation rotation;
};
+struct hbm_range {
+ /* Userspace brightness range (inclusive) for this HBM range */
+ u32 user_bri_start;
+ u32 user_bri_end;
+
+ /* Panel brightness range (inclusive) for this HBM range */
+ u32 panel_bri_start;
+ u32 panel_bri_end;
+
+ /* Command to be sent to the panel when entering this HBM range */
+ struct dsi_panel_cmd_set entry_cmd;
+ /*
+ * Command to be sent to the panel to stop brightness dimming while
+ * in this HBM range.
+ */
+ struct dsi_panel_cmd_set dimming_stop_cmd;
+ /* Number of frames dimming will take. */
+ u32 num_dimming_frames;
+};
+
+enum elvss_mode {
+ ELVSS_MODE_INIT = 0,
+ ELVSS_MODE_ENABLE,
+ ELVSS_MODE_DISABLE
+};
+
+enum ctrl_elvss {
+ ELVSS_PRE_UPDATE = 0,
+ ELVSS_POST_UPDATE
+};
+
+struct elvss_range {
+ /* Use brightness threshold to update the command */
+ u32 brightness_threshold;
+
+ /* Command to be sent to the panel to adjust the ELVSS power */
+ struct dsi_panel_cmd_set elvss_cmd;
+};
+
+struct dynamic_elvss_data {
+ /* Record the current elvss range */
+ u32 cur_elvss_range;
+ /* Number of elvss ranges */
+ u32 num_ranges;
+ /* Different status*/
+ enum elvss_mode cur_mode;
+
+ /* Command to disable dynamic elvss */
+ struct dsi_panel_cmd_set disable_dynamic_elvss_cmd;
+
+ /* Store the elvss data */
+ struct elvss_range nodes[DYNAMIC_ELVSS_RANGE_MAX];
+
+ /* Enable/Disable dynamic elvss */
+ bool enable_dynamic_elvss;
+};
+
+struct hbm_data {
+ /* IRC register address */
+ u8 irc_addr;
+ u8 *irc_data;
+ u32 irc_bit_offset;
+
+ /* Command to be sent to the panel to irc unlock */
+ struct dsi_panel_cmd_set irc_unlock_cmd;
+ /* Command to be sent to the panel to irc lock */
+ struct dsi_panel_cmd_set irc_lock_cmd;
+
+ /* Command to be sent to the panel when exiting HBM */
+ struct dsi_panel_cmd_set exit_cmd;
+ /* Command to be sent to the panel to stop brightness dimming */
+ struct dsi_panel_cmd_set exit_dimming_stop_cmd;
+ /* Number of frames dimming will take */
+ u32 exit_num_dimming_frames;
+
+ struct hbm_range ranges[HBM_RANGE_MAX];
+ u32 num_ranges;
+ u32 cur_range;
+
+ /* Brightness dimming currently active */
+ bool dimming_active;
+ /* Total number of frames brightness dimming takes */
+ u32 dimming_frames_total;
+ /* Number of frames remaining until brightness settles */
+ u32 dimming_frames_left;
+ /* DSI command to send once brightness dimming settles */
+ struct dsi_panel_cmd_set *dimming_stop_cmd;
+
+ /* Work queue used to count frames during dimming */
+ struct workqueue_struct *dimming_workq;
+ struct work_struct dimming_work;
+ struct dsi_panel *panel;
+};
+
+struct bl_notifier_data {
+ u32 ranges[BL_RANGE_MAX];
+ u32 num_ranges;
+ u32 cur_range;
+};
+
struct dsi_backlight_config {
enum dsi_backlight_type type;
enum bl_update_flag bl_update;
@@ -118,21 +231,49 @@
u32 bl_min_level;
u32 bl_max_level;
u32 brightness_max_level;
- u32 bl_level;
u32 bl_scale;
u32 bl_scale_sv;
- bool bl_inverted_dbv;
- u32 bl_dcs_subtype;
+ u32 bl_actual;
+ u16 *lut;
+ bool bl_update_pending;
+ bool allow_bl_update;
+ bool dimming_mode;
+ u32 high_byte_offset;
+ unsigned int last_state;
+ struct mutex state_lock;
+
+ struct bl_notifier_data *bl_notifier;
+ struct dynamic_elvss_data *elvss;
+ struct hbm_data *hbm;
int en_gpio;
- /* PWM params */
- struct pwm_device *pwm_bl;
- bool pwm_enabled;
- u32 pwm_period_usecs;
+ struct backlight_device *bl_device;
+ struct regulator *lab_vreg;
- /* WLED params */
- struct led_trigger *wled;
- struct backlight_device *raw_bd;
+ void *priv;
+
+ /**
+ * update_bl - function used to update backlight
+ * @bl_cfg - ptr to backlight config struct
+ * @bl_lvl - backlight level set
+ *
+ * return: non-zero on success otherwise errno
+ */
+ int (*update_bl)(struct dsi_backlight_config *bl_cfg, u32 bl_lvl);
+
+ /**
+ * unregister - unregisters and frees any backlight data
+ * @bl_cfg - ptr to backlight config struct
+ */
+ void (*unregister)(struct dsi_backlight_config *bl_cfg);
+
+ /**
+ * debugfs_init - debugfs initialization for DSI backlight
+ * @parent - dentry to create
+ * @bl_cfg - ptr to backlight config struct
+ */
+ void (*debugfs_init)(struct dentry *parent,
+ struct dsi_backlight_config *bl_cfg);
};
struct dsi_reset_seq {
@@ -150,6 +291,11 @@
u32 mode_sel_state;
};
+struct dsi_panel_debug {
+ u8 reg_read_cmd;
+ size_t reg_read_len;
+};
+
enum esd_check_status_mode {
ESD_MODE_REG_READ,
ESD_MODE_SW_BTA,
@@ -172,6 +318,44 @@
u32 groups;
};
+struct dsi_panel_sn_location {
+ u32 start_byte;
+ u32 sn_length;
+ u8 addr;
+};
+
+struct dsi_panel_vendor_info {
+ struct dsi_panel_sn_location location;
+ bool is_sn;
+ u8 *sn;
+ const char *name;
+ u8 extinfo_loc_length;
+ u32 *extinfo_loc;
+ u8 extinfo_length;
+ u8 extinfo_read;
+ u8 *extinfo;
+};
+
+struct dsi_panel_te2_edge {
+ u16 rising;
+ u16 falling;
+};
+
+enum dsi_panel_te2_type {
+ TE2_EDGE_90HZ,
+ TE2_EDGE_60HZ,
+ TE2_EDGE_LP_HIGH,
+ TE2_EDGE_LP_LOW,
+ TE2_EDGE_MAX
+};
+
+struct dsi_panel_te2_config {
+ struct dsi_panel_te2_edge te2_edge[TE2_EDGE_MAX];
+ enum dsi_panel_te2_type current_type;
+ bool te2_ready;
+ u32 lp_threshold;
+};
+
struct dsi_panel {
const char *name;
const char *type;
@@ -200,12 +384,16 @@
struct dsi_regulator_info power_info;
struct dsi_backlight_config bl_config;
struct dsi_panel_reset_config reset_config;
+ struct dsi_panel_te2_config te2_config;
struct dsi_pinctrl_info pinctrl;
struct drm_panel_hdr_properties hdr_props;
struct drm_panel_esd_config esd_config;
+ struct dsi_panel_debug debug;
struct dsi_parser_utils utils;
+ struct dsi_panel_vendor_info vendor_info;
+ u32 init_delay_us;
bool lp11_init;
bool ulps_feature_enabled;
bool ulps_suspend_enabled;
@@ -225,6 +413,49 @@
int panel_test_gpio;
int power_mode;
enum dsi_panel_physical_type panel_type;
+
+ const struct dsi_panel_funcs *funcs;
+ void *private_data;
+
+ /* the following set of members are guarded by panel_lock */
+ enum hbm_mode_type hbm_mode;
+ bool hbm_pending_irc_on;
+ bool hbm_sv_enabled;
+ /* Work used to handle hbmsv hang */
+ struct delayed_work hanghandler_work;
+};
+
+/**
+ * struct dsi_panel_funcs - functions that handle panel switch operations
+ *
+ * @pre_disable: called before panel is about to be disabled
+ * @post_enable: called on panel post enable
+ * @mode_switch: called when a mode switch is happening
+ * @pre_kickoff: called just before frame kickoff
+ * @idle: called when updates haven't been received for a while (idle)
+ * @wakeup: called when coming out of idle state
+ * @pre_lp1: called before power mode is going to be lp1
+ * @update_te2: called when te2 configuration needs to be updated
+ *
+ * Note: none of these functions above should be called while holding panel_lock
+ *
+ * @update_hbm: for certain projects hbm/dimming configuration may need to be
+ * kept in sync depending on current mode. This function should be called with
+ * updated hbm/dimming params
+ * @send_nolp: called when sending nolp commands
+ */
+struct dsi_panel_funcs {
+ int (*pre_disable)(struct dsi_panel *);
+ int (*post_enable)(struct dsi_panel *);
+ int (*mode_switch)(struct dsi_panel *);
+ int (*pre_kickoff)(struct dsi_panel *);
+ int (*idle)(struct dsi_panel *);
+ int (*wakeup)(struct dsi_panel *);
+ int (*pre_lp1)(struct dsi_panel *);
+ int (*update_te2)(struct dsi_panel *panel);
+ int (*update_hbm)(struct dsi_panel *);
+ int (*update_irc)(struct dsi_panel *, bool);
+ int (*send_nolp)(struct dsi_panel *);
};
static inline bool dsi_panel_ulps_feature_enabled(struct dsi_panel *panel)
@@ -252,6 +483,11 @@
return (panel->panel_type == DSI_DISPLAY_PANEL_TYPE_OLED);
}
+static inline bool is_standby_mode(unsigned long state)
+{
+ return (state & BL_STATE_STANDBY) != 0;
+}
+
struct dsi_panel *dsi_panel_get(struct device *parent,
struct device_node *of_node,
struct device_node *parser_node,
@@ -266,6 +502,8 @@
int dsi_panel_drv_deinit(struct dsi_panel *panel);
+void dsi_panel_debugfs_init(struct dsi_panel *panel, struct dentry *dir);
+
int dsi_panel_get_mode_count(struct dsi_panel *panel);
void dsi_panel_put_mode(struct dsi_display_mode *mode);
@@ -309,8 +547,6 @@
int dsi_panel_post_unprepare(struct dsi_panel *panel);
-int dsi_panel_set_backlight(struct dsi_panel *panel, u32 bl_lvl);
-
int dsi_panel_update_pps(struct dsi_panel *panel);
int dsi_panel_send_qsync_on_dcs(struct dsi_panel *panel,
@@ -332,8 +568,6 @@
void dsi_dsc_pclk_param_calc(struct msm_display_dsc_info *dsc, int intf_width);
-void dsi_panel_bl_handoff(struct dsi_panel *panel);
-
struct dsi_panel *dsi_panel_ext_bridge_get(struct device *parent,
struct device_node *of_node,
int topology_override);
@@ -345,4 +579,57 @@
void dsi_panel_calc_dsi_transfer_time(struct dsi_host_common_cfg *config,
struct dsi_display_mode *mode, u32 frame_threshold_us);
+int dsi_panel_cmd_set_transfer(struct dsi_panel *panel,
+ struct dsi_panel_cmd_set *cmd);
+int dsi_panel_parse_dt_cmd_set(struct device_node *of_node,
+ const char *cmd_str,
+ const char *cmd_state_str,
+ struct dsi_panel_cmd_set *cmd);
+void dsi_panel_destroy_cmd_packets(struct dsi_panel_cmd_set *set);
+void dsi_panel_dealloc_cmd_packets(struct dsi_panel_cmd_set *set);
+
+int dsi_panel_debugfs_create_cmdset(struct dentry *parent,
+ const char *label,
+ struct dsi_panel *panel,
+ struct dsi_panel_cmd_set *set);
+
+int dsi_backlight_early_dpms(struct dsi_backlight_config *bl, int power_state);
+int dsi_backlight_late_dpms(struct dsi_backlight_config *bl, int power_state);
+
+int dsi_backlight_get_dpms(struct dsi_backlight_config *bl);
+
+int dsi_backlight_hbm_dimming_start(struct dsi_backlight_config *bl,
+ u32 num_frames, struct dsi_panel_cmd_set *stop_cmd);
+void dsi_backlight_hbm_dimming_stop(struct dsi_backlight_config *bl);
+
+int dsi_panel_bl_register(struct dsi_panel *panel);
+int dsi_panel_bl_unregister(struct dsi_panel *panel);
+int dsi_panel_bl_parse_config(struct device *parent,
+ struct dsi_backlight_config *bl);
+int dsi_panel_bl_brightness_handoff(struct dsi_panel *panel);
+void dsi_panel_bl_debugfs_init(struct dentry *parent, struct dsi_panel *panel);
+void dsi_panel_bl_elvss_debugfs_init(struct dentry *parent,
+ struct dsi_panel *panel);
+
+/* Set/get high brightness mode */
+int dsi_panel_update_hbm(struct dsi_panel *panel, enum hbm_mode_type);
+enum hbm_mode_type dsi_panel_get_hbm(struct dsi_panel *panel);
+
+int dsi_panel_bl_update_irc(struct dsi_backlight_config *bl, bool enable);
+
+int dsi_panel_switch_init(struct dsi_panel *panel);
+void dsi_panel_switch_destroy(struct dsi_panel *panel);
+void dsi_panel_switch_put_mode(struct dsi_display_mode *mode);
+int dsi_panel_switch_update_hbm(struct dsi_panel *panel);
+
+int dsi_panel_idle(struct dsi_panel *panel);
+int dsi_panel_wakeup(struct dsi_panel *panel);
+
+int dsi_panel_get_sn(struct dsi_panel *panel);
+int dsi_panel_get_vendor_extinfo(struct dsi_panel *panel);
+
+ssize_t parse_byte_buf(u8 *out, size_t out_len, char *src, size_t src_len);
+int parse_u32_buf(char *src, size_t src_len, u32 *out, size_t out_len);
+const struct dsi_display_mode *get_panel_display_mode(struct dsi_panel *panel);
+
#endif /* _DSI_PANEL_H_ */
diff --git a/msm/dsi/dsi_panel_switch.c b/msm/dsi/dsi_panel_switch.c
new file mode 100644
index 0000000..17496b1
--- /dev/null
+++ b/msm/dsi/dsi_panel_switch.c
@@ -0,0 +1,2317 @@
+/*
+ * Copyright (c) 2019, 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
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/atomic.h>
+#include <linux/completion.h>
+#include <linux/wait.h>
+#include <linux/kthread.h>
+#include <uapi/linux/sched/types.h>
+#include <video/mipi_display.h>
+
+#include "dsi_display.h"
+#include "dsi_panel.h"
+#include "sde_trace.h"
+#include "sde_connector.h"
+
+#define DSI_PANEL_GAMMA_NAME "google,dsi_s6e3hc2_gamma"
+#define DSI_PANEL_SWITCH_NAME "google,dsi_panel_switch"
+
+#define TE_TIMEOUT_MS 50
+
+#define for_each_display_mode(i, mode, panel) \
+ for (i = 0, mode = dsi_panel_to_display(panel)->modes; \
+ i < panel->num_timing_nodes; i++, mode++)
+
+
+#define DSI_WRITE_CMD_BUF(dsi, cmd) \
+ (IS_ERR_VALUE(mipi_dsi_dcs_write_buffer(dsi, cmd, ARRAY_SIZE(cmd))))
+
+#define DEFAULT_GAMMA_STR "default"
+#define CALI_GAMMA_HEADER_SIZE 3
+
+#define S6E3HC2_DEFAULT_FPS 60
+
+static const u8 unlock_cmd[] = { 0xF0, 0x5A, 0x5A };
+static const u8 lock_cmd[] = { 0xF0, 0xA5, 0xA5 };
+
+struct panel_switch_funcs {
+ struct panel_switch_data *(*create)(struct dsi_panel *panel);
+ void (*destroy)(struct panel_switch_data *pdata);
+ void (*put_mode)(struct dsi_display_mode *mode);
+ void (*perform_switch)(struct panel_switch_data *pdata,
+ const struct dsi_display_mode *mode);
+ int (*post_enable)(struct panel_switch_data *pdata);
+ int (*support_update_te2)(struct dsi_panel *panel);
+ int (*support_update_hbm)(struct dsi_panel *);
+ int (*send_nolp_cmds)(struct dsi_panel *panel);
+ ssize_t (*support_cali_gamma_store)(struct dsi_panel *panel,
+ const char *buf, size_t count);
+};
+
+struct panel_switch_data {
+ struct dsi_panel *panel;
+ struct dentry *debug_root;
+
+ struct kthread_work switch_work;
+ struct kthread_worker worker;
+ struct task_struct *thread;
+
+ const struct dsi_display_mode *display_mode;
+ const struct dsi_display_mode *idle_mode;
+ wait_queue_head_t switch_wq;
+ bool switch_pending;
+ int switch_te_listen_count;
+
+ atomic_t te_counter;
+ struct dsi_display_te_listener te_listener;
+ struct completion te_completion;
+
+ const struct panel_switch_funcs *funcs;
+};
+
+static inline
+struct dsi_display *dsi_panel_to_display(const struct dsi_panel *panel)
+{
+ return dev_get_drvdata(panel->parent);
+}
+
+static inline bool is_display_mode_same(const struct dsi_display_mode *m1,
+ const struct dsi_display_mode *m2)
+{
+ return m1 && m2 && m1->timing.refresh_rate == m2->timing.refresh_rate;
+}
+
+static inline void sde_atrace_mode_fps(const struct panel_switch_data *pdata,
+ const struct dsi_display_mode *mode)
+{
+ sde_atrace('C', pdata->thread, "FPS", mode->timing.refresh_rate);
+}
+
+ssize_t panel_dsi_write_buf(struct dsi_panel *panel,
+ const void *data, size_t len, bool send_last)
+{
+ const struct mipi_dsi_device *dsi = &panel->mipi_device;
+ const struct mipi_dsi_host_ops *ops = panel->host->ops;
+ struct mipi_dsi_msg msg = {
+ .channel = dsi->channel,
+ .tx_buf = data,
+ .tx_len = len,
+ .flags = 0,
+ };
+
+ switch (len) {
+ case 0:
+ return -EINVAL;
+
+ case 1:
+ msg.type = MIPI_DSI_DCS_SHORT_WRITE;
+ break;
+
+ case 2:
+ msg.type = MIPI_DSI_DCS_SHORT_WRITE_PARAM;
+ break;
+
+ default:
+ msg.type = MIPI_DSI_DCS_LONG_WRITE;
+ break;
+ }
+
+ if (send_last)
+ msg.flags |= MIPI_DSI_MSG_LASTCOMMAND;
+
+ return ops->transfer(panel->host, &msg);
+}
+
+static void panel_handle_te(struct dsi_display_te_listener *tl)
+{
+ struct panel_switch_data *pdata;
+
+ if (unlikely(!tl))
+ return;
+
+ pdata = container_of(tl, struct panel_switch_data, te_listener);
+
+ complete(&pdata->te_completion);
+
+ if (likely(pdata->thread)) {
+ /* 1-bit counter that shows up in panel thread timeline */
+ sde_atrace('C', pdata->thread, "TE_VSYNC",
+ atomic_inc_return(&pdata->te_counter) & 1);
+ }
+}
+
+enum s6e3hc2_wrctrld_flags {
+ S6E3HC2_WRCTRLD_DIMMING_BIT = BIT(3),
+ S6E3HC2_WRCTRLD_FRAME_RATE_BIT = BIT(4),
+ S6E3HC2_WRCTRLD_BCTRL_BIT = BIT(5),
+ S6E3HC2_WRCTRLD_HBM_BIT = BIT(7) | BIT(6),
+};
+
+struct s6e3hc2_wrctrl_data {
+ bool hbm_enable;
+ bool dimming_active;
+ u32 refresh_rate;
+};
+
+static int s6e3hc2_write_ctrld_reg(struct dsi_panel *panel,
+ const struct s6e3hc2_wrctrl_data *data,
+ bool send_last)
+{
+ u8 wrctrl_reg = S6E3HC2_WRCTRLD_BCTRL_BIT;
+ u8 payload[2] = { MIPI_DCS_WRITE_CONTROL_DISPLAY, 0 };
+
+ if (data->hbm_enable)
+ wrctrl_reg |= S6E3HC2_WRCTRLD_HBM_BIT;
+
+ if (data->dimming_active)
+ wrctrl_reg |= S6E3HC2_WRCTRLD_DIMMING_BIT;
+
+ if (data->refresh_rate == 90)
+ wrctrl_reg |= S6E3HC2_WRCTRLD_FRAME_RATE_BIT;
+
+ pr_debug("hbm_enable: %d dimming_active: %d refresh_rate: %d hz\n",
+ data->hbm_enable, data->dimming_active, data->refresh_rate);
+
+ payload[1] = wrctrl_reg;
+
+ return panel_dsi_write_buf(panel, &payload, sizeof(payload), send_last);
+}
+
+static int s6e3hc2_switch_mode_update(struct dsi_panel *panel,
+ const struct dsi_display_mode *mode,
+ bool send_last)
+{
+ const struct hbm_data *hbm = panel->bl_config.hbm;
+ struct s6e3hc2_wrctrl_data data = {0};
+
+ if (unlikely(!mode || !hbm))
+ return -EINVAL;
+
+ /* display is expected not to operate in HBM mode for the first bl range
+ * (cur_range = 0) when panel->hbm_mode is not off
+ */
+ data.hbm_enable = (panel->hbm_mode != HBM_MODE_OFF) &&
+ hbm->cur_range != 0;
+ data.dimming_active = panel->bl_config.dimming_mode ||
+ panel->bl_config.hbm->dimming_active;
+ data.refresh_rate = mode->timing.refresh_rate;
+
+ return s6e3hc2_write_ctrld_reg(panel, &data, send_last);
+}
+
+static void panel_switch_cmd_set_transfer(struct panel_switch_data *pdata,
+ const struct dsi_display_mode *mode)
+{
+ struct dsi_panel *panel = pdata->panel;
+ struct dsi_panel_cmd_set *cmd;
+ int rc;
+
+ cmd = &mode->priv_info->cmd_sets[DSI_CMD_SET_TIMING_SWITCH];
+
+ rc = dsi_panel_cmd_set_transfer(panel, cmd);
+ if (rc)
+ pr_warn("failed to send TIMING switch cmd, rc=%d\n", rc);
+}
+
+static void panel_switch_to_mode(struct panel_switch_data *pdata,
+ const struct dsi_display_mode *mode)
+{
+ struct dsi_panel *panel = pdata->panel;
+
+ SDE_ATRACE_BEGIN(__func__);
+ if (pdata->funcs && pdata->funcs->perform_switch)
+ pdata->funcs->perform_switch(pdata, mode);
+
+ if (pdata->switch_pending) {
+ pdata->switch_pending = false;
+ wake_up_all(&pdata->switch_wq);
+ }
+
+ if (panel->bl_config.bl_device)
+ sysfs_notify(&panel->bl_config.bl_device->dev.kobj, NULL,
+ "state");
+
+ SDE_ATRACE_END(__func__);
+}
+
+static void panel_switch_worker(struct kthread_work *work)
+{
+ struct panel_switch_data *pdata;
+ struct dsi_panel *panel;
+ struct dsi_display *display;
+ const struct dsi_display_mode *mode;
+ const unsigned long timeout = msecs_to_jiffies(TE_TIMEOUT_MS);
+ int rc;
+ u32 te_listen_cnt;
+
+ if (unlikely(!work))
+ return;
+
+ pdata = container_of(work, struct panel_switch_data, switch_work);
+ panel = pdata->panel;
+ if (unlikely(!panel))
+ return;
+
+ display = dsi_panel_to_display(panel);
+ if (unlikely(!display))
+ return;
+
+ mutex_lock(&panel->panel_lock);
+ mode = pdata->display_mode;
+ if (unlikely(!mode)) {
+ mutex_unlock(&panel->panel_lock);
+ return;
+ }
+
+ SDE_ATRACE_BEGIN(__func__);
+
+ pr_debug("switching mode to %dhz\n", mode->timing.refresh_rate);
+
+ te_listen_cnt = pdata->switch_te_listen_count;
+ if (te_listen_cnt) {
+ reinit_completion(&pdata->te_completion);
+ dsi_display_add_te_listener(display, &pdata->te_listener);
+ }
+
+ /* switch is shadowed by vsync so this can be done ahead of TE */
+ panel_switch_to_mode(pdata, mode);
+ mutex_unlock(&panel->panel_lock);
+
+ if (te_listen_cnt) {
+ rc = wait_for_completion_timeout(&pdata->te_completion,
+ timeout);
+ if (!rc)
+ pr_warn("Timed out waiting for TE while switching!\n");
+ else
+ pr_debug("TE received after %dus\n",
+ jiffies_to_usecs(timeout - rc));
+ }
+
+ sde_atrace_mode_fps(pdata, mode);
+ SDE_ATRACE_END(__func__);
+
+ /*
+ * this is meant only for debugging purposes, keep TE enabled for a few
+ * extra frames to see how they align after switch
+ */
+ if (te_listen_cnt) {
+ te_listen_cnt--;
+ pr_debug("waiting for %d extra te\n", te_listen_cnt);
+ while (rc && te_listen_cnt) {
+ rc = wait_for_completion_timeout(&pdata->te_completion,
+ timeout);
+ te_listen_cnt--;
+ }
+ dsi_display_remove_te_listener(display, &pdata->te_listener);
+ }
+}
+
+static bool dsi_mode_matches_cmdline(const struct dsi_display_mode *dm,
+ const struct drm_cmdline_mode *cm)
+{
+
+ if (!cm->refresh_specified && !cm->specified)
+ return false;
+ if (cm->refresh_specified && cm->refresh != dm->timing.refresh_rate)
+ return false;
+ if (cm->specified && (cm->xres != dm->timing.h_active ||
+ cm->yres != dm->timing.v_active))
+ return false;
+ return true;
+}
+
+static const struct dsi_display_mode *
+display_mode_from_cmdline(const struct dsi_panel *panel,
+ const char *modestr)
+{
+ const struct dsi_display *display;
+ const struct dsi_display_mode *mode;
+ struct drm_cmdline_mode cm = {0};
+ int i;
+
+ display = dsi_panel_to_display(panel);
+ if (!display)
+ return ERR_PTR(-ENODEV);
+
+ if (!drm_mode_parse_command_line_for_connector(modestr,
+ display->drm_conn,
+ &cm))
+ return ERR_PTR(-EINVAL);
+
+ for_each_display_mode(i, mode, panel) {
+ if (dsi_mode_matches_cmdline(mode, &cm))
+ return mode;
+ }
+
+ return NULL;
+}
+
+static const struct dsi_display_mode *
+display_mode_from_user(const struct dsi_panel *panel,
+ const char __user *user_buf, size_t user_len)
+{
+ char modestr[40];
+ size_t len = min(user_len, sizeof(modestr) - 1);
+ int rc;
+
+ rc = copy_from_user(modestr, user_buf, len);
+ if (rc)
+ return ERR_PTR(-EFAULT);
+
+ modestr[len] = '\0';
+
+ return display_mode_from_cmdline(panel, strim(modestr));
+}
+
+static void panel_queue_switch(struct panel_switch_data *pdata,
+ const struct dsi_display_mode *new_mode)
+{
+ if (unlikely(!pdata || !pdata->panel || !new_mode))
+ return;
+
+ kthread_flush_work(&pdata->switch_work);
+
+ mutex_lock(&pdata->panel->panel_lock);
+ pdata->display_mode = new_mode;
+ pdata->switch_pending = true;
+ mutex_unlock(&pdata->panel->panel_lock);
+
+ kthread_queue_work(&pdata->worker, &pdata->switch_work);
+}
+
+static int panel_switch(struct dsi_panel *panel)
+{
+ struct panel_switch_data *pdata = panel->private_data;
+
+ if (!panel->cur_mode)
+ return -EINVAL;
+
+ SDE_ATRACE_BEGIN(__func__);
+ panel_queue_switch(pdata, panel->cur_mode);
+ SDE_ATRACE_END(__func__);
+
+ return 0;
+}
+
+static int panel_pre_kickoff(struct dsi_panel *panel)
+{
+ struct panel_switch_data *pdata = panel->private_data;
+ const unsigned long timeout = msecs_to_jiffies(TE_TIMEOUT_MS);
+
+ if (!wait_event_timeout(pdata->switch_wq,
+ !pdata->switch_pending, timeout))
+ pr_warn("Timed out waiting for panel switch\n");
+
+ return 0;
+}
+
+static int panel_flush_switch_queue(struct dsi_panel *panel)
+{
+ struct panel_switch_data *pdata = panel->private_data;
+
+ kthread_flush_worker(&pdata->worker);
+
+ return 0;
+}
+
+static int panel_post_enable(struct dsi_panel *panel)
+{
+ struct panel_switch_data *pdata = panel->private_data;
+ int rc = 0;
+
+ if (!pdata)
+ return -EINVAL;
+
+ if (pdata->funcs && pdata->funcs->post_enable)
+ rc = pdata->funcs->post_enable(pdata);
+
+ return rc;
+}
+
+const struct dsi_display_mode *get_panel_display_mode(struct dsi_panel *panel)
+{
+ struct panel_switch_data *pdata = panel->private_data;
+
+ return !pdata ? panel->cur_mode : pdata->display_mode;
+}
+
+static int panel_idle(struct dsi_panel *panel)
+{
+ struct panel_switch_data *pdata = panel->private_data;
+ const struct dsi_display_mode *idle_mode;
+
+ if (unlikely(!pdata))
+ return -EINVAL;
+
+ kthread_flush_work(&pdata->switch_work);
+
+ mutex_lock(&panel->panel_lock);
+ idle_mode = pdata->idle_mode;
+ if (idle_mode && !is_display_mode_same(idle_mode, panel->cur_mode)) {
+ /*
+ * clocks are going to be turned off right after this call, so
+ * switch needs to happen synchronously
+ */
+ pdata->display_mode = idle_mode;
+ panel_switch_to_mode(pdata, idle_mode);
+
+ sde_atrace_mode_fps(pdata, idle_mode);
+ }
+ mutex_unlock(&panel->panel_lock);
+
+ SDE_ATRACE_INT("display_idle", 1);
+
+ return 0;
+}
+
+static int panel_wakeup(struct dsi_panel *panel)
+{
+ struct panel_switch_data *pdata = panel->private_data;
+ const struct dsi_display_mode *mode = NULL;
+
+ if (unlikely(!pdata))
+ return -EINVAL;
+
+ mutex_lock(&panel->panel_lock);
+ if (!is_display_mode_same(pdata->display_mode, panel->cur_mode))
+ mode = panel->cur_mode;
+ mutex_unlock(&panel->panel_lock);
+
+ if (mode)
+ panel_queue_switch(pdata, mode);
+
+ SDE_ATRACE_INT("display_idle", 0);
+
+ return 0;
+}
+
+static int panel_update_hbm(struct dsi_panel *panel)
+{
+ struct panel_switch_data *pdata = panel->private_data;
+
+ if (unlikely(!pdata || !pdata->funcs))
+ return -EINVAL;
+
+ if (!pdata->funcs->support_update_hbm)
+ return -EOPNOTSUPP;
+
+ return pdata->funcs->support_update_hbm(panel);
+}
+
+static int s6e3hc2_te2_set_edge_info(struct dsi_panel *panel)
+{
+ struct dsi_panel_te2_config *te2;
+ enum dsi_panel_te2_type current_type;
+ u8 payload[5] = { 0xB9, 0x00, 0x77, 0xBA, 0xBC };
+ u8 rising_low_byte, rising_high_byte;
+ u8 falling_low_byte, falling_high_byte;
+
+ if (unlikely(!panel))
+ return -EINVAL;
+
+ te2 = &panel->te2_config;
+ current_type = te2->current_type;
+
+ rising_low_byte = te2->te2_edge[current_type].rising & 0xff;
+ rising_high_byte = (te2->te2_edge[current_type].rising >> 8) & 0xff;
+ falling_low_byte = te2->te2_edge[current_type].falling & 0xff;
+ falling_high_byte = (te2->te2_edge[current_type].falling >> 8) & 0xff;
+
+ payload[2] = (rising_high_byte << 4) | falling_high_byte;
+ payload[3] = rising_low_byte;
+ payload[4] = falling_low_byte;
+
+ return panel_dsi_write_buf(panel,
+ &payload, sizeof(payload), true);
+}
+
+static int s6e3hc2_te2_update(struct dsi_panel *panel)
+{
+ struct mipi_dsi_device *dsi = &panel->mipi_device;
+ const u8 global_para[] = { 0xB0, 0x2C, 0xF2 };
+ const u8 tout_enable[] = { 0xF2, 0x01 };
+
+ if (s6e3hc2_te2_set_edge_info(panel))
+ goto error;
+
+ if (DSI_WRITE_CMD_BUF(dsi, global_para))
+ goto error;
+
+ if (DSI_WRITE_CMD_BUF(dsi, tout_enable))
+ goto error;
+
+ return 0;
+
+error:
+ pr_err("TE2: Failed to te2 setting update\n");
+ return -EFAULT;
+}
+
+/*
+ * s6e3hc2_te2_update_reg_locked() write the TE2 register data
+ * into panel. Control TE2 signal timing.
+ */
+static int s6e3hc2_te2_update_reg_locked(struct dsi_panel *panel)
+{
+ struct mipi_dsi_device *dsi;
+ int rc;
+
+ if (unlikely(!panel || !panel->private_data))
+ return -EINVAL;
+
+ dsi = &panel->mipi_device;
+
+ if (DSI_WRITE_CMD_BUF(dsi, unlock_cmd))
+ return -EFAULT;
+
+ rc = s6e3hc2_te2_update(panel);
+ if (rc < 0)
+ pr_warn("TE2: setting update fail\n");
+
+ if (DSI_WRITE_CMD_BUF(dsi, lock_cmd))
+ return -EFAULT;
+
+ return rc;
+}
+
+static int panel_update_te2(struct dsi_panel *panel)
+{
+ struct panel_switch_data *pdata = panel->private_data;
+
+ if (unlikely(!panel || !pdata || !pdata->funcs))
+ return -EINVAL;
+
+ if (!pdata->funcs->support_update_te2)
+ return -EOPNOTSUPP;
+
+ return pdata->funcs->support_update_te2(panel);
+}
+
+static int s6e3hc2_te2_normal_mode_update(struct dsi_panel *panel,
+ bool reg_locked)
+{
+ const struct dsi_display_mode *mode;
+
+ if (unlikely(!panel || !panel->te2_config.te2_ready))
+ return -EINVAL;
+
+ mode = panel->cur_mode;
+ panel->te2_config.current_type =
+ ((mode->timing.refresh_rate == S6E3HC2_DEFAULT_FPS)
+ ? TE2_EDGE_60HZ : TE2_EDGE_90HZ);
+
+ if (reg_locked == true)
+ s6e3hc2_te2_update_reg_locked(panel);
+ else
+ s6e3hc2_te2_update(panel);
+
+ return 0;
+}
+
+static int panel_send_nolp(struct dsi_panel *panel)
+{
+ struct panel_switch_data *pdata = panel->private_data;
+
+ if (unlikely(!pdata || !pdata->funcs))
+ return -EINVAL;
+
+ if (!pdata->funcs->send_nolp_cmds)
+ return -EOPNOTSUPP;
+
+ return pdata->funcs->send_nolp_cmds(panel);
+}
+
+static ssize_t debugfs_panel_switch_mode_write(struct file *file,
+ const char __user *user_buf,
+ size_t user_len,
+ loff_t *ppos)
+{
+ struct seq_file *seq = file->private_data;
+ struct panel_switch_data *pdata = seq->private;
+ const struct dsi_display_mode *mode;
+
+ if (!pdata->panel || !dsi_panel_initialized(pdata->panel))
+ return -ENOENT;
+
+ mode = display_mode_from_user(pdata->panel, user_buf, user_len);
+ if (IS_ERR(mode))
+ return PTR_ERR(mode);
+ else if (!mode)
+ return -ENOENT;
+
+ panel_queue_switch(pdata, mode);
+
+ return user_len;
+}
+
+static int debugfs_panel_switch_mode_read(struct seq_file *seq, void *data)
+{
+ struct panel_switch_data *pdata = seq->private;
+ const struct dsi_display_mode *mode;
+
+ if (unlikely(!pdata->panel))
+ return -ENOENT;
+
+ mutex_lock(&pdata->panel->panel_lock);
+ mode = pdata->display_mode;
+ mutex_unlock(&pdata->panel->panel_lock);
+
+ if (mode)
+ seq_printf(seq, "%dx%d@%d\n", mode->timing.h_active,
+ mode->timing.v_active, mode->timing.refresh_rate);
+ else
+ seq_puts(seq, "unknown");
+
+ return 0;
+}
+
+static int debugfs_panel_switch_mode_open(struct inode *inode, struct file *f)
+{
+ return single_open(f, debugfs_panel_switch_mode_read, inode->i_private);
+}
+
+static const struct file_operations panel_switch_fops = {
+ .owner = THIS_MODULE,
+ .open = debugfs_panel_switch_mode_open,
+ .write = debugfs_panel_switch_mode_write,
+ .read = seq_read,
+ .release = single_release,
+};
+
+static ssize_t te2_table_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ const struct dsi_display *display;
+ struct dsi_panel *panel;
+ struct dsi_backlight_config *bl;
+ struct backlight_device *bd;
+ u32 table[TE2_EDGE_MAX * 2] = {0};
+ char *buf_dup;
+ ssize_t rc, buf_dup_len;
+ u8 i, table_len;
+
+ display = dev_get_drvdata(dev);
+ if (unlikely(!display || !display->panel || !count))
+ return -EINVAL;
+
+ panel = display->panel;
+ bl = &panel->bl_config;
+ bd = bl->bl_device;
+ if (unlikely(!bd))
+ return -EINVAL;
+
+ if (!panel->te2_config.te2_ready)
+ return -EOPNOTSUPP;
+
+ buf_dup = kstrndup(buf, count, GFP_KERNEL);
+ if (!buf_dup)
+ return -ENOMEM;
+
+ buf_dup_len = strlen(buf_dup) + 1;
+
+ rc = parse_u32_buf(buf_dup, buf_dup_len, table, TE2_EDGE_MAX * 2);
+ if (rc < 0) {
+ kfree(buf_dup);
+ pr_warn("incorrect parameters from te2 table node, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ table_len = rc / 2;
+ if (table_len != ARRAY_SIZE(panel->te2_config.te2_edge)) {
+ kfree(buf_dup);
+ pr_warn("incorrect array size of te2 table.\n");
+ return -EINVAL;
+ }
+
+ mutex_lock(&panel->panel_lock);
+ for (i = 0; i < table_len; i++) {
+ panel->te2_config.te2_edge[i].rising = table[i * 2];
+ panel->te2_config.te2_edge[i].falling = table[i * 2 + 1];
+ }
+
+ if (!is_standby_mode(bd->props.state)) {
+ pr_debug("te2_config.current_type =%d\n",
+ panel->te2_config.current_type);
+ s6e3hc2_te2_update_reg_locked(panel);
+ }
+
+ mutex_unlock(&panel->panel_lock);
+
+ kfree(buf_dup);
+
+ return count;
+}
+
+static ssize_t te2_table_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ const struct dsi_display *display;
+ struct dsi_panel *panel;
+ int i;
+ ssize_t len = 0;
+
+ display = dev_get_drvdata(dev);
+ if (unlikely(!display || !display->panel))
+ return -EINVAL;
+
+ panel = display->panel;
+ if (!panel->te2_config.te2_ready)
+ return -EOPNOTSUPP;
+
+ mutex_lock(&panel->panel_lock);
+
+ for (i = 0; i < TE2_EDGE_MAX; i++) {
+ len += scnprintf((buf + len), PAGE_SIZE - len, "%u %u ",
+ panel->te2_config.te2_edge[i].rising,
+ panel->te2_config.te2_edge[i].falling);
+ }
+
+ mutex_unlock(&panel->panel_lock);
+
+ len += scnprintf(buf + len, PAGE_SIZE - len, "\n");
+
+ return len;
+}
+
+static DEVICE_ATTR_RW(te2_table);
+
+static struct attribute *panel_te2_sysfs_attrs[] = {
+ &dev_attr_te2_table.attr,
+ NULL,
+};
+
+static struct attribute_group panel_te2_attrs_group = {
+ .attrs = panel_te2_sysfs_attrs,
+};
+
+
+static ssize_t sysfs_idle_mode_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ const struct dsi_display *display;
+ struct panel_switch_data *pdata;
+ const struct dsi_display_mode *mode = NULL;
+ ssize_t rc;
+
+ display = dev_get_drvdata(dev);
+ if (unlikely(!display || !display->panel ||
+ !display->panel->private_data))
+ return -EINVAL;
+
+ pdata = display->panel->private_data;
+
+ mutex_lock(&pdata->panel->panel_lock);
+ mode = pdata->idle_mode;
+ mutex_unlock(&pdata->panel->panel_lock);
+
+ if (mode)
+ rc = snprintf(buf, PAGE_SIZE, "%dx%d@%d\n",
+ mode->timing.h_active, mode->timing.v_active,
+ mode->timing.refresh_rate);
+ else
+ rc = snprintf(buf, PAGE_SIZE, "none\n");
+
+ return rc;
+}
+static ssize_t sysfs_idle_mode_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ const struct dsi_display *display;
+ struct panel_switch_data *pdata;
+ const struct dsi_display_mode *mode = NULL;
+
+ display = dev_get_drvdata(dev);
+ if (unlikely(!display || !display->panel ||
+ !display->panel->private_data))
+ return -EINVAL;
+
+ pdata = display->panel->private_data;
+ if (count > 1 && strncmp(buf, "none", 4)) {
+ char *modestr = kstrndup(buf, count, GFP_KERNEL);
+
+ /* remove any trailing lf at end of sysfs input */
+ mode = display_mode_from_cmdline(display->panel,
+ strim(modestr));
+ kfree(modestr);
+
+ if (IS_ERR(mode))
+ return PTR_ERR(mode);
+ }
+ mutex_lock(&display->panel->panel_lock);
+ pdata->idle_mode = mode;
+ mutex_unlock(&display->panel->panel_lock);
+
+ return count;
+}
+
+static DEVICE_ATTR(idle_mode, 0644,
+ sysfs_idle_mode_show,
+ sysfs_idle_mode_store);
+
+static struct attribute *panel_switch_sysfs_attrs[] = {
+ &dev_attr_idle_mode.attr,
+ NULL,
+};
+
+static struct attribute_group panel_switch_sysfs_attrs_group = {
+ .attrs = panel_switch_sysfs_attrs,
+};
+
+static const struct dsi_panel_funcs panel_funcs = {
+ .mode_switch = panel_switch,
+ .pre_disable = panel_flush_switch_queue,
+ .pre_kickoff = panel_pre_kickoff,
+ .idle = panel_idle,
+ .wakeup = panel_wakeup,
+ .post_enable = panel_post_enable,
+ .pre_lp1 = panel_flush_switch_queue,
+ .update_te2 = panel_update_te2,
+ .update_hbm = panel_update_hbm,
+ .send_nolp = panel_send_nolp,
+};
+
+static int panel_switch_data_init(struct dsi_panel *panel,
+ struct panel_switch_data *pdata)
+{
+ struct sched_param param = {
+ .sched_priority = 16,
+ };
+ const struct dsi_display *display;
+
+ display = dsi_panel_to_display(panel);
+ if (unlikely(!display))
+ return -ENOENT;
+
+ kthread_init_work(&pdata->switch_work, panel_switch_worker);
+ kthread_init_worker(&pdata->worker);
+ pdata->thread = kthread_run(kthread_worker_fn, &pdata->worker, "panel");
+ if (IS_ERR_OR_NULL(pdata->thread))
+ return -EFAULT;
+
+ pdata->panel = panel;
+ pdata->te_listener.handler = panel_handle_te;
+ pdata->display_mode = panel->cur_mode;
+
+ sched_setscheduler(pdata->thread, SCHED_FIFO, ¶m);
+ init_completion(&pdata->te_completion);
+ init_waitqueue_head(&pdata->switch_wq);
+ atomic_set(&pdata->te_counter, 0);
+
+ panel->private_data = pdata;
+ panel->funcs = &panel_funcs;
+
+ pdata->debug_root = debugfs_create_dir("switch", display->root);
+ debugfs_create_file("mode", 0600, pdata->debug_root, pdata,
+ &panel_switch_fops);
+ debugfs_create_u32("te_listen_count", 0600, pdata->debug_root,
+ &pdata->switch_te_listen_count);
+ debugfs_create_atomic_t("te_counter", 0600, pdata->debug_root,
+ &pdata->te_counter);
+
+ sysfs_create_group(&panel->parent->kobj,
+ &panel_switch_sysfs_attrs_group);
+ sysfs_create_group(&panel->parent->kobj,
+ &panel_te2_attrs_group);
+
+ return 0;
+}
+
+static void panel_switch_data_deinit(struct panel_switch_data *pdata)
+{
+ kthread_flush_worker(&pdata->worker);
+ kthread_stop(pdata->thread);
+ sysfs_remove_group(&pdata->panel->parent->kobj,
+ &panel_switch_sysfs_attrs_group);
+ sysfs_remove_group(&pdata->panel->parent->kobj,
+ &panel_te2_attrs_group);
+}
+
+static void panel_switch_data_destroy(struct panel_switch_data *pdata)
+{
+ panel_switch_data_deinit(pdata);
+ if (pdata->panel && pdata->panel->parent)
+ devm_kfree(pdata->panel->parent, pdata);
+}
+
+static struct panel_switch_data *panel_switch_create(struct dsi_panel *panel)
+{
+ struct panel_switch_data *pdata;
+ int rc;
+
+ pdata = devm_kzalloc(panel->parent, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata)
+ return ERR_PTR(-ENOMEM);
+
+ rc = panel_switch_data_init(panel, pdata);
+ if (rc)
+ return ERR_PTR(rc);
+
+ return pdata;
+}
+
+const struct panel_switch_funcs panel_switch_default_funcs = {
+ .create = panel_switch_create,
+ .destroy = panel_switch_data_destroy,
+ .perform_switch = panel_switch_cmd_set_transfer,
+};
+
+struct gamma_calibration_info {
+ u8 *data;
+ size_t len;
+};
+
+struct gamma_fixup_location {
+ u32 src_idx;
+ u32 dst_idx;
+ u8 cmd;
+ u8 mask;
+};
+
+struct gamma_fixup_info {
+ struct gamma_fixup_location *fixup_loc;
+ u32 fixup_loc_count;
+ u32 panel_id_len;
+ u8 *panel_id;
+};
+
+struct gamma_switch_data {
+ struct panel_switch_data base;
+ struct kthread_work gamma_work;
+ struct gamma_fixup_info fixup_gamma;
+ struct gamma_calibration_info cali_gamma;
+ u8 num_of_cali_gamma;
+ bool native_gamma_ready;
+};
+
+#define S6E3HC2_GAMMA_BAND_LEN 45
+
+/**
+ * s6e3hc2_gamma_info - Information used to access gamma data on s6e3hc2.
+ * @cmd: Command to use when writing/reading gamma from the DDIC.
+ * @len: Total number of bytes to write/read from DDIC, including prefix_len.
+ * @prefix_len: Number of bytes that precede gamma data when writing/reading
+ * from the DDIC. This is a subset of len.
+ * @flash_offset: Address offset to use when reading from flash.
+ */
+static const struct s6e3hc2_gamma_info {
+ u8 cmd;
+ u32 len;
+ u32 prefix_len;
+ u32 flash_offset;
+} s6e3hc2_gamma_tables[] = {
+ /* order of commands matter due to use of cmds grouping */
+ { 0xC8, S6E3HC2_GAMMA_BAND_LEN * 3, 0, 0x0000 },
+ { 0xC9, S6E3HC2_GAMMA_BAND_LEN * 4, 0, 0x0087 },
+ { 0xB3, 2 + S6E3HC2_GAMMA_BAND_LEN, 2, 0x013B },
+};
+
+#define S6E3HC2_NUM_GAMMA_TABLES ARRAY_SIZE(s6e3hc2_gamma_tables)
+#define MAX_GAMMA_PACKETS S6E3HC2_NUM_GAMMA_TABLES
+
+struct s6e3hc2_gamma_packet_group {
+ size_t num_packets;
+ struct {
+ u32 index;
+ size_t max_size;
+ } packets[MAX_GAMMA_PACKETS];
+};
+
+struct s6e3hc2_gamma_packet {
+ u32 dbv_threshold;
+ size_t num_groups;
+ struct s6e3hc2_gamma_packet_group groups[MAX_GAMMA_PACKETS];
+};
+
+static const struct s6e3hc2_gamma_packet s6e3hc2_gamma_packets[] = {
+ { .dbv_threshold = 0x39, .num_groups = 2, .groups = {
+ { .num_packets = 1, .packets = {
+ { 1 }, /* 0xC9 */
+ }}, { .num_packets = 2, .packets = {
+ { 0 }, /* 0xC8 */
+ { 2 }, /* 0xB3 */
+ }},
+ }}, { .dbv_threshold = 0xB6, .num_groups = 2, .groups = {
+ { .num_packets = 2, .packets = {
+ { 0 }, /* 0xC8 */
+ { 1, S6E3HC2_GAMMA_BAND_LEN }, /* 0xC9: 1st gamma */
+ }}, { .num_packets = 2, .packets = {
+ { 1 }, /* 0xC9: full gamma */
+ { 2 }, /* 0xB3 */
+ }},
+ }}, { .dbv_threshold = UINT_MAX, .num_groups = 2, .groups = {
+ { .num_packets = 2, .packets = {
+ { 0 }, /* 0xC8 */
+ { 2 }, /* 0xB3 */
+ }}, { .num_packets = 1, .packets = {
+ { 1 }, /* 0xC9 */
+ }},
+ }},
+};
+
+struct s6e3hc2_panel_data {
+ u8 *gamma_data[S6E3HC2_NUM_GAMMA_TABLES];
+ u8 *native_gamma_data[S6E3HC2_NUM_GAMMA_TABLES];
+};
+
+static void s6e3hc2_gamma_group_write(struct panel_switch_data *pdata,
+ struct s6e3hc2_panel_data *priv_data,
+ const struct s6e3hc2_gamma_packet_group *group)
+{
+ const struct s6e3hc2_gamma_info *info;
+ const void *data;
+ size_t len;
+ int i;
+
+ for (i = 0; i < group->num_packets; i++) {
+ const u32 ndx = group->packets[i].index;
+ const u32 max_size = group->packets[i].max_size;
+ const bool last_packet = (i + 1) == group->num_packets;
+
+ if (WARN(ndx >= S6E3HC2_NUM_GAMMA_TABLES, "invalid ndx=%d\n"))
+ continue;
+
+ data = priv_data->gamma_data[ndx];
+
+ if (WARN(!data, "Gamma table #%d not read\n", i))
+ continue;
+
+ info = &s6e3hc2_gamma_tables[ndx];
+ /* extra byte for the dsi command */
+ len = (max_size ?: info->len) + 1;
+
+ pr_debug("Writing %d bytes to 0x%02X gamma last: %d\n",
+ len, info->cmd, last_packet);
+
+
+ if (IS_ERR_VALUE(panel_dsi_write_buf(pdata->panel, data, len,
+ last_packet)))
+ pr_warn("failed sending gamma cmd 0x%02x\n", info->cmd);
+ }
+}
+
+/*
+ * s6e3hc2_gamma_update() expects DD-IC to be in unlocked state, so
+ * to make sure there are unlock/lock commands when calling this func.
+ */
+static void s6e3hc2_gamma_update(struct panel_switch_data *pdata,
+ const struct dsi_display_mode *mode)
+{
+ struct s6e3hc2_panel_data *priv_data;
+ const struct s6e3hc2_gamma_packet *packet = NULL;
+ int i, dbv;
+
+ if (unlikely(!mode || !mode->priv_info))
+ return;
+
+ priv_data = mode->priv_info->switch_data;
+ if (unlikely(!priv_data))
+ return;
+
+ dbv = pdata->panel->bl_config.bl_actual;
+
+ for (i = 0; i < ARRAY_SIZE(s6e3hc2_gamma_packets); i++) {
+ if (dbv < s6e3hc2_gamma_packets[i].dbv_threshold) {
+ packet = &s6e3hc2_gamma_packets[i];
+ break;
+ }
+ }
+
+ if (!packet) {
+ pr_err("Unable to find packet for dbv=%02X\n", dbv);
+ return;
+ }
+
+ pr_debug("Found packet ndx=%d for dbv=%02X\n", i, dbv);
+
+ for (i = 0; i < packet->num_groups; i++)
+ s6e3hc2_gamma_group_write(pdata, priv_data, &packet->groups[i]);
+}
+
+static void s6e3hc2_gamma_update_reg_locked(struct panel_switch_data *pdata,
+ const struct dsi_display_mode *mode)
+{
+ struct dsi_panel *panel = pdata->panel;
+ struct mipi_dsi_device *dsi = &panel->mipi_device;
+
+ if (unlikely(!mode))
+ return;
+
+ DSI_WRITE_CMD_BUF(dsi, unlock_cmd);
+ s6e3hc2_gamma_update(pdata, mode);
+ DSI_WRITE_CMD_BUF(dsi, lock_cmd);
+}
+
+static int s6e3hc2_gamma_read_otp(struct panel_switch_data *pdata,
+ const struct s6e3hc2_panel_data *priv_data)
+{
+ struct mipi_dsi_device *dsi;
+ ssize_t rc;
+ int i;
+
+ SDE_ATRACE_BEGIN(__func__);
+
+ dsi = &pdata->panel->mipi_device;
+
+ for (i = 0; i < S6E3HC2_NUM_GAMMA_TABLES; i++) {
+ const struct s6e3hc2_gamma_info *info =
+ &s6e3hc2_gamma_tables[i];
+ u8 *buf = priv_data->gamma_data[i];
+
+ /* store cmd on first byte to send payload as is */
+ *buf = info->cmd;
+ buf++;
+
+ rc = mipi_dsi_dcs_read(dsi, info->cmd, buf, info->len);
+ if (rc != info->len)
+ pr_warn("Only got %zd / %d bytes\n", rc, info->len);
+ }
+
+ SDE_ATRACE_END(__func__);
+
+ return 0;
+}
+
+static int s6e3hc2_gamma_read_flash(struct panel_switch_data *pdata,
+ const struct s6e3hc2_panel_data *priv_data)
+{
+ struct mipi_dsi_device *dsi;
+ const u8 flash_mode_en[] = { 0xF1, 0xF1, 0xA2 };
+ const u8 flash_mode_dis[] = { 0xF1, 0xA5, 0xA5 };
+ const u8 pgm_dis[] = { 0xC0, 0x00 };
+ const u8 pgm_en[] = { 0xC0, 0x02 };
+ const u8 exe_inst[] = { 0xC0, 0x03 };
+ const u8 write_en[] = { 0xC1,
+ 0x00, 0x00, 0x00, 0x06,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x05 };
+ const u8 quad_en[] = { 0xC1,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x40, 0x02, 0x00, 0x00,
+ 0x00, 0x00, 0x10 };
+ ssize_t rc;
+ int i, j;
+
+ SDE_ATRACE_BEGIN(__func__);
+
+ dsi = &pdata->panel->mipi_device;
+
+ if (DSI_WRITE_CMD_BUF(dsi, flash_mode_en) ||
+ DSI_WRITE_CMD_BUF(dsi, pgm_en) ||
+ DSI_WRITE_CMD_BUF(dsi, write_en) ||
+ DSI_WRITE_CMD_BUF(dsi, exe_inst))
+ goto error;
+ usleep_range(950, 1000);
+
+ if (DSI_WRITE_CMD_BUF(dsi, quad_en) ||
+ DSI_WRITE_CMD_BUF(dsi, exe_inst))
+ goto error;
+ msleep(30);
+
+ for (i = 0; i < S6E3HC2_NUM_GAMMA_TABLES; i++) {
+ const struct s6e3hc2_gamma_info *info;
+ const u8 gpar_cmd[] = { 0xB0, 0x0B };
+ u8 flash_rd[] = { 0xC1,
+ 0x00, 0x00, 0x00, 0x6B, 0x00, 0x00, 0x00, /*Read Inst*/
+ 0x0A, 0x00, 0x00, /* Flash data Address : 0A0000h */
+ 0x00, 0x05, /* Bit rate setting */
+ 0x01 };
+ u32 offset;
+ u8 *buf;
+
+ info = &s6e3hc2_gamma_tables[i];
+ offset = info->flash_offset;
+ buf = priv_data->gamma_data[i];
+ /* store cmd on first byte to send payload as is */
+ *buf = info->cmd;
+ buf++;
+
+ for (j = info->prefix_len; j < info->len; j++, offset++) {
+ u8 tmp[2];
+
+ flash_rd[9] = (offset >> 8) & 0xFF;
+ flash_rd[10] = offset & 0xFF;
+
+ if (DSI_WRITE_CMD_BUF(dsi, flash_rd) ||
+ DSI_WRITE_CMD_BUF(dsi, exe_inst))
+ goto error;
+ usleep_range(200, 250);
+
+ if (DSI_WRITE_CMD_BUF(dsi, gpar_cmd))
+ goto error;
+
+ rc = mipi_dsi_dcs_read(dsi, 0xFB, tmp, sizeof(tmp));
+ if (rc != 2)
+ pr_warn("Only got %zd / 2 bytes\n", rc);
+
+ pr_debug("read flash offset %04x: %02X %02X\n",
+ offset, tmp[0], tmp[1]);
+ buf[j] = tmp[1];
+ }
+ }
+
+ if (DSI_WRITE_CMD_BUF(dsi, pgm_dis) ||
+ DSI_WRITE_CMD_BUF(dsi, flash_mode_dis))
+ goto error;
+
+ SDE_ATRACE_END(__func__);
+
+ return 0;
+
+error:
+ SDE_ATRACE_END(__func__);
+
+ pr_err("Failed to read gamma from flash\n");
+ return -EFAULT;
+}
+
+static int s6e3hc2_gamma_alloc_mode_memory(const struct dsi_display_mode *mode)
+{
+ struct s6e3hc2_panel_data *priv_data;
+ size_t offset, native_offset, total_size;
+ int i;
+ u8 *buf, *native_buf;
+
+ if (unlikely(!mode || !mode->priv_info))
+ return -EINVAL;
+
+ if (mode->priv_info->switch_data)
+ return 0;
+
+ total_size = sizeof(*priv_data);
+
+ for (i = 0; i < S6E3HC2_NUM_GAMMA_TABLES; i++)
+ total_size += s6e3hc2_gamma_tables[i].len;
+ /* add an extra byte for cmd */
+ total_size += S6E3HC2_NUM_GAMMA_TABLES;
+
+ /* hold native gamma */
+ native_offset = total_size;
+ total_size *= 2;
+
+ priv_data = kmalloc(total_size, GFP_KERNEL);
+ if (!priv_data)
+ return -ENOMEM;
+
+ /* use remaining data at the end of buffer */
+ buf = (u8 *)(priv_data);
+ offset = sizeof(*priv_data);
+ native_buf = buf + native_offset;
+
+ for (i = 0; i < S6E3HC2_NUM_GAMMA_TABLES; i++) {
+ const size_t len = s6e3hc2_gamma_tables[i].len;
+
+ priv_data->gamma_data[i] = buf + offset;
+ priv_data->native_gamma_data[i] = native_buf + offset;
+ /* reserve extra byte to hold cmd */
+ offset += len + 1;
+ }
+
+ mode->priv_info->switch_data = priv_data;
+
+ return 0;
+}
+
+static int s6e3hc2_gamma_read_mode(struct panel_switch_data *pdata,
+ const struct dsi_display_mode *mode)
+{
+ const struct s6e3hc2_panel_data *priv_data;
+ int rc;
+
+ rc = s6e3hc2_gamma_alloc_mode_memory(mode);
+ if (rc)
+ return rc;
+
+ priv_data = mode->priv_info->switch_data;
+
+ switch (mode->timing.refresh_rate) {
+ case 60:
+ rc = s6e3hc2_gamma_read_otp(pdata, priv_data);
+ break;
+ case 90:
+ rc = s6e3hc2_gamma_read_flash(pdata, priv_data);
+ break;
+ default:
+ pr_warn("Unknown refresh rate!\n");
+ rc = -EINVAL;
+ break;
+ }
+
+ return rc;
+}
+
+static int find_gamma_data_for_refresh_rate(struct dsi_panel *panel,
+ u32 refresh_rate, u8 ***gamma_data)
+{
+ struct dsi_display_mode *mode;
+ int i;
+
+ if (!gamma_data)
+ return -EINVAL;
+
+ for_each_display_mode(i, mode, panel)
+ if (mode->timing.refresh_rate == refresh_rate) {
+ struct s6e3hc2_panel_data *priv_data =
+ mode->priv_info->switch_data;
+
+ if (unlikely(!priv_data))
+ return -ENODATA;
+
+ *gamma_data = priv_data->gamma_data;
+ return 0;
+ }
+
+ return -ENODATA;
+}
+
+/*
+ * For some modes, gamma curves are located in registers addresses that require
+ * an offset to read/write. Because we cannot access a register offset directly,
+ * we must read the portion of the data that precedes the gamma curve data
+ * itself ("prefix") as well. In such cases, we read the prefix + gamma curve
+ * data from DDIC registers, and only gamma curve data from flash.
+ *
+ * This function looks for such gamma curves, and adjusts gamma data read from
+ * flash to include the prefix read from registers. The result is that, for all
+ * modes, wherever the gamma curves were read from (registers or flash), when
+ * that gamma data is written back to registers the write includes the original
+ * prefix.
+ * In other words, when we write gamma data to registers, we do not modify
+ * prefix data; we only modify gamma data.
+ */
+static int s6e3hc2_gamma_set_prefixes(struct panel_switch_data *pdata)
+{
+ int i;
+ int rc = 0;
+ u8 **gamma_data_otp;
+ u8 **gamma_data_flash;
+
+ /*
+ * For s6e3hc2, 60Hz gamma curves are read from OTP and 90Hz
+ * gamma curves are read from flash.
+ */
+ rc = find_gamma_data_for_refresh_rate(pdata->panel, 60,
+ &gamma_data_otp);
+ if (rc) {
+ pr_err("Error setting gamma prefix: no matching OTP mode, err %d\n",
+ rc);
+ return rc;
+ }
+
+ rc = find_gamma_data_for_refresh_rate(pdata->panel, 90,
+ &gamma_data_flash);
+ if (rc) {
+ pr_err("Error setting gamma prefix: no matching flash mode, err %d\n",
+ rc);
+ return rc;
+ }
+
+ for (i = 0; i < S6E3HC2_NUM_GAMMA_TABLES; i++) {
+ const struct s6e3hc2_gamma_info *gamma_info =
+ &s6e3hc2_gamma_tables[i];
+ u8 *gamma_curve_otp = gamma_data_otp[i];
+ u8 *gamma_curve_flash = gamma_data_flash[i];
+
+ if (!gamma_info->prefix_len)
+ continue;
+
+ /* skip command byte */
+ gamma_curve_otp++;
+ gamma_curve_flash++;
+
+ memcpy(gamma_curve_flash, gamma_curve_otp,
+ gamma_info->prefix_len);
+ }
+
+ return rc;
+}
+
+static int s6e3hc2_copy_gamma_table(u8 **dest_gamma, u8 **src_gamma)
+{
+ int i;
+
+ for (i = 0; i < S6E3HC2_NUM_GAMMA_TABLES; i++) {
+ const size_t len = s6e3hc2_gamma_tables[i].len;
+
+ memcpy(dest_gamma[i], src_gamma[i], len * sizeof(**src_gamma));
+ }
+
+ return 0;
+}
+
+static int s6e3hc2_gamma_read_tables(struct panel_switch_data *pdata)
+{
+ struct gamma_switch_data *sdata;
+ const struct dsi_display_mode *mode;
+ struct mipi_dsi_device *dsi;
+ int i, rc = 0;
+
+ if (unlikely(!pdata || !pdata->panel))
+ return -ENOENT;
+
+ sdata = container_of(pdata, struct gamma_switch_data, base);
+ if (sdata->native_gamma_ready)
+ return 0;
+
+ dsi = &pdata->panel->mipi_device;
+ if (DSI_WRITE_CMD_BUF(dsi, unlock_cmd))
+ return -EFAULT;
+
+ for_each_display_mode(i, mode, pdata->panel) {
+ rc = s6e3hc2_gamma_read_mode(pdata, mode);
+ if (rc) {
+ pr_err("Unable to read gamma for mode #%d\n", i);
+ goto abort;
+ }
+ }
+
+ rc = s6e3hc2_gamma_set_prefixes(pdata);
+ if (rc) {
+ pr_err("Unable to set gamma prefix\n");
+ goto abort;
+ }
+
+ for_each_display_mode(i, mode, pdata->panel) {
+ struct s6e3hc2_panel_data *priv_data =
+ mode->priv_info->switch_data;
+ u8 **gamma_data = priv_data->gamma_data;
+ u8 **native_gamma_data = priv_data->native_gamma_data;
+
+ s6e3hc2_copy_gamma_table(native_gamma_data, gamma_data);
+ }
+
+ sdata->native_gamma_ready = true;
+
+abort:
+ if (DSI_WRITE_CMD_BUF(dsi, lock_cmd))
+ return -EFAULT;
+
+ return rc;
+}
+
+static void s6e3hc2_gamma_print(struct seq_file *seq,
+ const struct dsi_display_mode *mode)
+{
+ const struct s6e3hc2_panel_data *priv_data;
+ int i, j;
+
+ if (!mode || !mode->priv_info)
+ return;
+
+ seq_printf(seq, "\n=== %dhz Mode Gamma ===\n",
+ mode->timing.refresh_rate);
+
+ priv_data = mode->priv_info->switch_data;
+
+ if (!priv_data) {
+ seq_puts(seq, "No data available!\n");
+ return;
+ }
+
+ for (i = 0; i < S6E3HC2_NUM_GAMMA_TABLES; i++) {
+ const size_t len = s6e3hc2_gamma_tables[i].len;
+ const u8 cmd = s6e3hc2_gamma_tables[i].cmd;
+ const u8 *buf = priv_data->gamma_data[i] + 1;
+
+ seq_printf(seq, "0x%02X:", cmd);
+ for (j = 0; j < len; j++) {
+ if (j && (j % 8) == 0)
+ seq_puts(seq, "\n ");
+ seq_printf(seq, " %02X", buf[j]);
+ }
+ seq_puts(seq, "\n");
+ }
+}
+
+static int debugfs_s6e3hc2_gamma_read(struct seq_file *seq, void *data)
+{
+ struct panel_switch_data *pdata = seq->private;
+ int i, rc;
+
+ if (unlikely(!pdata || !pdata->panel))
+ return -EINVAL;
+
+ if (!dsi_panel_initialized(pdata->panel))
+ return -EPIPE;
+
+ mutex_lock(&pdata->panel->panel_lock);
+ rc = s6e3hc2_gamma_read_tables(pdata);
+ if (!rc) {
+ const struct dsi_display_mode *mode;
+
+ for_each_display_mode(i, mode, pdata->panel)
+ s6e3hc2_gamma_print(seq, mode);
+ }
+ mutex_unlock(&pdata->panel->panel_lock);
+
+ return rc;
+}
+
+ssize_t debugfs_s6e3hc2_gamma_write(struct file *file,
+ const char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ struct seq_file *seq = file->private_data;
+ struct panel_switch_data *pdata = seq->private;
+ struct gamma_switch_data *sdata;
+
+ if (!pdata || !pdata->panel)
+ return -EINVAL;
+
+ sdata = container_of(pdata, struct gamma_switch_data, base);
+
+ mutex_lock(&pdata->panel->panel_lock);
+ sdata->native_gamma_ready = false;
+ mutex_unlock(&pdata->panel->panel_lock);
+
+ return count;
+}
+
+static int debugfs_s6e3hc2_gamma_open(struct inode *inode, struct file *f)
+{
+ return single_open(f, debugfs_s6e3hc2_gamma_read, inode->i_private);
+}
+
+static const struct file_operations s6e3hc2_read_gamma_fops = {
+ .owner = THIS_MODULE,
+ .open = debugfs_s6e3hc2_gamma_open,
+ .write = debugfs_s6e3hc2_gamma_write,
+ .read = seq_read,
+ .release = single_release,
+};
+
+static int s6e3hc2_check_gamma_infos(const struct s6e3hc2_gamma_info *infos,
+ size_t num_infos)
+{
+ int i;
+
+ if (!infos) {
+ pr_err("Null gamma infos\n");
+ return -EINVAL;
+ }
+
+ for (i = 0; i < num_infos; i++) {
+ const struct s6e3hc2_gamma_info *info = &infos[i];
+
+ if (unlikely(info->prefix_len >= info->len)) {
+ pr_err("Gamma prefix length (%u) >= total length length (%u)\n",
+ info->prefix_len, info->len);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int s6e3hc2_restore_gamma_data(struct gamma_switch_data *sdata)
+{
+ struct panel_switch_data *pdata;
+ struct dsi_display_mode *mode;
+ int i;
+
+ if (unlikely(!sdata))
+ return -EINVAL;
+
+ pdata = &sdata->base;
+ if (unlikely(!pdata->panel))
+ return -EINVAL;
+
+ for_each_display_mode(i, mode, pdata->panel) {
+ struct s6e3hc2_panel_data *priv_data =
+ mode->priv_info->switch_data;
+ u8 **gamma_data = priv_data->gamma_data;
+ u8 **native_gamma_data = priv_data->native_gamma_data;
+
+ s6e3hc2_copy_gamma_table(gamma_data, native_gamma_data);
+ }
+
+ sdata->num_of_cali_gamma = 0;
+
+ return 0;
+}
+
+static int parse_payload_len(const u8 *payload, size_t len, size_t *out_size)
+{
+ size_t payload_len;
+
+ if (!out_size || len <= CALI_GAMMA_HEADER_SIZE)
+ return -EINVAL;
+
+ *out_size = payload_len = (payload[1] << 8) + payload[2];
+
+ return payload_len &&
+ (payload_len + CALI_GAMMA_HEADER_SIZE <= len) ? 0 : -EINVAL;
+}
+
+/* ID (1 byte) | payload size (2 bytes) | payload */
+static int s6e3hc2_check_gamma_payload(const u8 *src, size_t len)
+{
+ size_t total_len = 0;
+ int num_payload = 0;
+
+ if (!src)
+ return -EINVAL;
+
+ while (len > total_len) {
+ const u8 *payload = src + total_len;
+ size_t payload_len;
+ int rc;
+
+ rc = parse_payload_len(payload, len - total_len, &payload_len);
+ if (rc)
+ return -EINVAL;
+
+ total_len += CALI_GAMMA_HEADER_SIZE + payload_len;
+ num_payload++;
+ }
+
+ return num_payload;
+}
+
+static void s6e3hc2_gamma_fixup_after_overwrite(
+ const struct gamma_fixup_location *fixup_loc,
+ u8 *gamma_buf, const size_t len)
+{
+ const u32 src_idx = fixup_loc->src_idx;
+ const u32 dst_idx = fixup_loc->dst_idx;
+ const u8 mask = fixup_loc->mask;
+
+ if (len <= src_idx || len <= dst_idx) {
+ pr_warn("gamma length %zu of cmd 0x%x not enough\n", len,
+ fixup_loc->cmd);
+ return;
+ }
+
+ if (mask) {
+ gamma_buf[dst_idx] &= ~mask;
+ gamma_buf[dst_idx] |= (gamma_buf[src_idx] & mask);
+ }
+}
+
+static int s6e3hc2_overwrite_gamma_bands(
+ const struct dsi_panel_vendor_info *panel_info,
+ const struct gamma_fixup_info *fixup_gamma,
+ u8 **gamma_data, const u8 *src, size_t len)
+{
+ int i, num_of_reg;
+ size_t payload_len, read_len = 0;
+
+ if (!gamma_data || !src)
+ return -EINVAL;
+
+ num_of_reg = s6e3hc2_check_gamma_payload(src, len);
+ if (num_of_reg != S6E3HC2_NUM_GAMMA_TABLES - 1) {
+ pr_err("Invalid gamma bands\n");
+ return -EINVAL;
+ }
+
+ if (fixup_gamma) {
+ if (!panel_info || !panel_info->extinfo_length ||
+ panel_info->extinfo_length != fixup_gamma->panel_id_len ||
+ memcmp(panel_info->extinfo, fixup_gamma->panel_id,
+ panel_info->extinfo_length))
+ fixup_gamma = NULL;
+ }
+
+ /* reg (1 byte) | band size (2 bytes) | band */
+ for (i = 0; i < num_of_reg; i++) {
+ const struct s6e3hc2_gamma_info *gamma_info =
+ &s6e3hc2_gamma_tables[i];
+ const u8 *payload = src + read_len;
+ const u8 cmd = *payload;
+ u8 *tmp_buf = gamma_data[i] + sizeof(cmd)
+ + gamma_info->prefix_len;
+
+ parse_payload_len(payload, len - read_len, &payload_len);
+ if (gamma_info->cmd != cmd ||
+ payload_len != (gamma_info->len - gamma_info->prefix_len)) {
+ pr_err("Failed to overwrite 0x%02X reg\n", cmd);
+ return -EINVAL;
+ }
+
+ memcpy(tmp_buf, payload + CALI_GAMMA_HEADER_SIZE, payload_len);
+ if (fixup_gamma && fixup_gamma->fixup_loc) {
+ int cnt;
+
+ for (cnt = 0; cnt < fixup_gamma->fixup_loc_count; cnt++)
+ if (cmd == fixup_gamma->fixup_loc[cnt].cmd)
+ s6e3hc2_gamma_fixup_after_overwrite(
+ &fixup_gamma->fixup_loc[cnt],
+ tmp_buf, payload_len);
+ }
+ read_len = CALI_GAMMA_HEADER_SIZE + payload_len;
+ }
+
+ return 0;
+}
+
+static int s6e3hc2_overwrite_gamma_data(struct gamma_switch_data *sdata)
+{
+ struct panel_switch_data *pdata;
+ struct dsi_panel *panel;
+ u8 *payload, **old_gamma_data;
+ size_t payload_len, total_len;
+ int rc = 0, num_of_fps;
+ u8 count = 0;
+
+ if (unlikely(!sdata))
+ return -EINVAL;
+
+ pdata = &sdata->base;
+ payload = sdata->cali_gamma.data;
+
+ if (unlikely(!pdata->panel || !payload))
+ return -EINVAL;
+
+ total_len = sdata->cali_gamma.len;
+ num_of_fps = s6e3hc2_check_gamma_payload(payload, total_len);
+ if (num_of_fps <= 0) {
+ pr_err("Invalid gamma data\n");
+ return -EINVAL;
+ }
+
+ /*
+ * 60Hz's gamma table and 90Hz's gamma table are the same size,
+ * so we won't recalculate payload_len.
+ */
+ parse_payload_len(payload, total_len, &payload_len);
+
+ panel = pdata->panel;
+
+ /* FPS (1 byte) | gamma size (2 bytes) | gamma */
+ while (count < num_of_fps) {
+ const u8 cali_fps = *payload;
+
+ rc = find_gamma_data_for_refresh_rate(panel,
+ cali_fps, &old_gamma_data);
+ if (rc) {
+ pr_err("Not support %ufps, err %d\n", cali_fps, rc);
+ break;
+ }
+ payload += CALI_GAMMA_HEADER_SIZE;
+ rc = s6e3hc2_overwrite_gamma_bands(&panel->vendor_info,
+ &sdata->fixup_gamma,
+ old_gamma_data, payload,
+ payload_len);
+ if (rc) {
+ pr_err("Failed to overwrite gamma\n");
+ break;
+ }
+ payload += payload_len;
+ count++;
+ }
+
+ if (rc) {
+ s6e3hc2_restore_gamma_data(sdata);
+ } else {
+ s6e3hc2_gamma_update_reg_locked(pdata, panel->cur_mode);
+ sdata->num_of_cali_gamma = count;
+ pr_info("Finished overwriting gamma\n");
+ }
+ return rc;
+}
+
+static void s6e3hc2_release_cali_gamma(struct gamma_switch_data *sdata)
+{
+ if (!sdata)
+ return;
+
+ kfree(sdata->cali_gamma.data);
+ sdata->cali_gamma.data = NULL;
+ sdata->cali_gamma.len = 0;
+}
+
+static int s6e3hc2_create_cali_gamma(struct gamma_switch_data *sdata,
+ size_t len, char *buf, size_t buf_len)
+{
+ int rc = 0;
+
+ if (unlikely(!sdata || !buf))
+ return -EINVAL;
+
+ if (sdata->cali_gamma.data)
+ s6e3hc2_release_cali_gamma(sdata);
+
+ sdata->cali_gamma.data = kzalloc(len, GFP_KERNEL);
+ if (!sdata->cali_gamma.data)
+ return -ENOMEM;
+
+ rc = parse_byte_buf(sdata->cali_gamma.data, len, buf, buf_len);
+ if (rc <= 0) {
+ pr_err("Invalid gamma data\n");
+ s6e3hc2_release_cali_gamma(sdata);
+ return -EINVAL;
+ }
+ sdata->cali_gamma.len = rc;
+
+ return 0;
+}
+
+static void s6e3hc2_gamma_work(struct kthread_work *work)
+{
+ struct gamma_switch_data *sdata;
+ struct dsi_panel *panel;
+
+ sdata = container_of(work, struct gamma_switch_data, gamma_work);
+ panel = sdata->base.panel;
+
+ if (!panel)
+ return;
+
+ mutex_lock(&panel->panel_lock);
+
+ s6e3hc2_gamma_read_tables(&sdata->base);
+ if (sdata->cali_gamma.data) {
+ s6e3hc2_overwrite_gamma_data(sdata);
+ s6e3hc2_release_cali_gamma(sdata);
+ }
+
+ mutex_unlock(&panel->panel_lock);
+}
+
+static ssize_t
+s6e3hc2_gamma_store(struct dsi_panel *panel, const char *buf, size_t count)
+{
+ struct panel_switch_data *pdata;
+ struct gamma_switch_data *sdata;
+ size_t len, buf_dup_len;
+ int rc = 0;
+ char *buf_dup;
+
+ if (unlikely(!panel || !panel->private_data))
+ return -EINVAL;
+
+ pdata = panel->private_data;
+ sdata = container_of(pdata, struct gamma_switch_data, base);
+
+ /* calculate length for worst case (1 digit per byte + whitespace) */
+ len = (count + 1) / 2;
+ buf_dup = kstrndup(buf, count, GFP_KERNEL);
+ if (!buf_dup)
+ return -ENOMEM;
+
+ buf_dup_len = strlen(buf_dup) + 1;
+
+ mutex_lock(&panel->panel_lock);
+
+ if (!strncmp(buf_dup, DEFAULT_GAMMA_STR, strlen(DEFAULT_GAMMA_STR))) {
+ if (!sdata->num_of_cali_gamma)
+ goto done;
+
+ s6e3hc2_restore_gamma_data(sdata);
+ if (dsi_panel_initialized(panel))
+ s6e3hc2_gamma_update_reg_locked(pdata,
+ panel->cur_mode);
+ pr_info("Finished restore gamma\n");
+ goto done;
+ }
+
+ rc = s6e3hc2_create_cali_gamma(sdata, len, buf_dup, buf_dup_len);
+ if (rc)
+ goto error;
+
+ if (!sdata->native_gamma_ready) {
+ pr_info("Gamma table is not ready!\n");
+ goto done;
+ }
+
+ if (!dsi_panel_initialized(panel)) {
+ pr_warn("Panel not yet initialized.\n");
+ rc = -EINVAL;
+ goto error;
+ }
+
+ rc = s6e3hc2_overwrite_gamma_data(sdata);
+ if (rc)
+ pr_warn("Failed to update calibrated gamma.\n");
+
+ s6e3hc2_release_cali_gamma(sdata);
+
+error:
+done:
+ mutex_unlock(&panel->panel_lock);
+ kfree(buf_dup);
+
+ return rc ? rc : count;
+}
+
+static ssize_t
+gamma_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ const struct dsi_display *display;
+ struct dsi_panel *panel;
+ struct panel_switch_data *pdata;
+
+ display = dev_get_drvdata(dev);
+ if (unlikely(!display || !display->panel ||
+ !display->panel->private_data))
+ return -EINVAL;
+
+ panel = display->panel;
+ pdata = panel->private_data;
+
+ if (unlikely(!pdata || !pdata->funcs))
+ return -EINVAL;
+
+ if (!pdata->funcs->support_cali_gamma_store)
+ return -EOPNOTSUPP;
+
+ return pdata->funcs->support_cali_gamma_store(panel, buf, count);
+}
+
+struct device_attribute dev_attr_gamma = __ATTR_WO(gamma);
+
+static struct attribute *gamma_attrs[] = {
+ &dev_attr_gamma.attr,
+ NULL
+};
+
+static const struct attribute_group gamma_group = {
+ .attrs = gamma_attrs,
+};
+
+static void
+s6e3hc2_gamma_release_fixup_info(struct gamma_fixup_info *fixup_gamma)
+{
+ if (!fixup_gamma)
+ return;
+
+ kfree(fixup_gamma->fixup_loc);
+ fixup_gamma->fixup_loc = NULL;
+ fixup_gamma->fixup_loc_count = 0;
+
+ kfree(fixup_gamma->panel_id);
+ fixup_gamma->panel_id = NULL;
+ fixup_gamma->panel_id_len = 0;
+}
+
+#define GAMMA_FIXUP_LOCATION_LEN 4
+static void
+s6e3hc2_gamma_create_fixup_info(struct dsi_panel *panel,
+ struct gamma_fixup_info *fixup_gamma)
+{
+ struct dsi_display *display;
+ struct device_node *of_node;
+ int i, rc, id_len, loc_len, offset = 0;
+ u32 loc_count, *fixup_loc;
+
+ if (unlikely(!panel || !fixup_gamma))
+ return;
+
+ display = dsi_panel_to_display(panel);
+ if (unlikely(!display || !display->pdev)) {
+ pr_warn("Invalid device\n");
+ return;
+ }
+
+ of_node = display->pdev->dev.of_node;
+ if (unlikely(!of_node))
+ return;
+
+ id_len = of_property_count_u8_elems(of_node,
+ "google,gamma_fixup_panel_id");
+ if (id_len <= 0) {
+ pr_debug("Skip gamma fixup\n");
+ return;
+ }
+
+ fixup_gamma->panel_id = kzalloc(id_len *
+ sizeof(*fixup_gamma->panel_id), GFP_KERNEL);
+ if (!fixup_gamma->panel_id)
+ return;
+
+ rc = of_property_read_u8_array(of_node,
+ "google,gamma_fixup_panel_id",
+ fixup_gamma->panel_id, id_len);
+ if (rc) {
+ pr_err("Failed to parse panel id\n");
+ goto error;
+ }
+
+ loc_len = of_property_count_u32_elems(of_node,
+ "google,gamma_fixup_location");
+ if (loc_len <= 0 || loc_len % GAMMA_FIXUP_LOCATION_LEN != 0) {
+ pr_err("invalid format\n");
+ goto error;
+ }
+
+ loc_count = loc_len / GAMMA_FIXUP_LOCATION_LEN;
+ fixup_loc = kcalloc(loc_len, sizeof(*fixup_loc), GFP_KERNEL);
+ if (!fixup_loc)
+ goto error;
+
+ rc = of_property_read_u32_array(of_node,
+ "google,gamma_fixup_location", fixup_loc, loc_len);
+ if (rc) {
+ pr_err("Failed to parse location\n");
+ goto error_fixup_loc;
+ }
+
+ fixup_gamma->fixup_loc = kcalloc(loc_count,
+ sizeof(*fixup_gamma->fixup_loc), GFP_KERNEL);
+ if (!fixup_gamma->fixup_loc)
+ goto error_fixup_loc;
+
+ for (i = 0; i < loc_count; i++) {
+ fixup_gamma->fixup_loc[i].cmd = fixup_loc[offset];
+ fixup_gamma->fixup_loc[i].src_idx = fixup_loc[offset + 1];
+ fixup_gamma->fixup_loc[i].dst_idx = fixup_loc[offset + 2];
+ fixup_gamma->fixup_loc[i].mask = fixup_loc[offset + 3];
+ offset += GAMMA_FIXUP_LOCATION_LEN;
+ }
+
+ fixup_gamma->fixup_loc_count = loc_count;
+ fixup_gamma->panel_id_len = id_len;
+
+ kfree(fixup_loc);
+
+ return;
+
+error_fixup_loc:
+ kfree(fixup_loc);
+error:
+ kfree(fixup_gamma->panel_id);
+ fixup_gamma->panel_id = NULL;
+}
+
+static struct panel_switch_data *s6e3hc2_switch_create(struct dsi_panel *panel)
+{
+ struct gamma_switch_data *sdata;
+ int rc;
+
+ rc = s6e3hc2_check_gamma_infos(s6e3hc2_gamma_tables,
+ S6E3HC2_NUM_GAMMA_TABLES);
+ if (rc)
+ return ERR_PTR(rc);
+
+ sdata = devm_kzalloc(panel->parent, sizeof(*sdata), GFP_KERNEL);
+ if (!sdata)
+ return ERR_PTR(-ENOMEM);
+
+ rc = panel_switch_data_init(panel, &sdata->base);
+ if (rc)
+ return ERR_PTR(rc);
+
+ kthread_init_work(&sdata->gamma_work, s6e3hc2_gamma_work);
+ debugfs_create_file("gamma", 0600, sdata->base.debug_root,
+ &sdata->base, &s6e3hc2_read_gamma_fops);
+ s6e3hc2_gamma_create_fixup_info(panel, &sdata->fixup_gamma);
+
+ return &sdata->base;
+}
+
+static void s6e3hc2_switch_data_destroy(struct panel_switch_data *pdata)
+{
+ struct gamma_switch_data *sdata;
+
+ sdata = container_of(pdata, struct gamma_switch_data, base);
+
+ s6e3hc2_gamma_release_fixup_info(&sdata->fixup_gamma);
+ panel_switch_data_deinit(pdata);
+ if (pdata->panel && pdata->panel->parent)
+ devm_kfree(pdata->panel->parent, sdata);
+}
+
+static int s6e3hc2_update_hbm(struct dsi_panel *panel)
+{
+ const struct panel_switch_data *pdata = panel->private_data;
+
+ return s6e3hc2_switch_mode_update(panel, pdata->display_mode, true);
+}
+
+static void s6e3hc2_perform_switch(struct panel_switch_data *pdata,
+ const struct dsi_display_mode *mode)
+{
+ struct dsi_panel *panel = pdata->panel;
+ struct mipi_dsi_device *dsi = &panel->mipi_device;
+
+ if (!mode)
+ return;
+
+ if (DSI_WRITE_CMD_BUF(dsi, unlock_cmd))
+ return;
+
+ s6e3hc2_switch_mode_update(panel, mode, false);
+ s6e3hc2_gamma_update(pdata, mode);
+
+ s6e3hc2_te2_normal_mode_update(panel, false);
+
+ DSI_WRITE_CMD_BUF(dsi, lock_cmd);
+}
+
+int s6e3hc2_send_nolp_cmds(struct dsi_panel *panel)
+{
+ struct panel_switch_data *pdata;
+ struct dsi_display_mode *cur_mode;
+ struct dsi_panel_cmd_set *cmd;
+ int rc = 0;
+
+ if (unlikely(!panel || !panel->cur_mode))
+ return -EINVAL;
+
+ pdata = panel->private_data;
+ cur_mode = panel->cur_mode;
+
+ cmd = &cur_mode->priv_info->cmd_sets[DSI_CMD_SET_NOLP];
+ rc = dsi_panel_cmd_set_transfer(panel, cmd);
+ if (rc) {
+ pr_debug("[%s] failed to send DSI_CMD_SET_NOLP cmd, rc=%d\n",
+ panel->name, rc);
+ return rc;
+ }
+
+ s6e3hc2_gamma_update(pdata, cur_mode);
+
+ s6e3hc2_te2_normal_mode_update(panel, false);
+
+ cmd = &cur_mode->priv_info->cmd_sets[DSI_CMD_SET_POST_NOLP];
+ rc = dsi_panel_cmd_set_transfer(panel, cmd);
+ if (rc)
+ pr_debug("[%s] failed to send DSI_CMD_SET_POST_NOLP cmd, rc=%d\n",
+ panel->name, rc);
+ return rc;
+}
+
+static bool s6e3hc2_need_update_gamma(
+ const struct gamma_switch_data *sdata,
+ const struct dsi_display_mode *mode)
+{
+ const struct panel_switch_data *pdata = &sdata->base;
+
+ return mode && pdata && pdata->panel &&
+ !(mode->dsi_mode_flags & DSI_MODE_FLAG_DMS) &&
+ (pdata->panel->power_mode == SDE_MODE_DPMS_ON) &&
+ !((mode->timing.refresh_rate == S6E3HC2_DEFAULT_FPS) &&
+ !sdata->num_of_cali_gamma);
+}
+
+static int s6e3hc2_post_enable(struct panel_switch_data *pdata)
+{
+ struct gamma_switch_data *sdata;
+ const struct dsi_display_mode *mode;
+ struct dsi_panel *panel = pdata->panel;
+
+ if (unlikely(!pdata || !pdata->panel))
+ return -ENOENT;
+
+ sdata = container_of(pdata, struct gamma_switch_data, base);
+ mode = pdata->panel->cur_mode;
+
+ kthread_flush_work(&sdata->gamma_work);
+ if (!sdata->native_gamma_ready) {
+ kthread_queue_work(&pdata->worker, &sdata->gamma_work);
+ } else if (s6e3hc2_need_update_gamma(sdata, mode)) {
+ mutex_lock(&pdata->panel->panel_lock);
+ s6e3hc2_gamma_update_reg_locked(pdata, mode);
+ mutex_unlock(&pdata->panel->panel_lock);
+ pr_debug("Updated gamma for %dhz\n", mode->timing.refresh_rate);
+ }
+
+ if (!(mode->dsi_mode_flags & DSI_MODE_FLAG_DMS)) {
+ mutex_lock(&panel->panel_lock);
+ s6e3hc2_te2_normal_mode_update(panel, true);
+ mutex_unlock(&panel->panel_lock);
+ }
+
+ return 0;
+}
+
+const struct panel_switch_funcs s6e3hc2_switch_funcs = {
+ .create = s6e3hc2_switch_create,
+ .destroy = s6e3hc2_switch_data_destroy,
+ .perform_switch = s6e3hc2_perform_switch,
+ .post_enable = s6e3hc2_post_enable,
+ .support_update_te2 = s6e3hc2_te2_update_reg_locked,
+ .support_update_hbm = s6e3hc2_update_hbm,
+ .send_nolp_cmds = s6e3hc2_send_nolp_cmds,
+ .support_cali_gamma_store = s6e3hc2_gamma_store,
+};
+
+static const struct of_device_id panel_switch_dt_match[] = {
+ {
+ .compatible = DSI_PANEL_GAMMA_NAME,
+ .data = &s6e3hc2_switch_funcs,
+ },
+ {
+ .compatible = DSI_PANEL_SWITCH_NAME,
+ .data = &panel_switch_default_funcs,
+ },
+ {},
+};
+
+int dsi_panel_switch_init(struct dsi_panel *panel)
+{
+ struct panel_switch_data *pdata = NULL;
+ const struct panel_switch_funcs *funcs = NULL;
+ const struct of_device_id *match;
+
+ match = of_match_node(panel_switch_dt_match, panel->panel_of_node);
+ if (match && match->data)
+ funcs = match->data;
+
+ if (!funcs) {
+ pr_info("Panel switch is not supported\n");
+ return 0;
+ }
+
+ if (funcs->create)
+ pdata = funcs->create(panel);
+
+ if (IS_ERR_OR_NULL(pdata))
+ return -ENOENT;
+
+ pdata->funcs = funcs;
+
+ if (pdata->funcs->support_cali_gamma_store) {
+ if (sysfs_create_group(&panel->parent->kobj,
+ &gamma_group))
+ pr_warn("unable to create gamma_group\n");
+ }
+
+ return 0;
+}
+
+void dsi_panel_switch_put_mode(struct dsi_display_mode *mode)
+{
+ if (likely(mode->priv_info)) {
+ kfree(mode->priv_info->switch_data);
+ mode->priv_info->switch_data = NULL;
+ }
+}
+
+/*
+ * This should be called without panel_lock as flush/wait on worker can
+ * deadlock while holding it
+ */
+void dsi_panel_switch_destroy(struct dsi_panel *panel)
+{
+ struct panel_switch_data *pdata = panel->private_data;
+ struct dsi_display_mode *mode;
+ int i;
+
+ if (pdata->funcs->support_cali_gamma_store)
+ sysfs_remove_group(&pdata->panel->parent->kobj,
+ &gamma_group);
+
+ for_each_display_mode(i, mode, pdata->panel)
+ dsi_panel_switch_put_mode(mode);
+
+ if (pdata && pdata->funcs && pdata->funcs->destroy)
+ pdata->funcs->destroy(pdata);
+}
diff --git a/msm/dsi/dsi_pwr.c b/msm/dsi/dsi_pwr.c
index bcaefaf..1448d92 100644
--- a/msm/dsi/dsi_pwr.c
+++ b/msm/dsi/dsi_pwr.c
@@ -398,50 +398,3 @@
return rc;
}
-/*
- * dsi_pwr_panel_regulator_mode_set()
- * set the AB/IBB regulator mode for OLED panel
- * AOD mode entry and exit
- * @regs: Pointer to set of regulators to enable or disable.
- * @reg_name: Name of panel power we want to set.
- * @retulator_mode: Regulator mode values, like:
- * REGULATOR_MODE_INVALID
- * REGULATOR_MODE_FAST
- * REGULATOR_MODE_NORMAL
- * REGULATOR_MODE_IDLE
- * REGULATOR_MODE_STANDBY
- *
- * return: error code in case of failure or 0 for success.
- */
-int dsi_pwr_panel_regulator_mode_set(struct dsi_regulator_info *regs,
- const char *reg_name,
- int regulator_mode)
-{
- int i = 0, rc = 0;
- struct dsi_vreg *vreg;
-
- if (regs->count == 0)
- return -EINVAL;
-
- if (!regs->vregs)
- return -EINVAL;
-
- for (i = 0; i < regs->count; i++) {
- vreg = ®s->vregs[i];
- if (!strcmp(vreg->vreg_name, reg_name)) {
- rc = regulator_set_mode(vreg->vreg,
- regulator_mode);
- if (rc)
- DSI_ERR("Regulator %s set mode %d failed\n",
- vreg->vreg_name, rc);
- break;
- }
- }
-
- if (i >= regs->count) {
- DSI_ERR("Regulator %s was not found\n", reg_name);
- return -EINVAL;
- }
-
- return rc;
-}
diff --git a/msm/dsi/dsi_pwr.h b/msm/dsi/dsi_pwr.h
index fd9ef2c..3c5518d 100644
--- a/msm/dsi/dsi_pwr.h
+++ b/msm/dsi/dsi_pwr.h
@@ -86,21 +86,4 @@
*/
int dsi_pwr_enable_regulator(struct dsi_regulator_info *regs, bool enable);
-/**
- * dsi_pwr_panel_regulator_mode_set()
- * set regulator mode for OLED panel
- * @regs: Pointer to set of regulators to enable or disable.
- * @reg_name: Panel regulator name
- * @regulator_mode: Regulator mode values, like:
- * REGULATOR_MODE_INVALID
- * REGULATOR_MODE_FAST
- * REGULATOR_MODE_NORMAL
- * REGULATOR_MODE_IDLE
- * REGULATOR_MODE_STANDBY
- *
- * return: error code in case of failure or 0 for success.
- */
-int dsi_pwr_panel_regulator_mode_set(struct dsi_regulator_info *regs,
- const char *reg_name,
- int regulator_mode);
#endif /* _DSI_PWR_H_ */
diff --git a/msm/msm_atomic.c b/msm/msm_atomic.c
index ddd966d..cd5bca1 100644
--- a/msm/msm_atomic.c
+++ b/msm/msm_atomic.c
@@ -33,6 +33,49 @@
struct kthread_work commit_work;
};
+static BLOCKING_NOTIFIER_HEAD(msm_drm_notifier_list);
+
+/**
+ * msm_drm_register_client - register a client notifier
+ * @nb: notifier block to callback on events
+ *
+ * This function registers a notifier callback function
+ * to msm_drm_notifier_list, which would be called when
+ * received unblank/power down event.
+ */
+int msm_drm_register_client(struct notifier_block *nb)
+{
+ return blocking_notifier_chain_register(&msm_drm_notifier_list,
+ nb);
+}
+EXPORT_SYMBOL(msm_drm_register_client);
+
+/**
+ * msm_drm_unregister_client - unregister a client notifier
+ * @nb: notifier block to callback on events
+ *
+ * This function unregisters the callback function from
+ * msm_drm_notifier_list.
+ */
+int msm_drm_unregister_client(struct notifier_block *nb)
+{
+ return blocking_notifier_chain_unregister(&msm_drm_notifier_list,
+ nb);
+}
+EXPORT_SYMBOL(msm_drm_unregister_client);
+
+/**
+ * msm_drm_notifier_call_chain - notify clients of drm_events
+ * @val: event MSM_DRM_EARLY_EVENT_BLANK or MSM_DRM_EVENT_BLANK
+ * @v: notifier data, inculde display id and display blank
+ * event(unblank or power down).
+ */
+int msm_drm_notifier_call_chain(unsigned long val, void *v)
+{
+ return blocking_notifier_call_chain(&msm_drm_notifier_list, val,
+ v);
+}
+
static inline bool _msm_seamless_for_crtc(struct drm_device *dev,
struct drm_atomic_state *state,
struct drm_crtc_state *crtc_state, bool enable)
diff --git a/msm/msm_drv.c b/msm/msm_drv.c
index 7025d9d..c3f58be 100644
--- a/msm/msm_drv.c
+++ b/msm/msm_drv.c
@@ -39,6 +39,7 @@
#include <linux/of_address.h>
#include <linux/kthread.h>
+#include <linux/workqueue.h>
#include <uapi/linux/sched/types.h>
#include <drm/drm_of.h>
@@ -61,6 +62,9 @@
#define MSM_VERSION_MINOR 3
#define MSM_VERSION_PATCHLEVEL 0
+#define IDLE_ENCODER_MASK_DEFAULT 1
+#define IDLE_TIMEOUT_MS_DEFAULT 100
+
static DEFINE_MUTEX(msm_release_lock);
static void msm_fb_output_poll_changed(struct drm_device *dev)
@@ -697,6 +701,160 @@
return kms;
}
+static ssize_t idle_encoder_mask_store(struct device *device,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct drm_device *ddev = dev_get_drvdata(device);
+ struct msm_drm_private *priv = ddev->dev_private;
+ struct msm_idle *idle = &priv->idle;
+ u32 encoder_mask = 0;
+ int rc;
+ unsigned long flags;
+
+ rc = kstrtouint(buf, 0, &encoder_mask);
+ if (rc)
+ return rc;
+
+ spin_lock_irqsave(&idle->lock, flags);
+ idle->encoder_mask = encoder_mask;
+ idle->active_mask &= encoder_mask;
+ spin_unlock_irqrestore(&idle->lock, flags);
+
+ return count;
+}
+
+static ssize_t idle_encoder_mask_show(struct device *device,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct drm_device *ddev = dev_get_drvdata(device);
+ struct msm_drm_private *priv = ddev->dev_private;
+ struct msm_idle *idle = &priv->idle;
+
+ return snprintf(buf, PAGE_SIZE, "0x%x\n", idle->encoder_mask);
+}
+
+static ssize_t idle_timeout_ms_store(struct device *device,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct drm_device *ddev = dev_get_drvdata(device);
+ struct msm_drm_private *priv = ddev->dev_private;
+ struct msm_idle *idle = &priv->idle;
+ u32 timeout_ms = 0;
+ int rc;
+ unsigned long flags;
+
+ rc = kstrtouint(buf, 10, &timeout_ms);
+ if (rc)
+ return rc;
+
+ spin_lock_irqsave(&idle->lock, flags);
+ idle->timeout_ms = timeout_ms;
+ spin_unlock_irqrestore(&idle->lock, flags);
+
+ return count;
+}
+
+static ssize_t idle_timeout_ms_show(struct device *device,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct drm_device *ddev = dev_get_drvdata(device);
+ struct msm_drm_private *priv = ddev->dev_private;
+ struct msm_idle *idle = &priv->idle;
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", idle->timeout_ms);
+}
+
+static ssize_t idle_state_show(struct device *device,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct drm_device *ddev = dev_get_drvdata(device);
+ struct msm_drm_private *priv = ddev->dev_private;
+ struct msm_idle *idle = &priv->idle;
+ const char *state;
+ unsigned long flags;
+
+ spin_lock_irqsave(&idle->lock, flags);
+ if (idle->active_mask) {
+ state = "active";
+ spin_unlock_irqrestore(&idle->lock, flags);
+ return scnprintf(buf, PAGE_SIZE, "%s (0x%x)\n",
+ state, idle->active_mask);
+ } else if (delayed_work_pending(&idle->work))
+ state = "pending";
+ else
+ state = "idle";
+ spin_unlock_irqrestore(&idle->lock, flags);
+
+ return scnprintf(buf, PAGE_SIZE, "%s\n", state);
+}
+
+static DEVICE_ATTR_RW(idle_encoder_mask);
+static DEVICE_ATTR_RW(idle_timeout_ms);
+static DEVICE_ATTR_RO(idle_state);
+
+static const struct attribute *msm_idle_attrs[] = {
+ &dev_attr_idle_encoder_mask.attr,
+ &dev_attr_idle_timeout_ms.attr,
+ &dev_attr_idle_state.attr,
+ NULL
+};
+
+static void msm_idle_work(struct work_struct *work)
+{
+ struct delayed_work *dw = to_delayed_work(work);
+ struct msm_idle *idle = container_of(dw, struct msm_idle, work);
+ struct msm_drm_private *priv = container_of(idle,
+ struct msm_drm_private, idle);
+
+ if (!idle->active_mask)
+ sysfs_notify(&priv->dev->dev->kobj, NULL, "idle_state");
+}
+
+void msm_idle_set_state(struct drm_encoder *encoder, bool active)
+{
+ struct drm_device *ddev = encoder->dev;
+ struct msm_drm_private *priv = ddev->dev_private;
+ struct msm_idle *idle = &priv->idle;
+ unsigned int mask = 1 << drm_encoder_index(encoder);
+ unsigned long flags;
+
+ spin_lock_irqsave(&idle->lock, flags);
+ if (mask & idle->encoder_mask) {
+ if (active)
+ idle->active_mask |= mask;
+ else
+ idle->active_mask &= ~mask;
+
+ if (idle->timeout_ms && !idle->active_mask)
+ mod_delayed_work(system_wq, &idle->work,
+ msecs_to_jiffies(idle->timeout_ms));
+ else
+ cancel_delayed_work(&idle->work);
+ }
+ spin_unlock_irqrestore(&idle->lock, flags);
+}
+
+static void msm_idle_init(struct drm_device *ddev)
+{
+ struct msm_drm_private *priv = ddev->dev_private;
+ struct msm_idle *idle = &priv->idle;
+
+ if (sysfs_create_files(&ddev->dev->kobj, msm_idle_attrs) < 0)
+ pr_warn("failed to create idle state file");
+
+ idle->active_mask = 0;
+ idle->encoder_mask = IDLE_ENCODER_MASK_DEFAULT;
+ idle->timeout_ms = IDLE_TIMEOUT_MS_DEFAULT;
+
+ INIT_DELAYED_WORK(&idle->work, msm_idle_work);
+ spin_lock_init(&idle->lock);
+}
+
static int msm_drm_init(struct device *dev, struct drm_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
@@ -747,6 +905,8 @@
goto dbg_init_fail;
}
+ msm_idle_init(ddev);
+
/* Bind all our sub-components: */
ret = msm_component_bind_all(dev, ddev);
if (ret)
@@ -2066,13 +2226,13 @@
priv->shutdown_in_progress = true;
}
-static const struct of_device_id dt_match[] = {
+static const struct of_device_id dt_match_msm_drv[] = {
{ .compatible = "qcom,mdp4", .data = (void *)KMS_MDP4 },
{ .compatible = "qcom,mdss", .data = (void *)KMS_MDP5 },
{ .compatible = "qcom,sde-kms", .data = (void *)KMS_SDE },
{},
};
-MODULE_DEVICE_TABLE(of, dt_match);
+MODULE_DEVICE_TABLE(of, dt_match_msm_drv);
static struct platform_driver msm_platform_driver = {
.probe = msm_pdev_probe,
@@ -2080,7 +2240,7 @@
.shutdown = msm_pdev_shutdown,
.driver = {
.name = "msm_drm",
- .of_match_table = dt_match,
+ .of_match_table = dt_match_msm_drv,
.pm = &msm_pm_ops,
.suppress_bind_attrs = true,
},
@@ -2109,8 +2269,76 @@
msm_smmu_driver_cleanup();
}
+#ifdef CONFIG_DRM_MSM_MODULE
+static int __init msm_drm_module_init(void)
+{
+ int ret;
+
+ ret = sde_rsc_rpmh_register();
+ if (ret) {
+ pr_err("sde_rsc_rpmh register fails, error:%d\n", ret);
+ return ret;
+ }
+
+ ret = mdss_pll_driver_init();
+ if (ret) {
+ pr_err("mdss_pll_driver_init failed, error %d\n", ret);
+ return ret;
+ }
+
+ ret = sde_wb_register();
+ if (ret) {
+ pr_err("sde_wb register fails, error:%d\n", ret);
+ return ret;
+ }
+
+ ret = sde_rsc_register();
+ if (ret) {
+ pr_err("sde_rsc register fails, error: %d\n", ret);
+ return ret;
+ }
+
+ ret = dsi_display_register();
+ if (ret) {
+ pr_err("dsi_display register fails, error: %d\n", ret);
+ return ret;
+ }
+
+ ret = msm_drm_register();
+ if (ret) {
+ pr_err("msm_drm register fails, error: %d\n", ret);
+ return ret;
+ }
+
+ ret = msm_notifier_register();
+ if (ret) {
+ pr_err("msm_notifier register fails, error: %d\n", ret);
+ return ret;
+ }
+
+ ret = dp_display_init();
+ if (ret)
+ pr_err("dp_display init fails, error: %d\n", ret);
+
+ return ret;
+}
+
+static void __exit msm_drm_module_cleanup(void)
+{
+ dp_display_cleanup();
+ msm_notifier_unregister();
+ msm_drm_unregister();
+ dsi_display_unregister();
+ sde_rsc_unregister();
+ sde_wb_unregister();
+ mdss_pll_driver_deinit();
+}
+module_init(msm_drm_module_init);
+module_exit(msm_drm_module_cleanup);
+#else
module_init(msm_drm_register);
module_exit(msm_drm_unregister);
+#endif
MODULE_AUTHOR("Rob Clark <robdclark@gmail.com");
MODULE_DESCRIPTION("MSM DRM Driver");
diff --git a/msm/msm_drv.h b/msm/msm_drv.h
index 4ba732d..57bf491 100644
--- a/msm/msm_drv.h
+++ b/msm/msm_drv.h
@@ -591,6 +591,15 @@
struct kthread_worker worker;
};
+struct msm_idle {
+ u32 timeout_ms;
+ u32 encoder_mask;
+ u32 active_mask;
+
+ spinlock_t lock;
+ struct delayed_work work;
+};
+
struct msm_drm_private {
struct drm_device *dev;
@@ -701,6 +710,8 @@
/* update the flag when msm driver receives shutdown notification */
bool shutdown_in_progress;
+
+ struct msm_idle idle;
};
/* get struct msm_kms * from drm_device * */
@@ -716,6 +727,7 @@
void msm_atomic_commit_tail(struct drm_atomic_state *state);
int msm_atomic_commit(struct drm_device *dev,
struct drm_atomic_state *state, bool nonblock);
+int msm_drm_notifier_call_chain(unsigned long val, void *v);
/* callback from wq once fence has passed: */
struct msm_fence_cb {
@@ -967,6 +979,7 @@
}
#endif
+void msm_idle_set_state(struct drm_encoder *encoder, bool active);
#ifdef CONFIG_DEBUG_FS
void msm_gem_describe(struct drm_gem_object *obj, struct seq_file *m);
void msm_gem_describe_objects(struct list_head *list, struct seq_file *m);
@@ -1039,4 +1052,20 @@
const struct drm_display_mode *mode,
const struct msm_resource_caps_info *res, u32 *num_lm);
+#ifdef CONFIG_DRM_MSM_MODULE
+int dsi_display_register(void);
+void dsi_display_unregister(void);
+int dp_display_init(void);
+void dp_display_cleanup(void);
+int sde_rsc_rpmh_register(void);
+int sde_rsc_register(void);
+void sde_rsc_unregister(void);
+int sde_wb_register(void);
+void sde_wb_unregister(void);
+int mdss_pll_driver_init(void);
+void mdss_pll_driver_deinit(void);
+int msm_notifier_register(void);
+void msm_notifier_unregister(void);
+#endif
+
#endif /* __MSM_DRV_H__ */
diff --git a/msm/msm_kms.h b/msm/msm_kms.h
index d6ff517..de34cb6 100644
--- a/msm/msm_kms.h
+++ b/msm/msm_kms.h
@@ -42,6 +42,8 @@
#define MSM_MODE_FLAG_SEAMLESS_POMS (1<<4)
/* Request to switch the bit clk */
#define MSM_MODE_FLAG_SEAMLESS_DYN_CLK (1<<5)
+/* Request to switch the connector mode, fps only */
+#define MSM_MODE_FLAG_SEAMLESS_DMS_FPS (1<<6)
/* As there are different display controller blocks depending on the
* snapdragon version, the kms support is split out and the appropriate
@@ -208,6 +210,13 @@
: false;
}
+static inline
+bool msm_is_mode_seamless_dms_fps(const struct drm_display_mode *mode)
+{
+ return mode &&
+ (mode->private_flags & MSM_MODE_FLAG_SEAMLESS_DMS_FPS) != 0;
+}
+
static inline bool msm_is_mode_dynamic_fps(const struct drm_display_mode *mode)
{
return ((mode->flags & DRM_MODE_FLAG_SEAMLESS) &&
diff --git a/msm/msm_notifier.c b/msm/msm_notifier.c
index 20be9ba..ed528b0 100644
--- a/msm/msm_notifier.c
+++ b/msm/msm_notifier.c
@@ -11,6 +11,7 @@
#include <linux/sched.h>
#include <drm/drm_panel.h>
+#include "msm_drv.h"
#include "sde_dbg.h"
struct msm_display_fps_info {
@@ -183,32 +184,34 @@
return ret;
}
-static const struct of_device_id dt_match[] = {
+static const struct of_device_id dt_match_msm_notifier[] = {
{ .compatible = "qcom,msm-notifier"},
{},
};
-MODULE_DEVICE_TABLE(of, dt_match);
+MODULE_DEVICE_TABLE(of, dt_match_msm_notifier);
static struct platform_driver msm_notifier_platform_driver = {
.probe = msm_notifier_probe,
.remove = msm_notifier_remove,
.driver = {
.name = "msm_notifier",
- .of_match_table = dt_match,
+ .of_match_table = dt_match_msm_notifier,
.suppress_bind_attrs = true,
},
};
-static int __init msm_notifier_register(void)
+int __init msm_notifier_register(void)
{
return platform_driver_register(&msm_notifier_platform_driver);
}
-static void __exit msm_notifier_unregister(void)
+void __exit msm_notifier_unregister(void)
{
platform_driver_unregister(&msm_notifier_platform_driver);
}
+#ifndef CONFIG_DRM_MSM_MODULE
late_initcall(msm_notifier_register);
module_exit(msm_notifier_unregister);
+#endif
diff --git a/msm/sde/sde_connector.c b/msm/sde/sde_connector.c
index 35f6783..166a52c 100644
--- a/msm/sde/sde_connector.c
+++ b/msm/sde/sde_connector.c
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
+ * Copyright (c) 2021-2022 Qualcomm Innovation Center, Inc. All rights reserved.
* Copyright (c) 2016-2020, The Linux Foundation. All rights reserved.
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
*/
@@ -18,7 +19,6 @@
#include "sde_crtc.h"
#include "sde_rm.h"
-#define BL_NODE_NAME_SIZE 32
#define HDR10_PLUS_VSIF_TYPE_CODE 0x81
/* Autorefresh will occur after FRAME_CNT frames. Large values are unlikely */
@@ -29,9 +29,6 @@
#define SDE_ERROR_CONN(c, fmt, ...) SDE_ERROR("conn%d " fmt,\
(c) ? (c)->base.base.id : -1, ##__VA_ARGS__)
-static u32 dither_matrix[DITHER_MATRIX_SZ] = {
- 15, 7, 13, 5, 3, 11, 1, 9, 12, 4, 14, 6, 0, 8, 2, 10
-};
static const struct drm_prop_enum_list e_topology_name[] = {
{SDE_RM_TOPOLOGY_NONE, "sde_none"},
@@ -67,104 +64,6 @@
{FRAME_DONE_WAIT_POSTED_START, "posted_start"},
};
-static int sde_backlight_device_update_status(struct backlight_device *bd)
-{
- int brightness;
- struct dsi_display *display;
- struct sde_connector *c_conn;
- int bl_lvl;
- struct drm_event event;
- int rc = 0;
-
- brightness = bd->props.brightness;
-
- if ((bd->props.power != FB_BLANK_UNBLANK) ||
- (bd->props.state & BL_CORE_FBBLANK) ||
- (bd->props.state & BL_CORE_SUSPENDED))
- brightness = 0;
-
- c_conn = bl_get_data(bd);
- display = (struct dsi_display *) c_conn->display;
- if (brightness > display->panel->bl_config.bl_max_level)
- brightness = display->panel->bl_config.bl_max_level;
-
- /* map UI brightness into driver backlight level with rounding */
- bl_lvl = mult_frac(brightness, display->panel->bl_config.bl_max_level,
- display->panel->bl_config.brightness_max_level);
-
- if (!bl_lvl && brightness)
- bl_lvl = 1;
-
- if (!c_conn->allow_bl_update) {
- c_conn->unset_bl_level = bl_lvl;
- return 0;
- }
-
- if (c_conn->ops.set_backlight) {
- /* skip notifying user space if bl is 0 */
- if (brightness != 0) {
- event.type = DRM_EVENT_SYS_BACKLIGHT;
- event.length = sizeof(u32);
- msm_mode_object_event_notify(&c_conn->base.base,
- c_conn->base.dev, &event, (u8 *)&brightness);
- }
- rc = c_conn->ops.set_backlight(&c_conn->base,
- c_conn->display, bl_lvl);
- c_conn->unset_bl_level = 0;
- }
-
- return rc;
-}
-
-static int sde_backlight_device_get_brightness(struct backlight_device *bd)
-{
- return 0;
-}
-
-static const struct backlight_ops sde_backlight_device_ops = {
- .update_status = sde_backlight_device_update_status,
- .get_brightness = sde_backlight_device_get_brightness,
-};
-
-static int sde_backlight_setup(struct sde_connector *c_conn,
- struct drm_device *dev)
-{
- struct backlight_properties props;
- struct dsi_display *display;
- struct dsi_backlight_config *bl_config;
- static int display_count;
- char bl_node_name[BL_NODE_NAME_SIZE];
-
- if (!c_conn || !dev || !dev->dev) {
- SDE_ERROR("invalid param\n");
- return -EINVAL;
- } else if (c_conn->connector_type != DRM_MODE_CONNECTOR_DSI) {
- return 0;
- }
-
- memset(&props, 0, sizeof(props));
- props.type = BACKLIGHT_RAW;
- props.power = FB_BLANK_UNBLANK;
-
- display = (struct dsi_display *) c_conn->display;
- bl_config = &display->panel->bl_config;
- props.max_brightness = bl_config->brightness_max_level;
- props.brightness = bl_config->brightness_max_level;
- snprintf(bl_node_name, BL_NODE_NAME_SIZE, "panel%u-backlight",
- display_count);
- c_conn->bl_device = backlight_device_register(bl_node_name, dev->dev,
- c_conn, &sde_backlight_device_ops, &props);
- if (IS_ERR_OR_NULL(c_conn->bl_device)) {
- SDE_ERROR("Failed to register backlight: %ld\n",
- PTR_ERR(c_conn->bl_device));
- c_conn->bl_device = NULL;
- return -ENODEV;
- }
- display_count++;
-
- return 0;
-}
-
int sde_connector_trigger_event(void *drm_connector,
uint32_t event_idx, uint32_t instance_idx,
uint32_t data0, uint32_t data1,
@@ -242,60 +141,12 @@
(void)sde_connector_register_event(connector, event_idx, 0, 0);
}
-static int _sde_connector_get_default_dither_cfg_v1(
- struct sde_connector *c_conn, void *cfg)
-{
- struct drm_msm_dither *dither_cfg = (struct drm_msm_dither *)cfg;
- enum dsi_pixel_format dst_format = DSI_PIXEL_FORMAT_MAX;
-
- if (!c_conn || !cfg) {
- SDE_ERROR("invalid argument(s), c_conn %pK, cfg %pK\n",
- c_conn, cfg);
- return -EINVAL;
- }
-
- if (!c_conn->ops.get_dst_format) {
- SDE_DEBUG("get_dst_format is unavailable\n");
- return 0;
- }
-
- dst_format = c_conn->ops.get_dst_format(&c_conn->base, c_conn->display);
- switch (dst_format) {
- case DSI_PIXEL_FORMAT_RGB888:
- dither_cfg->c0_bitdepth = 8;
- dither_cfg->c1_bitdepth = 8;
- dither_cfg->c2_bitdepth = 8;
- dither_cfg->c3_bitdepth = 8;
- break;
- case DSI_PIXEL_FORMAT_RGB666:
- case DSI_PIXEL_FORMAT_RGB666_LOOSE:
- dither_cfg->c0_bitdepth = 6;
- dither_cfg->c1_bitdepth = 6;
- dither_cfg->c2_bitdepth = 6;
- dither_cfg->c3_bitdepth = 6;
- break;
- default:
- SDE_DEBUG("no default dither config for dst_format %d\n",
- dst_format);
- return -ENODATA;
- }
-
- memcpy(&dither_cfg->matrix, dither_matrix,
- sizeof(u32) * DITHER_MATRIX_SZ);
- dither_cfg->temporal_en = 0;
- return 0;
-}
-
static void _sde_connector_install_dither_property(struct drm_device *dev,
struct sde_kms *sde_kms, struct sde_connector *c_conn)
{
char prop_name[DRM_PROP_NAME_LEN];
struct sde_mdss_cfg *catalog = NULL;
- struct drm_property_blob *blob_ptr;
- void *cfg;
- int ret = 0;
- u32 version = 0, len = 0;
- bool defalut_dither_needed = false;
+ u32 version = 0;
if (!dev || !sde_kms || !c_conn) {
SDE_ERROR("invld args (s), dev %pK, sde_kms %pK, c_conn %pK\n",
@@ -313,54 +164,48 @@
msm_property_install_blob(&c_conn->property_info, prop_name,
DRM_MODE_PROP_BLOB,
CONNECTOR_PROP_PP_DITHER);
- len = sizeof(struct drm_msm_dither);
- cfg = kzalloc(len, GFP_KERNEL);
- if (!cfg)
- return;
-
- ret = _sde_connector_get_default_dither_cfg_v1(c_conn, cfg);
- if (!ret)
- defalut_dither_needed = true;
break;
default:
SDE_ERROR("unsupported dither version %d\n", version);
return;
}
-
- if (defalut_dither_needed) {
- blob_ptr = drm_property_create_blob(dev, len, cfg);
- if (IS_ERR_OR_NULL(blob_ptr))
- goto exit;
- c_conn->blob_dither = blob_ptr;
- }
-exit:
- kfree(cfg);
}
int sde_connector_get_dither_cfg(struct drm_connector *conn,
struct drm_connector_state *state, void **cfg,
- size_t *len)
+ size_t *len, bool idle_pc)
{
struct sde_connector *c_conn = NULL;
struct sde_connector_state *c_state = NULL;
size_t dither_sz = 0;
+ bool is_dirty;
u32 *p = (u32 *)cfg;
- if (!conn || !state || !p)
+ if (!conn || !state || !p) {
+ SDE_ERROR("invalid arguments\n");
return -EINVAL;
+ }
c_conn = to_sde_connector(conn);
c_state = to_sde_connector_state(state);
- /* try to get user config data first */
- *cfg = msm_property_get_blob(&c_conn->property_info,
- &c_state->property_state,
- &dither_sz,
- CONNECTOR_PROP_PP_DITHER);
- /* if user config data doesn't exist, use default dither blob */
- if (*cfg == NULL && c_conn->blob_dither) {
- *cfg = c_conn->blob_dither->data;
- dither_sz = c_conn->blob_dither->length;
+ is_dirty = msm_property_is_dirty(&c_conn->property_info,
+ &c_state->property_state,
+ CONNECTOR_PROP_PP_DITHER);
+
+ if (!is_dirty && !idle_pc) {
+ return -ENODATA;
+ } else if (is_dirty || idle_pc) {
+ *cfg = msm_property_get_blob(&c_conn->property_info,
+ &c_state->property_state,
+ &dither_sz,
+ CONNECTOR_PROP_PP_DITHER);
+ /* in idle_pc use case return early, when dither is already disabled */
+ if (idle_pc && *cfg == NULL)
+ return -ENODATA;
+ /* disable dither based on user config data */
+ else if (*cfg == NULL)
+ return 0;
}
*len = dither_sz;
return 0;
@@ -588,25 +433,16 @@
bl_config = &dsi_display->panel->bl_config;
- if (!c_conn->allow_bl_update) {
- c_conn->unset_bl_level = bl_config->bl_level;
- return 0;
- }
-
- if (c_conn->unset_bl_level)
- bl_config->bl_level = c_conn->unset_bl_level;
-
bl_config->bl_scale = c_conn->bl_scale > MAX_BL_SCALE_LEVEL ?
MAX_BL_SCALE_LEVEL : c_conn->bl_scale;
bl_config->bl_scale_sv = c_conn->bl_scale_sv > MAX_SV_BL_SCALE_LEVEL ?
MAX_SV_BL_SCALE_LEVEL : c_conn->bl_scale_sv;
- SDE_DEBUG("bl_scale = %u, bl_scale_sv = %u, bl_level = %u\n",
+ SDE_DEBUG("bl_scale = %u, bl_scale_sv = %u, bl_actual = %u\n",
bl_config->bl_scale, bl_config->bl_scale_sv,
- bl_config->bl_level);
- rc = c_conn->ops.set_backlight(&c_conn->base,
- dsi_display, bl_config->bl_level);
- c_conn->unset_bl_level = 0;
+ bl_config->bl_actual);
+ if (bl_config->bl_device)
+ backlight_update_status(bl_config->bl_device);
return rc;
}
@@ -695,6 +531,8 @@
{
struct sde_connector *c_conn;
struct sde_connector_state *c_state;
+ struct dsi_display *dsi_display = NULL;
+ struct dsi_backlight_config *bl_config = NULL;
int idx;
if (!connector) {
@@ -705,6 +543,13 @@
c_conn = to_sde_connector(connector);
c_state = to_sde_connector_state(connector->state);
+ if (c_conn->connector_type == DRM_MODE_CONNECTOR_DSI) {
+ dsi_display = c_conn->display;
+
+ if (dsi_display && dsi_display->panel)
+ bl_config = &dsi_display->panel->bl_config;
+ }
+
mutex_lock(&c_conn->property_info.property_lock);
while ((idx = msm_property_pop_dirty(&c_conn->property_info,
&c_state->property_state)) >= 0) {
@@ -740,7 +585,8 @@
* Special handling for postproc properties and
* for updating backlight if any unset backlight level is present
*/
- if (c_conn->bl_scale_dirty || c_conn->unset_bl_level) {
+ if (c_conn->bl_scale_dirty || (bl_config &&
+ bl_config->bl_update_pending == true)) {
_sde_connector_update_bl_scale(c_conn);
c_conn->bl_scale_dirty = false;
}
@@ -877,25 +723,47 @@
/* Disable ESD thread */
sde_connector_schedule_status_work(connector, false);
- if (c_conn->bl_device) {
- c_conn->bl_device->props.power = FB_BLANK_POWERDOWN;
- c_conn->bl_device->props.state |= BL_CORE_FBBLANK;
- backlight_update_status(c_conn->bl_device);
- }
+ c_conn = to_sde_connector(connector);
- c_conn->allow_bl_update = false;
+ c_conn->last_panel_power_mode = SDE_MODE_DPMS_OFF;
}
-void sde_connector_helper_bridge_enable(struct drm_connector *connector)
+void sde_connector_helper_bridge_pre_enable(struct drm_connector *connector)
{
struct sde_connector *c_conn = NULL;
- struct dsi_display *display;
if (!connector)
return;
c_conn = to_sde_connector(connector);
- display = (struct dsi_display *) c_conn->display;
+
+ c_conn->last_panel_power_mode = SDE_MODE_DPMS_ON;
+}
+
+void sde_connector_helper_bridge_post_enable(struct drm_connector *connector)
+{
+ struct sde_connector *c_conn = NULL;
+ struct dsi_display *dsi_display = NULL;
+ struct dsi_backlight_config *bl_config = NULL;
+
+ if (!connector)
+ return;
+
+ c_conn = to_sde_connector(connector);
+
+ if (c_conn->connector_type == DRM_MODE_CONNECTOR_DSI) {
+ dsi_display = c_conn->display;
+
+ if (dsi_display && dsi_display->panel)
+ bl_config = &dsi_display->panel->bl_config;
+ }
+
+ c_conn->panel_dead = false;
+
+ if (!bl_config) {
+ SDE_ERROR("Invalid params bl_config null\n");
+ return;
+ }
/*
* Special handling for some panels which need atleast
@@ -903,18 +771,15 @@
* So delay backlight update to these panels until the
* first frame commit is received from the HW.
*/
- if (display->panel->bl_config.bl_update ==
- BL_UPDATE_DELAY_UNTIL_FIRST_FRAME)
- sde_encoder_wait_for_event(c_conn->encoder,
- MSM_ENC_TX_COMPLETE);
- c_conn->allow_bl_update = true;
-
- if (c_conn->bl_device) {
- c_conn->bl_device->props.power = FB_BLANK_UNBLANK;
- c_conn->bl_device->props.state &= ~BL_CORE_FBBLANK;
- backlight_update_status(c_conn->bl_device);
+ if (!bl_config->allow_bl_update) {
+ if (bl_config->bl_update == BL_UPDATE_DELAY_UNTIL_FIRST_FRAME)
+ sde_encoder_wait_for_event(c_conn->encoder,
+ MSM_ENC_TX_COMPLETE);
+ bl_config->allow_bl_update = true;
}
- c_conn->panel_dead = false;
+
+ if (bl_config->bl_device)
+ backlight_update_status(bl_config->bl_device);
}
int sde_connector_clk_ctrl(struct drm_connector *connector, bool enable)
@@ -967,8 +832,6 @@
if (c_conn->blob_ext_hdr)
drm_property_blob_put(c_conn->blob_ext_hdr);
- if (c_conn->bl_device)
- backlight_device_unregister(c_conn->bl_device);
drm_connector_unregister(connector);
mutex_destroy(&c_conn->lock);
sde_fence_deinit(c_conn->retire_fence);
@@ -1787,6 +1650,18 @@
return vfp;
}
+void sde_connector_set_idle_hint(struct drm_connector *connector, bool is_idle)
+{
+ struct sde_connector *c_conn;
+
+ if (unlikely(!connector))
+ return;
+
+ c_conn = to_sde_connector(connector);
+ if (c_conn->ops.set_idle_hint)
+ c_conn->ops.set_idle_hint(c_conn->display, is_idle);
+}
+
static int _sde_debugfs_conn_cmd_tx_open(struct inode *inode, struct file *file)
{
/* non-seekable */
@@ -2661,12 +2536,6 @@
goto error_cleanup_fence;
}
- rc = sde_backlight_setup(c_conn, dev);
- if (rc) {
- SDE_ERROR("failed to setup backlight, rc=%d\n", rc);
- goto error_cleanup_fence;
- }
-
/* create properties */
msm_property_init(&c_conn->property_info, &c_conn->base.base, dev,
priv->conn_property, c_conn->property_data,
diff --git a/msm/sde/sde_connector.h b/msm/sde/sde_connector.h
index cbc6bfa..dba3439 100644
--- a/msm/sde/sde_connector.h
+++ b/msm/sde/sde_connector.h
@@ -160,15 +160,6 @@
uint32_t event_idx, bool enable, void *display);
/**
- * set_backlight - set backlight level
- * @connector: Pointer to drm connector structure
- * @display: Pointer to private display structure
- * @bl_lvel: Backlight level
- */
- int (*set_backlight)(struct drm_connector *connector,
- void *display, u32 bl_lvl);
-
- /**
* set_colorspace - set colorspace for connector
* @connector: Pointer to drm connector structure
* @display: Pointer to private display structure
@@ -340,6 +331,13 @@
struct msm_display_conn_params *params);
/**
+ * set_idle_hint - gives hint to display whether display is idle
+ * @display: Pointer to private display handle
+ * @is_idle: true if display is idle, false otherwise
+ */
+ void (*set_idle_hint)(void *display, bool is_idle);
+
+ /**
* get_qsync_min_fps - Get qsync min fps from qsync-min-fps-list
* @display: Pointer to private display structure
* @mode_fps: Fps value in dfps list
@@ -476,8 +474,6 @@
bool bl_scale_dirty;
u32 bl_scale;
u32 bl_scale_sv;
- u32 unset_bl_level;
- bool allow_bl_update;
u32 qsync_mode;
bool qsync_updated;
@@ -862,10 +858,12 @@
* @state: Pointer to drm_connector_state struct
* @cfg: Pointer to pointer to dither cfg
* @len: length of the dither data
+ * @idle_pc: flag to indicate idle_pc_restore happened
* Returns: Zero on success
*/
int sde_connector_get_dither_cfg(struct drm_connector *conn,
- struct drm_connector_state *state, void **cfg, size_t *len);
+ struct drm_connector_state *state, void **cfg,
+ size_t *len, bool idle_pc);
/**
* sde_connector_set_blob_data - set connector blob property data
@@ -951,10 +949,18 @@
int sde_connector_event_notify(struct drm_connector *connector, uint32_t type,
uint32_t len, uint32_t val);
/**
- * sde_connector_helper_bridge_enable - helper function for drm bridge enable
+ * sde_connector_helper_bridge_pre_enable - helper function for drm bridge
+ * pre enable
* @connector: Pointer to DRM connector object
*/
-void sde_connector_helper_bridge_enable(struct drm_connector *connector);
+void sde_connector_helper_bridge_pre_enable(struct drm_connector *connector);
+
+/**
+ * sde_connector_helper_bridge_post_enable - helper function for drm bridge
+ * post enable
+ * @connector: Pointer to DRM connector object
+ */
+void sde_connector_helper_bridge_post_enable(struct drm_connector *connector);
/**
* sde_connector_get_panel_vfp - helper to get panel vfp
@@ -965,6 +971,14 @@
*/
int sde_connector_get_panel_vfp(struct drm_connector *connector,
struct drm_display_mode *mode);
+
+/**
+ * sde_connector_set_idle_hint - helper to give idle hint to connector
+ * @connector: pointer to drm connector
+ * @is_idle: true on idle, false on wake up from idle
+ */
+void sde_connector_set_idle_hint(struct drm_connector *connector, bool is_idle);
+
/**
* sde_connector_esd_status - helper function to check te status
* @connector: Pointer to DRM connector object
diff --git a/msm/sde/sde_crtc.c b/msm/sde/sde_crtc.c
index 602476b..889980f 100644
--- a/msm/sde/sde_crtc.c
+++ b/msm/sde/sde_crtc.c
@@ -1,4 +1,5 @@
/*
+ * Copyright (c) 2021-2022 Qualcomm Innovation Center, Inc. All rights reserved.
* Copyright (c) 2014-2021 The Linux Foundation. All rights reserved.
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
* Copyright (C) 2013 Red Hat
@@ -215,6 +216,54 @@
}
#endif
+static ssize_t early_wakeup_store(struct device *device,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct drm_crtc *crtc;
+ struct sde_crtc *sde_crtc;
+ struct msm_drm_private *priv;
+ u32 crtc_id;
+ bool trigger;
+
+ if (!device || !buf || !count) {
+ SDE_ERROR("invalid input param(s)\n");
+ return -EINVAL;
+ }
+
+ if (kstrtobool(buf, &trigger) < 0)
+ return -EINVAL;
+
+ if (!trigger)
+ return count;
+
+ crtc = dev_get_drvdata(device);
+ if (!crtc || !crtc->dev || !crtc->dev->dev_private) {
+ SDE_ERROR("invalid crtc\n");
+ return -EINVAL;
+ }
+
+ sde_crtc = to_sde_crtc(crtc);
+ priv = crtc->dev->dev_private;
+
+ crtc_id = drm_crtc_index(crtc);
+ if (crtc_id >= ARRAY_SIZE(priv->disp_thread)) {
+ SDE_ERROR("invalid crtc index[%d]\n", crtc_id);
+ return -EINVAL;
+ }
+
+ kthread_queue_work(&priv->disp_thread[crtc_id].worker,
+ &sde_crtc->early_wakeup_work);
+
+ return count;
+}
+
+static ssize_t early_wakeup_show(struct device *device,
+ struct device_attribute *attr, char *buf)
+{
+
+ return 0;
+}
+
static ssize_t fps_periodicity_ms_store(struct device *device,
struct device_attribute *attr, const char *buf, size_t count)
{
@@ -401,12 +450,14 @@
static DEVICE_ATTR_RO(vsync_event);
static DEVICE_ATTR_RO(measured_fps);
static DEVICE_ATTR_RW(fps_periodicity_ms);
+static DEVICE_ATTR_RW(early_wakeup);
static DEVICE_ATTR_RO(retire_frame_event);
static struct attribute *sde_crtc_dev_attrs[] = {
&dev_attr_vsync_event.attr,
&dev_attr_measured_fps.attr,
&dev_attr_fps_periodicity_ms.attr,
+ &dev_attr_early_wakeup.attr,
&dev_attr_retire_frame_event.attr,
NULL
};
@@ -445,6 +496,7 @@
_sde_crtc_deinit_events(sde_crtc);
drm_crtc_cleanup(crtc);
+ mutex_destroy(&sde_crtc->vblank_modeset_ctrl_lock);
mutex_destroy(&sde_crtc->crtc_lock);
kfree(sde_crtc);
}
@@ -5770,14 +5822,15 @@
sde_crtc->vblank_cb_count * 1000, diff_ms) : 0;
seq_printf(s,
- "vblank fps:%lld count:%u total:%llums total_framecount:%llu\n",
+ "vblank fps:%lld count:%u total:%llums\n",
fps, sde_crtc->vblank_cb_count,
- ktime_to_ms(diff), sde_crtc->play_count);
+ ktime_to_ms(diff));
/* reset time & count for next measurement */
sde_crtc->vblank_cb_count = 0;
sde_crtc->vblank_cb_time = ktime_set(0, 0);
}
+ seq_printf(s, "total_framecount:%llu\n", sde_crtc->play_count);
mutex_unlock(&sde_crtc->crtc_lock);
@@ -6262,6 +6315,40 @@
}
}
+/*
+ * __sde_crtc_early_wakeup_work - trigger early wakeup from user space
+ */
+static void __sde_crtc_early_wakeup_work(struct kthread_work *work)
+{
+ struct sde_crtc *sde_crtc = container_of(work, struct sde_crtc,
+ early_wakeup_work);
+ struct drm_crtc *crtc;
+ struct drm_device *dev;
+ struct msm_drm_private *priv;
+ struct sde_kms *sde_kms;
+
+ if (!sde_crtc) {
+ SDE_ERROR("invalid sde crtc\n");
+ return;
+ }
+
+ if (!sde_crtc->enabled) {
+ SDE_INFO("sde crtc is not enabled\n");
+ return;
+ }
+
+ crtc = &sde_crtc->base;
+ dev = crtc->dev;
+ if (!dev) {
+ SDE_ERROR("invalid drm device\n");
+ return;
+ }
+
+ priv = dev->dev_private;
+ sde_kms = to_sde_kms(priv->kms);
+ sde_kms_trigger_early_wakeup(sde_kms, crtc);
+}
+
/* initialize crtc */
struct drm_crtc *sde_crtc_init(struct drm_device *dev, struct drm_plane *plane)
{
@@ -6353,6 +6440,8 @@
kthread_init_delayed_work(&sde_crtc->idle_notify_work,
__sde_crtc_idle_notify_work);
+ kthread_init_work(&sde_crtc->early_wakeup_work,
+ __sde_crtc_early_wakeup_work);
SDE_DEBUG("crtc=%d new_llcc=%d, old_llcc=%d\n",
crtc->base.id,
diff --git a/msm/sde/sde_crtc.h b/msm/sde/sde_crtc.h
index bd16cd3..9d05268 100644
--- a/msm/sde/sde_crtc.h
+++ b/msm/sde/sde_crtc.h
@@ -272,6 +272,7 @@
* @misr_frame_count : misr frame count provided by client
* @misr_data : store misr data before turning off the clocks.
* @idle_notify_work: delayed worker to notify idle timeout to user space
+ * @early_wakeup_work: work to trigger early wakeup
* @power_event : registered power event handle
* @cur_perf : current performance committed to clock/bandwidth driver
* @plane_mask_old: keeps track of the planes used in the previous commit
@@ -349,6 +350,7 @@
bool misr_enable_debugfs;
u32 misr_frame_count;
struct kthread_delayed_work idle_notify_work;
+ struct kthread_work early_wakeup_work;
struct sde_power_event *power_event;
diff --git a/msm/sde/sde_encoder.c b/msm/sde/sde_encoder.c
index 71bbeaf..036ac50 100644
--- a/msm/sde/sde_encoder.c
+++ b/msm/sde/sde_encoder.c
@@ -1100,6 +1100,11 @@
return ret;
}
+static inline bool is_lp_mode(int mode)
+{
+ return mode == SDE_MODE_DPMS_LP1 || mode == SDE_MODE_DPMS_LP2;
+}
+
static int sde_encoder_virt_atomic_check(
struct drm_encoder *drm_enc, struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
@@ -1141,6 +1146,18 @@
if (ret)
return ret;
+ if (msm_is_mode_seamless_dms(adj_mode)) {
+ const int prop_lp_mode = sde_connector_get_property(conn_state,
+ CONNECTOR_PROP_LP);
+
+ if (is_lp_mode(sde_conn->lp_mode) || is_lp_mode(prop_lp_mode)) {
+ SDE_ERROR_ENC(sde_enc,
+ "no DMS under LP mode: connector %d, prop %d\n",
+ sde_conn->lp_mode, prop_lp_mode);
+ return -EINVAL;
+ }
+ }
+
ret = _sde_encoder_atomic_check_pu_roi(sde_enc, crtc_state,
conn_state, sde_conn_state, sde_crtc_state);
if (ret)
@@ -2370,6 +2387,20 @@
sw_event);
}
+static void sde_encoder_idle_hint(const struct drm_encoder *drm_enc,
+ bool is_idle)
+{
+ const struct sde_encoder_virt *sde_enc = to_sde_encoder_virt(drm_enc);
+
+ SDE_ATRACE_BEGIN(__func__);
+ if (sde_enc->cur_master) {
+ struct drm_connector *conn = sde_enc->cur_master->connector;
+
+ sde_connector_set_idle_hint(conn, is_idle);
+ }
+ SDE_ATRACE_END(__func__);
+}
+
static int _sde_encoder_rc_kickoff(struct drm_encoder *drm_enc,
u32 sw_event, struct sde_encoder_virt *sde_enc, bool is_vid_mode)
{
@@ -2383,6 +2414,8 @@
/* cancel delayed off work, if any */
_sde_encoder_rc_cancel_delayed(sde_enc, sw_event);
+ msm_idle_set_state(drm_enc, true);
+
mutex_lock(&sde_enc->rc_lock);
/* return if the resource control is already in ON state */
@@ -2417,6 +2450,9 @@
SDE_EVTLOG_ERROR);
goto end;
}
+
+ sde_encoder_idle_hint(drm_enc, false);
+
_sde_encoder_update_rsc_client(drm_enc, true);
}
SDE_EVT32(DRMID(drm_enc), sw_event, sde_enc->rc_state,
@@ -2492,6 +2528,8 @@
else
idle_pc_duration = IDLE_POWERCOLLAPSE_DURATION;
+ msm_idle_set_state(drm_enc, false);
+
if (!autorefresh_enabled)
kthread_mod_delayed_work(
&disp_thread->worker,
@@ -2719,6 +2757,8 @@
_sde_encoder_irq_control(drm_enc, false);
sde_kms_update_pm_qos_irq_request(sde_kms, false, false);
} else {
+ sde_encoder_idle_hint(drm_enc, true);
+
/* disable all the clks and resources */
_sde_encoder_update_rsc_client(drm_enc, false);
_sde_encoder_resource_control_helper(drm_enc, false);
@@ -2790,6 +2830,8 @@
goto end;
}
+ sde_encoder_idle_hint(drm_enc, false);
+
_sde_encoder_update_rsc_client(drm_enc, true);
/*
@@ -2988,9 +3030,11 @@
if (msm_is_mode_seamless_dms(adj_mode) ||
(msm_is_mode_seamless_dyn_clk(adj_mode) &&
is_cmd_mode)) {
+ SDE_ATRACE_BEGIN("pre_modeset");
/* restore resource state before releasing them */
ret = sde_encoder_resource_control(drm_enc,
SDE_ENC_RC_EVENT_PRE_MODESET);
+ SDE_ATRACE_END("pre_modeset");
if (ret) {
SDE_ERROR_ENC(sde_enc,
"sde resource control failed: %d\n",
@@ -3000,9 +3044,11 @@
/*
* Disable dsc before switch the mode and after pre_modeset,
- * to guarantee that previous kickoff finished.
+ * to guarantee that previous kickoff finished. No need to
+ * disable if there's only change in refresh rate
*/
- _sde_encoder_dsc_disable(sde_enc);
+ if (!msm_is_mode_seamless_dms_fps(adj_mode))
+ _sde_encoder_dsc_disable(sde_enc);
} else if (msm_is_mode_seamless_poms(adj_mode)) {
_sde_encoder_modeset_helper_locked(drm_enc,
SDE_ENC_RC_EVENT_PRE_MODESET);
@@ -3083,9 +3129,12 @@
/* update resources after seamless mode change */
if (msm_is_mode_seamless_dms(adj_mode) ||
(msm_is_mode_seamless_dyn_clk(adj_mode) &&
- is_cmd_mode))
+ is_cmd_mode)) {
+ SDE_ATRACE_BEGIN("post_modeset");
sde_encoder_resource_control(&sde_enc->base,
SDE_ENC_RC_EVENT_POST_MODESET);
+ SDE_ATRACE_END("post_modeset");
+ }
else if (msm_is_mode_seamless_poms(adj_mode))
_sde_encoder_modeset_helper_locked(drm_enc,
SDE_ENC_RC_EVENT_POST_MODESET);
@@ -3292,6 +3341,57 @@
memset(&sde_enc->cur_conn_roi, 0, sizeof(sde_enc->cur_conn_roi));
}
+static void _sde_encoder_setup_dither(struct sde_encoder_phys *phys)
+{
+ void *dither_cfg = NULL;
+ int ret = 0, i = 0;
+ size_t len = 0;
+ enum sde_rm_topology_name topology;
+ struct drm_encoder *drm_enc;
+ struct msm_display_dsc_info *dsc = NULL;
+ struct sde_encoder_virt *sde_enc;
+ struct sde_hw_pingpong *hw_pp;
+
+ if (!phys || !phys->connector || !phys->hw_pp ||
+ !phys->hw_pp->ops.setup_dither || !phys->parent)
+ return;
+
+ topology = sde_connector_get_topology_name(phys->connector);
+ if ((topology == SDE_RM_TOPOLOGY_PPSPLIT) &&
+ (phys->split_role == ENC_ROLE_SLAVE))
+ return;
+
+ drm_enc = phys->parent;
+ sde_enc = to_sde_encoder_virt(drm_enc);
+ dsc = &sde_enc->mode_info.comp_info.dsc_info;
+
+ /* disable dither for 10 bpp or 10bpc dsc config */
+ if (dsc->bpp == 10 || dsc->bpc == 10) {
+ phys->hw_pp->ops.setup_dither(phys->hw_pp, NULL, 0);
+ return;
+ }
+
+ ret = sde_connector_get_dither_cfg(phys->connector,
+ phys->connector->state, &dither_cfg,
+ &len, sde_enc->idle_pc_restore);
+
+ /* skip reg writes when return values are invalid or no data */
+ if (ret && ret == -ENODATA)
+ return;
+
+ if (TOPOLOGY_DUALPIPE_MERGE_MODE(topology)) {
+ for (i = 0; i < MAX_CHANNELS_PER_ENC; i++) {
+ hw_pp = sde_enc->hw_pp[i];
+ phys->hw_pp->ops.setup_dither(hw_pp,
+ dither_cfg,len);
+ }
+
+ } else {
+ phys->hw_pp->ops.setup_dither(phys->hw_pp,
+ dither_cfg, len);
+ }
+}
+
void sde_encoder_virt_restore(struct drm_encoder *drm_enc)
{
struct sde_encoder_virt *sde_enc = NULL;
@@ -3324,6 +3424,8 @@
if ((phys != sde_enc->cur_master) && phys->ops.restore)
phys->ops.restore(phys);
+
+ _sde_encoder_setup_dither(phys);
}
if (sde_enc->cur_master->ops.restore)
@@ -4467,53 +4569,6 @@
sde_enc->idle_pc_restore = false;
}
-static void _sde_encoder_setup_dither(struct sde_encoder_phys *phys)
-{
- void *dither_cfg;
- int ret = 0, i = 0;
- size_t len = 0;
- enum sde_rm_topology_name topology;
- struct drm_encoder *drm_enc;
- struct msm_display_dsc_info *dsc = NULL;
- struct sde_encoder_virt *sde_enc;
- struct sde_hw_pingpong *hw_pp;
-
- if (!phys || !phys->connector || !phys->hw_pp ||
- !phys->hw_pp->ops.setup_dither || !phys->parent)
- return;
-
- topology = sde_connector_get_topology_name(phys->connector);
- if ((topology == SDE_RM_TOPOLOGY_PPSPLIT) &&
- (phys->split_role == ENC_ROLE_SLAVE))
- return;
-
- drm_enc = phys->parent;
- sde_enc = to_sde_encoder_virt(drm_enc);
- dsc = &sde_enc->mode_info.comp_info.dsc_info;
- /* disable dither for 10 bpp or 10bpc dsc config */
- if (dsc->bpp == 10 || dsc->bpc == 10) {
- phys->hw_pp->ops.setup_dither(phys->hw_pp, NULL, 0);
- return;
- }
-
- ret = sde_connector_get_dither_cfg(phys->connector,
- phys->connector->state, &dither_cfg, &len);
- if (ret)
- return;
-
- if (TOPOLOGY_DUALPIPE_MERGE_MODE(topology)) {
- for (i = 0; i < MAX_CHANNELS_PER_ENC; i++) {
- hw_pp = sde_enc->hw_pp[i];
- if (hw_pp) {
- phys->hw_pp->ops.setup_dither(hw_pp, dither_cfg,
- len);
- }
- }
- } else {
- phys->hw_pp->ops.setup_dither(phys->hw_pp, dither_cfg, len);
- }
-}
-
static int _sde_encoder_wakeup_time(struct drm_encoder *drm_enc,
ktime_t *wakeup_time)
{
@@ -6313,3 +6368,28 @@
sde_enc = to_sde_encoder_virt(encoder);
sde_enc->recovery_events_enabled = enabled;
}
+
+void sde_encoder_trigger_early_wakeup(struct drm_encoder *drm_enc)
+{
+ struct sde_encoder_virt *sde_enc = NULL;
+ struct msm_drm_private *priv = NULL;
+
+ priv = drm_enc->dev->dev_private;
+ sde_enc = to_sde_encoder_virt(drm_enc);
+ if (!sde_enc->crtc || (sde_enc->crtc->index
+ >= ARRAY_SIZE(priv->disp_thread))) {
+ SDE_DEBUG_ENC(sde_enc,
+ "invalid cached CRTC: %d or crtc index: %d\n",
+ sde_enc->crtc == NULL,
+ sde_enc->crtc ? sde_enc->crtc->index : -EINVAL);
+ return;
+ }
+
+ if (sde_enc->rc_state == SDE_ENC_RC_STATE_IDLE ||
+ sde_enc->rc_state == SDE_ENC_RC_STATE_ON) {
+ SDE_ATRACE_BEGIN("sde_encoder_resource_control");
+ sde_encoder_resource_control(drm_enc,
+ SDE_ENC_RC_EVENT_EARLY_WAKEUP);
+ SDE_ATRACE_END("sde_encoder_resource_control");
+ }
+}
diff --git a/msm/sde/sde_encoder.h b/msm/sde/sde_encoder.h
index ebee140..3204a15 100644
--- a/msm/sde/sde_encoder.h
+++ b/msm/sde/sde_encoder.h
@@ -373,6 +373,12 @@
void sde_encoder_uidle_enable(struct drm_encoder *drm_enc, bool enable);
/**
+ * sde_encoder_trigger_early_wakeup - trigger early wake up
+ * @drm_enc: Pointer to drm encoder structure
+ */
+void sde_encoder_trigger_early_wakeup(struct drm_encoder *drm_enc);
+
+/**
* sde_encoder_virt_reset - delay encoder virt reset
* @drm_enc: Pointer to drm encoder structure
*/
diff --git a/msm/sde/sde_kms.c b/msm/sde/sde_kms.c
index 2b8e9e6..ce6c6a4 100644
--- a/msm/sde/sde_kms.c
+++ b/msm/sde/sde_kms.c
@@ -28,6 +28,7 @@
#include <linux/dma-buf.h>
#include <linux/memblock.h>
#include <linux/bootmem.h>
+#include <linux/msm_drm_notify.h>
#include <soc/qcom/scm.h>
#include "msm_drv.h"
@@ -62,6 +63,7 @@
#define MEM_PROTECT_SD_CTRL_SWITCH 0x18
#define MDP_DEVICE_ID 0x1A
+#define PANEL_INFO_CLASS_NAME "panel_info"
#define TCSR_DISP_HF_SF_ARES_GLITCH_MASK 0x01FCA084
static const char * const iommu_ports[] = {
@@ -110,6 +112,11 @@
static int _sde_kms_mmu_init(struct sde_kms *sde_kms);
static int _sde_kms_register_events(struct msm_kms *kms,
struct drm_mode_object *obj, u32 event, bool en);
+
+static int panel_info_dev_create(struct sde_kms *sde_kms);
+static void panel_info_dev_release(struct sde_kms *sde_kms);
+static struct class *panel_info_class;
+
bool sde_is_custom_client(void)
{
return sdecustom;
@@ -1396,11 +1403,164 @@
sde_kms->wb_displays = NULL;
sde_kms->wb_display_count = 0;
+ panel_info_dev_release(sde_kms);
kfree(sde_kms->dsi_displays);
sde_kms->dsi_displays = NULL;
sde_kms->dsi_display_count = 0;
}
+static ssize_t panel_vendor_name_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct dsi_display *display;
+ struct dsi_panel *panel;
+
+ display = dev_get_drvdata(dev);
+ panel = display->panel;
+
+ if (!display || !panel || !panel->vendor_info.name || !buf) {
+ pr_err("Failed to show vendor name\n");
+ return 0;
+ }
+
+ return scnprintf(buf, PAGE_SIZE, "%s\n", panel->vendor_info.name);
+}
+
+static ssize_t serial_number_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct dsi_display *display;
+ struct dsi_panel *panel;
+
+ display = dev_get_drvdata(dev);
+ panel = display->panel;
+
+ if (!display || !panel || !panel->vendor_info.is_sn || !buf) {
+ pr_err("Failed to show SN\n");
+ return 0;
+ }
+
+ return scnprintf(buf, PAGE_SIZE, "%s\n", panel->vendor_info.sn);
+}
+
+static ssize_t panel_extinfo_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct dsi_display *display;
+ struct dsi_panel *panel;
+ const struct dsi_panel_vendor_info *vendor_info;
+ int i, written;
+
+ display = dev_get_drvdata(dev);
+ if (unlikely(!display || !display->panel || !buf))
+ return -EINVAL;
+
+ panel = display->panel;
+ vendor_info = &panel->vendor_info;
+ if (!vendor_info->extinfo_read) {
+ pr_err("Failed to show Ext info\n");
+ return 0;
+ }
+
+ mutex_lock(&panel->panel_lock);
+ written = 0;
+ for (i = 0; i < vendor_info->extinfo_read && written < PAGE_SIZE; i++) {
+ written += scnprintf(buf + written, PAGE_SIZE - written,
+ "0x%02X ", vendor_info->extinfo[i]);
+ }
+ mutex_unlock(&panel->panel_lock);
+ if (!written)
+ return -EFAULT;
+ /* replace last space with a line return */
+ buf[written - 1] = '\n';
+ buf[written] = '\0';
+
+ return written;
+}
+
+struct device_attribute dev_attr_panel_vendor_name =
+ __ATTR_RO_MODE(panel_vendor_name, 0400);
+struct device_attribute dev_attr_serial_number =
+ __ATTR_RO_MODE(serial_number, 0400);
+struct device_attribute dev_attr_panel_ext_info =
+ __ATTR_RO_MODE(panel_extinfo, 0400);
+
+static struct attribute *panel_info_dev_attrs[] = {
+ &dev_attr_panel_vendor_name.attr,
+ &dev_attr_serial_number.attr,
+ &dev_attr_panel_ext_info.attr,
+ NULL
+};
+
+
+static const struct attribute_group panel_info_dev_group = {
+ .attrs = panel_info_dev_attrs,
+};
+
+static const struct attribute_group *panel_info_dev_groups[] = {
+ &panel_info_dev_group,
+ NULL
+};
+
+static int panel_info_dev_create(struct sde_kms *sde_kms)
+{
+ struct dsi_display *display;
+ int i;
+
+ if (panel_info_class && !IS_ERR(panel_info_class))
+ return 0;
+
+ panel_info_class = class_create(THIS_MODULE, PANEL_INFO_CLASS_NAME);
+ if (!panel_info_class || IS_ERR(panel_info_class))
+ return -EINVAL;
+
+ for (i = 0; i < sde_kms->dsi_display_count; ++i) {
+ display = (struct dsi_display *)sde_kms->dsi_displays[i];
+ display->panel_info_dev =
+ device_create_with_groups(panel_info_class,
+ &display->pdev->dev,
+ 0,
+ display,
+ panel_info_dev_groups,
+ "panel%d",
+ i);
+ if (!display->panel_info_dev || IS_ERR(display->panel_info_dev))
+ goto error;
+ }
+ return 0;
+error:
+ while (--i >= 0) {
+ display = (struct dsi_display *)sde_kms->dsi_displays[i];
+ device_unregister(display->panel_info_dev);
+ display->panel_info_dev = NULL;
+ }
+
+ class_destroy(panel_info_class);
+ panel_info_class = NULL;
+
+ return -EINVAL;
+}
+
+static void panel_info_dev_release(struct sde_kms *sde_kms)
+{
+ struct dsi_display *display;
+ int i;
+
+ if (!panel_info_class || IS_ERR(panel_info_class))
+ return;
+
+ for (i = 0; i < sde_kms->dsi_display_count; ++i) {
+ display = (struct dsi_display *)sde_kms->dsi_displays[i];
+ device_unregister(display->panel_info_dev);
+ display->panel_info_dev = NULL;
+ }
+ class_destroy(panel_info_class);
+ panel_info_class = NULL;
+}
+
/**
* _sde_kms_setup_displays - create encoders, bridges and connectors
* for underlying displays
@@ -1420,7 +1580,6 @@
.pre_destroy = dsi_connector_put_modes,
.mode_valid = dsi_conn_mode_valid,
.get_info = dsi_display_get_info,
- .set_backlight = dsi_display_set_backlight,
.soft_reset = dsi_display_soft_reset,
.pre_kickoff = dsi_conn_pre_kickoff,
.clk_ctrl = dsi_display_clk_ctrl,
@@ -1434,6 +1593,7 @@
.cont_splash_config = dsi_display_cont_splash_config,
.get_panel_vfp = dsi_display_get_panel_vfp,
.get_default_lms = dsi_display_get_default_lms,
+ .set_idle_hint = dsi_display_set_idle_hint,
.get_qsync_min_fps = dsi_display_get_qsync_min_fps,
};
static const struct sde_connector_ops wb_ops = {
@@ -1489,6 +1649,7 @@
}
/* dsi */
+ panel_info_dev_create(sde_kms);
for (i = 0; i < sde_kms->dsi_display_count &&
priv->num_encoders < max_encoders; ++i) {
display = sde_kms->dsi_displays[i];
@@ -3933,3 +4094,26 @@
SDE_EVT32(DRMID(encoder), MSM_ENC_ACTIVE_REGION);
return sde_encoder_wait_for_event(encoder, MSM_ENC_ACTIVE_REGION);
}
+
+void sde_kms_trigger_early_wakeup(struct sde_kms *sde_kms,
+ struct drm_crtc *crtc)
+{
+ struct msm_drm_private *priv;
+ struct drm_encoder *drm_enc;
+
+ if (!sde_kms || !crtc) {
+ SDE_ERROR("invalid argument sde_kms %pK crtc %pK\n",
+ sde_kms, crtc);
+ return;
+ }
+
+ priv = sde_kms->dev->dev_private;
+
+ SDE_ATRACE_BEGIN("sde_kms_trigger_early_wakeup");
+ drm_for_each_encoder_mask(drm_enc, crtc->dev, crtc->state->encoder_mask)
+ sde_encoder_trigger_early_wakeup(drm_enc);
+
+ if (sde_kms->first_kickoff)
+ sde_power_scale_reg_bus(&priv->phandle, VOTE_INDEX_HIGH, false);
+ SDE_ATRACE_END("sde_kms_trigger_early_wakeup");
+}
diff --git a/msm/sde/sde_kms.h b/msm/sde/sde_kms.h
index 6882cdf..2c6f19a 100644
--- a/msm/sde/sde_kms.h
+++ b/msm/sde/sde_kms.h
@@ -663,6 +663,14 @@
int sde_kms_handle_recovery(struct drm_encoder *encoder);
/**
+ * sde_kms_trigger_early_wakeup - trigger early wake up
+ * @sde_kms: pointer to sde_kms structure
+ * @crtc: pointer to drm_crtc structure
+ */
+void sde_kms_trigger_early_wakeup(struct sde_kms *sde_kms,
+ struct drm_crtc *crtc);
+
+/**
* sde_kms_update_pm_qos_irq_request - Update Qos vote for CPU receiving
* display IRQ
* @sde_kms : pointer to sde_kms structure
diff --git a/msm/sde/sde_plane.c b/msm/sde/sde_plane.c
index 0264cef..5ef4889 100644
--- a/msm/sde/sde_plane.c
+++ b/msm/sde/sde_plane.c
@@ -1,4 +1,5 @@
/*
+ * Copyright (c) 2021-2022 Qualcomm Innovation Center, Inc. All rights reserved.
* Copyright (C) 2014-2020 The Linux Foundation. All rights reserved.
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
* Copyright (C) 2013 Red Hat
diff --git a/msm/sde/sde_wb.c b/msm/sde/sde_wb.c
index 4fcc2c3..4656373 100644
--- a/msm/sde/sde_wb.c
+++ b/msm/sde/sde_wb.c
@@ -823,15 +823,17 @@
},
};
-static int __init sde_wb_register(void)
+int __init sde_wb_register(void)
{
return platform_driver_register(&sde_wb_driver);
}
-static void __exit sde_wb_unregister(void)
+void __exit sde_wb_unregister(void)
{
platform_driver_unregister(&sde_wb_driver);
}
+#ifndef CONFIG_DRM_MSM_MODULE
module_init(sde_wb_register);
module_exit(sde_wb_unregister);
+#endif
diff --git a/msm/sde_dbg.c b/msm/sde_dbg.c
index 04037d5..8fc73fb 100644
--- a/msm/sde_dbg.c
+++ b/msm/sde_dbg.c
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
+ * Copyright (c) 2021-2022 Qualcomm Innovation Center, Inc. All rights reserved.
* Copyright (c) 2009-2020, The Linux Foundation. All rights reserved.
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
*/
@@ -3291,7 +3292,7 @@
if (in_mem) {
if (!(*dump_mem))
- *dump_mem = vzalloc(list_size);
+ *dump_mem = vzalloc(list_size);
if (*dump_mem) {
dump_addr = *dump_mem;
@@ -3450,7 +3451,7 @@
if (in_mem) {
if (!(*dump_mem))
- *dump_mem = vzalloc(list_size);
+ *dump_mem = vzalloc(list_size);
if (*dump_mem) {
dump_addr = *dump_mem;
diff --git a/msm/sde_power_handle.c b/msm/sde_power_handle.c
index 9f66039..ebe3eb9 100644
--- a/msm/sde_power_handle.c
+++ b/msm/sde_power_handle.c
@@ -279,7 +279,7 @@
return rc;
}
-#ifdef CONFIG_QCOM_BUS_SCALING
+#if IS_ENABLED(CONFIG_QCOM_BUS_SCALING)
#define MAX_AXI_PORT_COUNT 3
diff --git a/msm/sde_rsc.c b/msm/sde_rsc.c
index b6397a9..fed56a7 100644
--- a/msm/sde_rsc.c
+++ b/msm/sde_rsc.c
@@ -1647,19 +1647,19 @@
return 0;
}
-static const struct of_device_id dt_match[] = {
+static const struct of_device_id dt_match_sde_rsc[] = {
{ .compatible = "qcom,sde-rsc"},
{},
};
-MODULE_DEVICE_TABLE(of, dt_match);
+MODULE_DEVICE_TABLE(of, dt_match_sde_rsc);
static struct platform_driver sde_rsc_platform_driver = {
.probe = sde_rsc_probe,
.remove = sde_rsc_remove,
.driver = {
.name = "sde_rsc",
- .of_match_table = dt_match,
+ .of_match_table = dt_match_sde_rsc,
.suppress_bind_attrs = true,
},
};
@@ -1678,21 +1678,23 @@
},
};
-static int __init sde_rsc_register(void)
+int __init sde_rsc_register(void)
{
return platform_driver_register(&sde_rsc_platform_driver);
}
-static void __exit sde_rsc_unregister(void)
+void __exit sde_rsc_unregister(void)
{
platform_driver_unregister(&sde_rsc_platform_driver);
}
-static int __init sde_rsc_rpmh_register(void)
+int __init sde_rsc_rpmh_register(void)
{
return platform_driver_register(&sde_rsc_rpmh_driver);
}
+#ifndef CONFIG_DRM_MSM_MODULE
subsys_initcall(sde_rsc_rpmh_register);
module_init(sde_rsc_register);
module_exit(sde_rsc_unregister);
+#endif
diff --git a/pll/Makefile b/pll/Makefile
deleted file mode 100644
index 0b29a90..0000000
--- a/pll/Makefile
+++ /dev/null
@@ -1,18 +0,0 @@
-# SPDX-License-Identifier: GPL-2.0-only
-
-ccflags-y := -I$(srctree)/drivers/clk/qcom/
-
-obj-$(CONFIG_QCOM_MDSS_PLL) += pll_util.o
-obj-$(CONFIG_QCOM_MDSS_PLL) += pll_drv.o
-obj-$(CONFIG_QCOM_MDSS_PLL) += dsi_pll_10nm.o
-obj-$(CONFIG_QCOM_MDSS_PLL) += dsi_pll_7nm.o
-obj-$(CONFIG_QCOM_MDSS_PLL) += dsi_pll_28lpm.o
-obj-$(CONFIG_QCOM_MDSS_PLL) += dsi_pll_28nm_util.o
-obj-$(CONFIG_QCOM_MDSS_PLL) += dsi_pll_14nm.o
-obj-$(CONFIG_QCOM_MDSS_PLL) += dsi_pll_14nm_util.o
-obj-$(CONFIG_QCOM_MDSS_PLL) += hdmi_pll_28lpm.o
-obj-$(CONFIG_QCOM_MDSS_DP_PLL) += dp_pll_7nm.o \
- dp_pll_7nm_util.o \
- dp_pll_10nm.o \
- dp_pll_10nm_util.o \
- dp_pll_14nm.o \
diff --git a/pll/pll_drv.c b/pll/pll_drv.c
index 5e0cd4c..454baa0 100644
--- a/pll/pll_drv.c
+++ b/pll/pll_drv.c
@@ -391,7 +391,7 @@
{},
};
-MODULE_DEVICE_TABLE(of, mdss_clock_dt_match);
+MODULE_DEVICE_TABLE(of, mdss_pll_dt_match);
static struct platform_driver mdss_pll_driver = {
.probe = mdss_pll_probe,
@@ -399,10 +399,11 @@
.driver = {
.name = "mdss_pll",
.of_match_table = mdss_pll_dt_match,
+ .sync_state = clk_sync_state,
},
};
-static int __init mdss_pll_driver_init(void)
+int __init mdss_pll_driver_init(void)
{
int rc;
@@ -412,13 +413,15 @@
return rc;
}
-fs_initcall(mdss_pll_driver_init);
-static void __exit mdss_pll_driver_deinit(void)
+void __exit mdss_pll_driver_deinit(void)
{
platform_driver_unregister(&mdss_pll_driver);
}
-module_exit(mdss_pll_driver_deinit);
+#ifndef CONFIG_DRM_MSM_MODULE
+fs_initcall(mdss_pll_driver_init);
+module_exit(mdss_pll_driver_deinit);
+#endif
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("mdss pll driver");
diff --git a/rotator/sde_rotator_base.c b/rotator/sde_rotator_base.c
index 3cf8507..4ef5151 100644
--- a/rotator/sde_rotator_base.c
+++ b/rotator/sde_rotator_base.c
@@ -827,7 +827,7 @@
#define BUS_VOTE_40_MHZ 320000000
#define BUS_VOTE_80_MHZ 640000000
-#ifdef CONFIG_QCOM_BUS_SCALING
+#if IS_ENABLED(CONFIG_QCOM_BUS_SCALING)
static struct msm_bus_vectors mdp_reg_bus_vectors[] = {
MDP_REG_BUS_VECTOR_ENTRY(0, 0),
diff --git a/rotator/sde_rotator_core.c b/rotator/sde_rotator_core.c
index 438699e..e7f72fd 100644
--- a/rotator/sde_rotator_core.c
+++ b/rotator/sde_rotator_core.c
@@ -86,7 +86,7 @@
/* forward prototype */
static int sde_rotator_update_perf(struct sde_rot_mgr *mgr);
-#ifdef CONFIG_QCOM_BUS_SCALING
+#if IS_ENABLED(CONFIG_QCOM_BUS_SCALING)
static struct msm_bus_vectors rot_reg_bus_vectors[] = {
SDE_REG_BUS_VECTOR_ENTRY(0, 0),
SDE_REG_BUS_VECTOR_ENTRY(0, BUS_VOTE_19_MHZ),
@@ -2781,7 +2781,7 @@
.attrs = sde_rotator_fs_attrs
};
-#ifdef CONFIG_QCOM_BUS_SCALING
+#if IS_ENABLED(CONFIG_QCOM_BUS_SCALING)
static int sde_rotator_parse_dt_bus(struct sde_rot_mgr *mgr,
struct platform_device *dev)
{
@@ -2935,7 +2935,7 @@
return rc;
}
-#ifdef CONFIG_QCOM_BUS_SCALING
+#if IS_ENABLED(CONFIG_QCOM_BUS_SCALING)
static void sde_rotator_bus_scale_unregister(struct sde_rot_mgr *mgr)
{
SDEROT_DBG("unregister bus_hdl=%x, reg_bus_hdl=%x\n",