summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/devicetree/bindings/drm/msm/mdp.txt3
-rw-r--r--drivers/gpu/drm/msm/Makefile1
-rw-r--r--drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c29
-rw-r--r--drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h7
-rw-r--r--drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_bridge.c5
-rw-r--r--drivers/gpu/drm/msm/msm_mmu.h2
-rw-r--r--drivers/gpu/drm/msm/msm_smmu.c24
-rw-r--r--drivers/gpu/drm/msm/sde/sde_connector.c5
-rw-r--r--drivers/gpu/drm/msm/sde/sde_crtc.c24
-rw-r--r--drivers/gpu/drm/msm/sde/sde_kms.c76
-rw-r--r--drivers/gpu/drm/msm/sde/sde_kms.h4
-rw-r--r--drivers/gpu/drm/msm/sde/sde_splash.c631
-rw-r--r--drivers/gpu/drm/msm/sde/sde_splash.h124
13 files changed, 929 insertions, 6 deletions
diff --git a/Documentation/devicetree/bindings/drm/msm/mdp.txt b/Documentation/devicetree/bindings/drm/msm/mdp.txt
index 3a6db0553fe3..a76b604445bd 100644
--- a/Documentation/devicetree/bindings/drm/msm/mdp.txt
+++ b/Documentation/devicetree/bindings/drm/msm/mdp.txt
@@ -3,6 +3,7 @@ Qualcomm Technologies,Inc. Adreno/Snapdragon display controller
Required properties:
Optional properties:
+- contiguous-region: reserved memory for HDMI and DSI buffer.
- qcom,sde-plane-id-map: plane id mapping for virtual plane.
- qcom,sde-plane-id: each virtual plane mapping node.
- reg: reg property.
@@ -17,6 +18,8 @@ Optional properties:
Example:
&mdss_mdp {
+ contiguous-region = <&cont_splash_mem &cont_splash_mem_hdmi>;
+
qcom,sde-plane-id-map {
qcom,sde-plane-id@0 {
reg = <0x0>;
diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
index 84125b3d1f95..4c082fff2fc5 100644
--- a/drivers/gpu/drm/msm/Makefile
+++ b/drivers/gpu/drm/msm/Makefile
@@ -48,6 +48,7 @@ msm_drm-y := \
sde/sde_backlight.o \
sde/sde_color_processing.o \
sde/sde_vbif.o \
+ sde/sde_splash.o \
sde_dbg_evtlog.o \
sde_io_util.o \
dba_bridge.o \
diff --git a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c
index 2ee75a2c1039..869eadd0a7d8 100644
--- a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c
+++ b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c
@@ -1254,6 +1254,13 @@ static int _sde_hdmi_hpd_enable(struct sde_hdmi *sde_hdmi)
uint32_t hpd_ctrl;
int i, ret;
unsigned long flags;
+ struct drm_connector *connector;
+ struct msm_drm_private *priv;
+ struct sde_kms *sde_kms;
+
+ connector = hdmi->connector;
+ priv = connector->dev->dev_private;
+ sde_kms = to_sde_kms(priv->kms);
for (i = 0; i < config->hpd_reg_cnt; i++) {
ret = regulator_enable(hdmi->hpd_regs[i]);
@@ -1293,9 +1300,11 @@ static int _sde_hdmi_hpd_enable(struct sde_hdmi *sde_hdmi)
}
}
- sde_hdmi_set_mode(hdmi, false);
- _sde_hdmi_phy_reset(hdmi);
- sde_hdmi_set_mode(hdmi, true);
+ if (!sde_kms->splash_info.handoff) {
+ sde_hdmi_set_mode(hdmi, false);
+ _sde_hdmi_phy_reset(hdmi);
+ sde_hdmi_set_mode(hdmi, true);
+ }
hdmi_write(hdmi, REG_HDMI_USEC_REFTIMER, 0x0001001b);
@@ -2822,6 +2831,7 @@ int sde_hdmi_drm_init(struct sde_hdmi *display, struct drm_encoder *enc)
struct msm_drm_private *priv = NULL;
struct hdmi *hdmi;
struct platform_device *pdev;
+ struct sde_kms *sde_kms;
DBG("");
if (!display || !display->drm_dev || !enc) {
@@ -2880,6 +2890,19 @@ int sde_hdmi_drm_init(struct sde_hdmi *display, struct drm_encoder *enc)
enc->bridge = hdmi->bridge;
priv->bridges[priv->num_bridges++] = hdmi->bridge;
+ /*
+ * After initialising HDMI bridge, we need to check
+ * whether the early display is enabled for HDMI.
+ * If yes, we need to increase refcount of hdmi power
+ * clocks. This can skip the clock disabling operation in
+ * clock_late_init when finding clk.count == 1.
+ */
+ sde_kms = to_sde_kms(priv->kms);
+ if (sde_kms->splash_info.handoff) {
+ sde_hdmi_bridge_power_on(hdmi->bridge);
+ hdmi->power_on = true;
+ }
+
mutex_unlock(&display->display_lock);
return 0;
diff --git a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h
index 6b9bcfec031b..f11fe8302470 100644
--- a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h
+++ b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h
@@ -345,6 +345,13 @@ int sde_hdmi_set_property(struct drm_connector *connector,
int property_index,
uint64_t value,
void *display);
+/**
+ * sde_hdmi_bridge_power_on -- A wrapper of _sde_hdmi_bridge_power_on.
+ * @bridge: Handle to the drm bridge.
+ *
+ * Return: void.
+ */
+void sde_hdmi_bridge_power_on(struct drm_bridge *bridge);
/**
* sde_hdmi_get_property() - get the connector properties
diff --git a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_bridge.c b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_bridge.c
index 24d2320683e4..2995bb5000f5 100644
--- a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_bridge.c
+++ b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_bridge.c
@@ -736,6 +736,11 @@ static void _sde_hdmi_bridge_mode_set(struct drm_bridge *bridge,
_sde_hdmi_bridge_setup_scrambler(hdmi, mode);
}
+void sde_hdmi_bridge_power_on(struct drm_bridge *bridge)
+{
+ _sde_hdmi_bridge_power_on(bridge);
+}
+
static const struct drm_bridge_funcs _sde_hdmi_bridge_funcs = {
.pre_enable = _sde_hdmi_bridge_pre_enable,
.enable = _sde_hdmi_bridge_enable,
diff --git a/drivers/gpu/drm/msm/msm_mmu.h b/drivers/gpu/drm/msm/msm_mmu.h
index 8148d3e9e850..cd3a710f8f27 100644
--- a/drivers/gpu/drm/msm/msm_mmu.h
+++ b/drivers/gpu/drm/msm/msm_mmu.h
@@ -46,6 +46,8 @@ struct msm_mmu_funcs {
void (*destroy)(struct msm_mmu *mmu);
void (*enable)(struct msm_mmu *mmu);
void (*disable)(struct msm_mmu *mmu);
+ int (*set_property)(struct msm_mmu *mmu,
+ enum iommu_attr attr, void *data);
};
struct msm_mmu {
diff --git a/drivers/gpu/drm/msm/msm_smmu.c b/drivers/gpu/drm/msm/msm_smmu.c
index eb68eb977aa7..4247243055b6 100644
--- a/drivers/gpu/drm/msm/msm_smmu.c
+++ b/drivers/gpu/drm/msm/msm_smmu.c
@@ -170,12 +170,36 @@ static void msm_smmu_destroy(struct msm_mmu *mmu)
kfree(smmu);
}
+/* user can call this API to set the attribute of smmu*/
+static int msm_smmu_set_property(struct msm_mmu *mmu,
+ enum iommu_attr attr, void *data)
+{
+ struct msm_smmu *smmu = to_msm_smmu(mmu);
+ struct msm_smmu_client *client = msm_smmu_to_client(smmu);
+ struct iommu_domain *domain;
+ int ret = 0;
+
+ if (!client)
+ return -EINVAL;
+
+ domain = client->mmu_mapping->domain;
+ if (!domain)
+ return -EINVAL;
+
+ ret = iommu_domain_set_attr(domain, attr, data);
+ if (ret)
+ DRM_ERROR("set domain attribute failed\n");
+
+ return ret;
+}
+
static const struct msm_mmu_funcs funcs = {
.attach = msm_smmu_attach,
.detach = msm_smmu_detach,
.map = msm_smmu_map,
.unmap = msm_smmu_unmap,
.destroy = msm_smmu_destroy,
+ .set_property = msm_smmu_set_property,
};
static struct msm_smmu_domain msm_smmu_domains[MSM_SMMU_DOMAIN_MAX] = {
diff --git a/drivers/gpu/drm/msm/sde/sde_connector.c b/drivers/gpu/drm/msm/sde/sde_connector.c
index 0bb8298c1013..60d6327a1316 100644
--- a/drivers/gpu/drm/msm/sde/sde_connector.c
+++ b/drivers/gpu/drm/msm/sde/sde_connector.c
@@ -598,6 +598,7 @@ struct drm_connector *sde_connector_init(struct drm_device *dev,
struct sde_kms *sde_kms;
struct sde_kms_info *info;
struct sde_connector *c_conn = NULL;
+ struct sde_splash_info *sinfo;
int rc;
if (!dev || !dev->dev_private || !encoder) {
@@ -751,6 +752,10 @@ struct drm_connector *sde_connector_init(struct drm_device *dev,
SDE_DEBUG("connector %d attach encoder %d\n",
c_conn->base.base.id, encoder->base.id);
+ sinfo = &sde_kms->splash_info;
+ if (sinfo && sinfo->handoff)
+ sde_splash_setup_connector_count(sinfo, connector_type);
+
priv->connectors[priv->num_connectors++] = &c_conn->base;
return &c_conn->base;
diff --git a/drivers/gpu/drm/msm/sde/sde_crtc.c b/drivers/gpu/drm/msm/sde/sde_crtc.c
index 7d2291dadc18..49ea856b6c9c 100644
--- a/drivers/gpu/drm/msm/sde/sde_crtc.c
+++ b/drivers/gpu/drm/msm/sde/sde_crtc.c
@@ -590,14 +590,22 @@ void sde_crtc_complete_commit(struct drm_crtc *crtc,
{
struct sde_crtc *sde_crtc;
struct sde_crtc_state *cstate;
+ struct drm_connector *conn;
+ struct drm_device *dev;
+ struct msm_drm_private *priv;
+ struct sde_kms *sde_kms;
int i;
- if (!crtc || !crtc->state) {
+ if (!crtc || !crtc->state || !crtc->dev) {
SDE_ERROR("invalid crtc\n");
return;
}
+ dev = crtc->dev;
+ priv = dev->dev_private;
+
sde_crtc = to_sde_crtc(crtc);
+ sde_kms = _sde_crtc_get_kms(crtc);
cstate = to_sde_crtc_state(crtc->state);
SDE_EVT32(DRMID(crtc));
@@ -606,6 +614,20 @@ void sde_crtc_complete_commit(struct drm_crtc *crtc,
for (i = 0; i < cstate->num_connectors; ++i)
sde_connector_complete_commit(cstate->connectors[i]);
+
+ if (!sde_kms->splash_info.handoff &&
+ sde_kms->splash_info.lk_is_exited) {
+ mutex_lock(&dev->mode_config.mutex);
+ drm_for_each_connector(conn, crtc->dev) {
+ if (conn->state->crtc != crtc)
+ continue;
+
+ sde_splash_clean_up_free_resource(priv->kms,
+ &priv->phandle,
+ conn->connector_type);
+ }
+ mutex_unlock(&dev->mode_config.mutex);
+ }
}
/**
diff --git a/drivers/gpu/drm/msm/sde/sde_kms.c b/drivers/gpu/drm/msm/sde/sde_kms.c
index 031493aa42b8..a67b6a965b95 100644
--- a/drivers/gpu/drm/msm/sde/sde_kms.c
+++ b/drivers/gpu/drm/msm/sde/sde_kms.c
@@ -355,6 +355,9 @@ static void sde_kms_prepare_commit(struct msm_kms *kms,
struct drm_device *dev = sde_kms->dev;
struct msm_drm_private *priv = dev->dev_private;
+ if (sde_kms->splash_info.handoff)
+ sde_splash_clean_up_exit_lk(kms);
+
sde_power_resource_enable(&priv->phandle, sde_kms->core_client, true);
}
@@ -997,8 +1000,15 @@ static void _sde_kms_hw_destroy(struct sde_kms *sde_kms,
sde_hw_catalog_deinit(sde_kms->catalog);
sde_kms->catalog = NULL;
+ if (sde_kms->splash_info.handoff) {
+ if (sde_kms->core_client)
+ sde_splash_destroy(&sde_kms->splash_info,
+ &priv->phandle, sde_kms->core_client);
+ }
+
if (sde_kms->core_client)
- sde_power_client_destroy(&priv->phandle, sde_kms->core_client);
+ sde_power_client_destroy(&priv->phandle,
+ sde_kms->core_client);
sde_kms->core_client = NULL;
if (sde_kms->vbif[VBIF_NRT])
@@ -1110,6 +1120,24 @@ static int _sde_kms_mmu_init(struct sde_kms *sde_kms)
continue;
}
+ /* Attaching smmu means IOMMU HW starts to work immediately.
+ * However, display HW in LK is still accessing memory
+ * while the memory map is not done yet.
+ * So first set DOMAIN_ATTR_EARLY_MAP attribute 1 to bypass
+ * stage 1 translation in IOMMU HW.
+ */
+ if ((i == MSM_SMMU_DOMAIN_UNSECURE) &&
+ sde_kms->splash_info.handoff) {
+ ret = mmu->funcs->set_property(mmu,
+ DOMAIN_ATTR_EARLY_MAP,
+ &sde_kms->splash_info.handoff);
+ if (ret) {
+ SDE_ERROR("failed to set map att: %d\n", ret);
+ mmu->funcs->destroy(mmu);
+ goto fail;
+ }
+ }
+
aspace = msm_gem_smmu_address_space_create(sde_kms->dev->dev,
mmu, "sde");
if (IS_ERR(aspace)) {
@@ -1127,6 +1155,19 @@ static int _sde_kms_mmu_init(struct sde_kms *sde_kms)
goto fail;
}
+ /*
+ * It's safe now to map the physical memory blcok LK accesses.
+ */
+ if ((i == MSM_SMMU_DOMAIN_UNSECURE) &&
+ sde_kms->splash_info.handoff) {
+ ret = sde_splash_smmu_map(sde_kms->dev, mmu,
+ &sde_kms->splash_info);
+ if (ret) {
+ SDE_ERROR("map rsv mem failed: %d\n", ret);
+ msm_gem_address_space_put(aspace);
+ goto fail;
+ }
+ }
}
return 0;
@@ -1141,6 +1182,7 @@ static int sde_kms_hw_init(struct msm_kms *kms)
struct sde_kms *sde_kms;
struct drm_device *dev;
struct msm_drm_private *priv;
+ struct sde_splash_info *sinfo;
int i, rc = -EINVAL;
if (!kms) {
@@ -1230,6 +1272,33 @@ static int sde_kms_hw_init(struct msm_kms *kms)
goto power_error;
}
+ rc = sde_splash_parse_dt(dev);
+ if (rc) {
+ SDE_ERROR("parse dt for splash info failed: %d\n", rc);
+ goto power_error;
+ }
+
+ /*
+ * Read the DISP_INTF_SEL register to check
+ * whether early display is enabled in LK.
+ */
+ rc = sde_splash_get_handoff_status(kms);
+ if (rc) {
+ SDE_ERROR("get early splash status failed: %d\n", rc);
+ goto power_error;
+ }
+
+ /*
+ * when LK has enabled early display, sde_splash_init should be
+ * called first. This function will first do bandwidth voting job
+ * because display hardware is accessing AHB data bus, otherwise
+ * device reboot will happen. Second is to check if the memory is
+ * reserved.
+ */
+ sinfo = &sde_kms->splash_info;
+ if (sinfo->handoff)
+ sde_splash_init(&priv->phandle, kms);
+
for (i = 0; i < sde_kms->catalog->vbif_count; i++) {
u32 vbif_idx = sde_kms->catalog->vbif[i].id;
@@ -1304,7 +1373,10 @@ static int sde_kms_hw_init(struct msm_kms *kms)
*/
dev->mode_config.allow_fb_modifiers = true;
- sde_power_resource_enable(&priv->phandle, sde_kms->core_client, false);
+ if (!sde_kms->splash_info.handoff)
+ sde_power_resource_enable(&priv->phandle,
+ sde_kms->core_client, false);
+
return 0;
drm_obj_init_err:
diff --git a/drivers/gpu/drm/msm/sde/sde_kms.h b/drivers/gpu/drm/msm/sde/sde_kms.h
index 44f6be959ac9..d929e48a3fe8 100644
--- a/drivers/gpu/drm/msm/sde/sde_kms.h
+++ b/drivers/gpu/drm/msm/sde/sde_kms.h
@@ -34,6 +34,7 @@
#include "sde_power_handle.h"
#include "sde_irq.h"
#include "sde_core_perf.h"
+#include "sde_splash.h"
#define DRMID(x) ((x) ? (x)->base.id : -1)
@@ -157,6 +158,9 @@ struct sde_kms {
bool has_danger_ctrl;
void **hdmi_displays;
int hdmi_display_count;
+
+ /* splash handoff structure */
+ struct sde_splash_info splash_info;
};
struct vsync_info {
diff --git a/drivers/gpu/drm/msm/sde/sde_splash.c b/drivers/gpu/drm/msm/sde/sde_splash.c
new file mode 100644
index 000000000000..8b2894163eae
--- /dev/null
+++ b/drivers/gpu/drm/msm/sde/sde_splash.c
@@ -0,0 +1,631 @@
+/*
+ * 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.
+ *
+ */
+#include <linux/of_address.h>
+#include <linux/debugfs.h>
+#include <linux/memblock.h>
+
+#include "msm_drv.h"
+#include "msm_mmu.h"
+#include "sde_kms.h"
+#include "sde_hw_mdss.h"
+#include "sde_hw_util.h"
+#include "sde_hw_intf.h"
+#include "sde_hw_catalog.h"
+
+#define MDP_SSPP_TOP0_OFF 0x1000
+#define DISP_INTF_SEL 0x004
+#define SPLIT_DISPLAY_EN 0x2F4
+
+/* scratch registers */
+#define SCRATCH_REGISTER_0 0x014
+#define SCRATCH_REGISTER_1 0x018
+#define SCRATCH_REGISTER_2 0x01C
+
+#define SDE_LK_RUNNING_VALUE 0xC001CAFE
+#define SDE_LK_SHUT_DOWN_VALUE 0xDEADDEAD
+#define SDE_LK_EXIT_VALUE 0xDEADBEEF
+
+#define SDE_LK_EXIT_MAX_LOOP 20
+/*
+ * In order to free reseved memory from bootup, and we are not
+ * able to call the __init free functions, so we need to free
+ * this memory by ourselves using the free_reserved_page() function.
+ */
+static void _sde_splash_free_bootup_memory_to_system(phys_addr_t phys,
+ size_t size)
+{
+ unsigned long pfn_start, pfn_end, pfn_idx;
+
+ memblock_free(phys, size);
+
+ pfn_start = phys >> PAGE_SHIFT;
+ pfn_end = (phys + size) >> PAGE_SHIFT;
+
+ for (pfn_idx = pfn_start; pfn_idx < pfn_end; pfn_idx++)
+ free_reserved_page(pfn_to_page(pfn_idx));
+}
+
+static int _sde_splash_parse_dt_get_lk_pool_node(struct drm_device *dev,
+ struct sde_splash_info *sinfo)
+{
+ struct device_node *parent, *node;
+ struct resource r;
+ int ret = 0;
+
+ if (!sinfo)
+ return -EINVAL;
+
+ parent = of_find_node_by_path("/reserved-memory");
+ if (!parent)
+ return -EINVAL;
+
+ node = of_find_node_by_name(parent, "lk_pool");
+ if (!node) {
+ SDE_ERROR("mem reservation for lk_pool is not presented\n");
+ ret = -EINVAL;
+ goto parent_node_err;
+ }
+
+ /* find the mode */
+ if (of_address_to_resource(node, 0, &r)) {
+ ret = -EINVAL;
+ goto child_node_err;
+ }
+
+ sinfo->lk_pool_paddr = (dma_addr_t)r.start;
+ sinfo->lk_pool_size = r.end - r.start;
+
+ DRM_INFO("lk_pool: addr:%pK, size:%pK\n",
+ (void *)sinfo->lk_pool_paddr,
+ (void *)sinfo->lk_pool_size);
+
+child_node_err:
+ of_node_put(node);
+
+parent_node_err:
+ of_node_put(parent);
+
+ return ret;
+}
+
+static int _sde_splash_parse_dt_get_display_node(struct drm_device *dev,
+ struct sde_splash_info *sinfo)
+{
+ unsigned long size = 0;
+ dma_addr_t start;
+ struct device_node *node;
+ int ret = 0, i = 0, len = 0;
+
+ /* get reserved memory for display module */
+ if (of_get_property(dev->dev->of_node, "contiguous-region", &len))
+ sinfo->splash_mem_num = len / sizeof(u32);
+ else
+ sinfo->splash_mem_num = 0;
+
+ sinfo->splash_mem_paddr =
+ kmalloc(sizeof(phys_addr_t) * sinfo->splash_mem_num,
+ GFP_KERNEL);
+ if (!sinfo->splash_mem_paddr) {
+ SDE_ERROR("alloc splash_mem_paddr failed\n");
+ return -ENOMEM;
+ }
+
+ sinfo->splash_mem_size =
+ kmalloc(sizeof(size_t) * sinfo->splash_mem_num,
+ GFP_KERNEL);
+ if (!sinfo->splash_mem_size) {
+ SDE_ERROR("alloc splash_mem_size failed\n");
+ goto error;
+ }
+
+ sinfo->obj = kmalloc(sizeof(struct drm_gem_object *) *
+ sinfo->splash_mem_num, GFP_KERNEL);
+ if (!sinfo->obj) {
+ SDE_ERROR("construct splash gem objects failed\n");
+ goto error;
+ }
+
+ for (i = 0; i < sinfo->splash_mem_num; i++) {
+ node = of_parse_phandle(dev->dev->of_node,
+ "contiguous-region", i);
+
+ if (node) {
+ struct resource r;
+
+ ret = of_address_to_resource(node, 0, &r);
+ if (ret)
+ return ret;
+
+ size = r.end - r.start;
+ start = (dma_addr_t)r.start;
+
+ sinfo->splash_mem_paddr[i] = start;
+ sinfo->splash_mem_size[i] = size;
+
+ DRM_INFO("blk: %d, addr:%pK, size:%pK\n",
+ i, (void *)sinfo->splash_mem_paddr[i],
+ (void *)sinfo->splash_mem_size[i]);
+
+ of_node_put(node);
+ }
+ }
+
+ return ret;
+
+error:
+ kfree(sinfo->splash_mem_paddr);
+ sinfo->splash_mem_paddr = NULL;
+
+ kfree(sinfo->splash_mem_size);
+ sinfo->splash_mem_size = NULL;
+
+ return -ENOMEM;
+}
+
+static bool _sde_splash_lk_check(struct sde_hw_intr *intr)
+{
+ return (SDE_LK_RUNNING_VALUE == SDE_REG_READ(&intr->hw,
+ SCRATCH_REGISTER_1)) ? true : false;
+}
+
+/**
+ * _sde_splash_notify_lk_to_exit.
+ *
+ * Function to monitor LK's status and tell it to exit.
+ */
+static void _sde_splash_notify_lk_exit(struct sde_hw_intr *intr)
+{
+ int i = 0;
+
+ /* first is to write exit signal to scratch register*/
+ SDE_REG_WRITE(&intr->hw, SCRATCH_REGISTER_1, SDE_LK_SHUT_DOWN_VALUE);
+
+ while ((SDE_LK_EXIT_VALUE !=
+ SDE_REG_READ(&intr->hw, SCRATCH_REGISTER_1)) &&
+ (++i < SDE_LK_EXIT_MAX_LOOP)) {
+ DRM_INFO("wait for LK's exit");
+ msleep(20);
+ }
+
+ if (i == SDE_LK_EXIT_MAX_LOOP)
+ SDE_ERROR("Loop LK's exit failed\n");
+}
+
+static int _sde_splash_gem_new(struct drm_device *dev,
+ struct sde_splash_info *sinfo)
+{
+ int i, ret;
+
+ for (i = 0; i < sinfo->splash_mem_num; i++) {
+ sinfo->obj[i] = msm_gem_new(dev,
+ sinfo->splash_mem_size[i], MSM_BO_UNCACHED);
+
+ if (IS_ERR(sinfo->obj[i])) {
+ ret = PTR_ERR(sinfo->obj[i]);
+ SDE_ERROR("failed to allocate gem, ret=%d\n", ret);
+ goto error;
+ }
+ }
+
+ return 0;
+
+error:
+ for (i = 0; i < sinfo->splash_mem_num; i++) {
+ if (sinfo->obj[i])
+ msm_gem_free_object(sinfo->obj[i]);
+ sinfo->obj[i] = NULL;
+ }
+
+ return ret;
+}
+
+static int _sde_splash_get_pages(struct drm_gem_object *obj, phys_addr_t phys)
+{
+ struct msm_gem_object *msm_obj = to_msm_bo(obj);
+ struct page **p;
+ dma_addr_t paddr;
+ int npages = obj->size >> PAGE_SHIFT;
+ int i;
+
+ p = drm_malloc_ab(npages, sizeof(struct page *));
+ if (!p)
+ return -ENOMEM;
+
+ paddr = phys;
+
+ for (i = 0; i < npages; i++) {
+ p[i] = phys_to_page(paddr);
+ paddr += PAGE_SIZE;
+ }
+
+ msm_obj->sgt = drm_prime_pages_to_sg(p, npages);
+ if (IS_ERR(msm_obj->sgt)) {
+ SDE_ERROR("failed to allocate sgt\n");
+ return -ENOMEM;
+ }
+
+ msm_obj->pages = p;
+
+ return 0;
+}
+
+static void _sde_splash_destroy_gem_object(struct msm_gem_object *msm_obj)
+{
+ if (msm_obj->pages) {
+ sg_free_table(msm_obj->sgt);
+ kfree(msm_obj->sgt);
+ drm_free_large(msm_obj->pages);
+ msm_obj->pages = NULL;
+ }
+}
+
+static void _sde_splash_destroy_splash_node(struct sde_splash_info *sinfo)
+{
+ kfree(sinfo->splash_mem_paddr);
+ sinfo->splash_mem_paddr = NULL;
+
+ kfree(sinfo->splash_mem_size);
+ sinfo->splash_mem_size = NULL;
+}
+
+static int _sde_splash_free_resource(struct msm_mmu *mmu,
+ struct sde_splash_info *sinfo, enum splash_connector_type conn)
+{
+ struct msm_gem_object *msm_obj = to_msm_bo(sinfo->obj[conn]);
+
+ if (!msm_obj)
+ return -EINVAL;
+
+ if (mmu->funcs && mmu->funcs->unmap)
+ mmu->funcs->unmap(mmu, sinfo->splash_mem_paddr[conn],
+ msm_obj->sgt, NULL);
+
+ _sde_splash_free_bootup_memory_to_system(sinfo->splash_mem_paddr[conn],
+ sinfo->splash_mem_size[conn]);
+
+ _sde_splash_destroy_gem_object(msm_obj);
+
+ return 0;
+}
+
+__ref int sde_splash_init(struct sde_power_handle *phandle, struct msm_kms *kms)
+{
+ struct sde_kms *sde_kms;
+ struct sde_splash_info *sinfo;
+ int i = 0;
+
+ if (!phandle || !kms) {
+ SDE_ERROR("invalid phandle/kms\n");
+ return -EINVAL;
+ }
+
+ sde_kms = to_sde_kms(kms);
+ sinfo = &sde_kms->splash_info;
+
+ sinfo->dsi_connector_cnt = 0;
+ sinfo->hdmi_connector_cnt = 0;
+
+ sde_power_data_bus_bandwidth_ctrl(phandle,
+ sde_kms->core_client, true);
+
+ for (i = 0; i < sinfo->splash_mem_num; i++) {
+ if (!memblock_is_reserved(sinfo->splash_mem_paddr[i])) {
+ SDE_ERROR("failed to reserve memory\n");
+
+ /* withdraw the vote when failed. */
+ sde_power_data_bus_bandwidth_ctrl(phandle,
+ sde_kms->core_client, false);
+
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+void sde_splash_destroy(struct sde_splash_info *sinfo,
+ struct sde_power_handle *phandle,
+ struct sde_power_client *pclient)
+{
+ struct msm_gem_object *msm_obj;
+ int i = 0;
+
+ if (!sinfo || !phandle || !pclient) {
+ SDE_ERROR("invalid sde_kms/phandle/pclient\n");
+ return;
+ }
+
+ for (i = 0; i < sinfo->splash_mem_num; i++) {
+ msm_obj = to_msm_bo(sinfo->obj[i]);
+
+ if (msm_obj)
+ _sde_splash_destroy_gem_object(msm_obj);
+ }
+
+ sde_power_data_bus_bandwidth_ctrl(phandle, pclient, false);
+
+ _sde_splash_destroy_splash_node(sinfo);
+}
+
+/*
+ * sde_splash_parse_dt.
+ * In the function, it will parse and reserve two kinds of memory node.
+ * First is to get the reserved memory for display buffers.
+ * Second is to get the memory node LK's code stack is running on.
+ */
+int sde_splash_parse_dt(struct drm_device *dev)
+{
+ struct msm_drm_private *priv = dev->dev_private;
+ struct sde_kms *sde_kms;
+ struct sde_splash_info *sinfo;
+
+ if (!priv || !priv->kms) {
+ SDE_ERROR("Invalid kms\n");
+ return -EINVAL;
+ }
+
+ sde_kms = to_sde_kms(priv->kms);
+ sinfo = &sde_kms->splash_info;
+
+ if (_sde_splash_parse_dt_get_display_node(dev, sinfo)) {
+ SDE_ERROR("get display node failed\n");
+ return -EINVAL;
+ }
+
+ if (_sde_splash_parse_dt_get_lk_pool_node(dev, sinfo)) {
+ SDE_ERROR("get LK pool node failed\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int sde_splash_get_handoff_status(struct msm_kms *kms)
+{
+ uint32_t intf_sel = 0;
+ uint32_t split_display = 0;
+ uint32_t num_of_display_on = 0;
+ uint32_t i = 0;
+ struct sde_kms *sde_kms = to_sde_kms(kms);
+ struct sde_rm *rm;
+ struct sde_hw_blk_reg_map *c;
+ struct sde_splash_info *sinfo;
+ struct sde_mdss_cfg *catalog;
+
+ sinfo = &sde_kms->splash_info;
+ if (!sinfo) {
+ SDE_ERROR("%s(%d): invalid splash info\n",
+ __func__, __LINE__);
+ return -EINVAL;
+ }
+
+ rm = &sde_kms->rm;
+
+ if (!rm || !rm->hw_mdp) {
+ SDE_ERROR("invalid rm.\n");
+ return -EINVAL;
+ }
+
+ c = &rm->hw_mdp->hw;
+ if (c) {
+ intf_sel = SDE_REG_READ(c, DISP_INTF_SEL);
+ split_display = SDE_REG_READ(c, SPLIT_DISPLAY_EN);
+ }
+
+ catalog = sde_kms->catalog;
+
+ if (intf_sel != 0) {
+ for (i = 0; i < catalog->intf_count; i++)
+ if ((intf_sel >> i*8) & 0x000000FF)
+ num_of_display_on++;
+
+ /*
+ * For split display enabled - DSI0, DSI1 interfaces are
+ * considered as single display. So decrement
+ * 'num_of_display_on' by 1
+ */
+ if (split_display)
+ num_of_display_on--;
+ }
+
+ if (num_of_display_on) {
+ sinfo->handoff = true;
+ sinfo->program_scratch_regs = true;
+ } else {
+ sinfo->handoff = false;
+ sinfo->program_scratch_regs = false;
+ }
+
+ sinfo->lk_is_exited = false;
+
+ return 0;
+}
+
+int sde_splash_smmu_map(struct drm_device *dev, struct msm_mmu *mmu,
+ struct sde_splash_info *sinfo)
+{
+ struct msm_gem_object *msm_obj;
+ int i = 0, ret = 0;
+
+ if (!mmu || !sinfo)
+ return -EINVAL;
+
+ /* first is to construct drm_gem_objects for splash memory */
+ if (_sde_splash_gem_new(dev, sinfo))
+ return -ENOMEM;
+
+ /* second is to contruct sgt table for calling smmu map */
+ for (i = 0; i < sinfo->splash_mem_num; i++) {
+ if (_sde_splash_get_pages(sinfo->obj[i],
+ sinfo->splash_mem_paddr[i]))
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < sinfo->splash_mem_num; i++) {
+ msm_obj = to_msm_bo(sinfo->obj[i]);
+
+ if (mmu->funcs && mmu->funcs->map) {
+ ret = mmu->funcs->map(mmu, sinfo->splash_mem_paddr[i],
+ msm_obj->sgt, IOMMU_READ | IOMMU_NOEXEC, NULL);
+
+ if (!ret) {
+ SDE_ERROR("Map blk %d @%pK failed.\n",
+ i, (void *)sinfo->splash_mem_paddr[i]);
+ return ret;
+ }
+ }
+ }
+
+ return ret ? 0 : -ENOMEM;
+}
+
+void sde_splash_setup_connector_count(struct sde_splash_info *sinfo,
+ int connector_type)
+{
+ switch (connector_type) {
+ case DRM_MODE_CONNECTOR_HDMIA:
+ sinfo->hdmi_connector_cnt++;
+ break;
+ case DRM_MODE_CONNECTOR_DSI:
+ sinfo->dsi_connector_cnt++;
+ break;
+ default:
+ SDE_ERROR("invalid connector_type %d\n", connector_type);
+ }
+}
+
+int sde_splash_clean_up_free_resource(struct msm_kms *kms,
+ struct sde_power_handle *phandle, int connector_type)
+{
+ struct sde_kms *sde_kms;
+ struct sde_splash_info *sinfo;
+ struct msm_mmu *mmu;
+ int ret = 0;
+ static bool hdmi_is_released;
+ static bool dsi_is_released;
+
+ if (!phandle || !kms) {
+ SDE_ERROR("invalid phandle/kms.\n");
+ return -EINVAL;
+ }
+
+ sde_kms = to_sde_kms(kms);
+ sinfo = &sde_kms->splash_info;
+ if (!sinfo) {
+ SDE_ERROR("%s(%d): invalid splash info\n", __func__, __LINE__);
+ return -EINVAL;
+ }
+
+ /* When both hdmi's and dsi's resource are freed,
+ * 1. Destroy splash node objects.
+ * 2. Decrease ref count in bandwidth voting function.
+ */
+ if (sinfo->hdmi_connector_cnt == 0 &&
+ sinfo->dsi_connector_cnt == 0) {
+ DRM_INFO("HDMI and DSI resource handoff is completed\n");
+
+ sinfo->lk_is_exited = false;
+
+ _sde_splash_destroy_splash_node(sinfo);
+
+ sde_power_data_bus_bandwidth_ctrl(phandle,
+ sde_kms->core_client, false);
+ return 0;
+ }
+
+ mmu = sde_kms->aspace[0]->mmu;
+
+ switch (connector_type) {
+ case DRM_MODE_CONNECTOR_HDMIA:
+ if (!hdmi_is_released)
+ sinfo->hdmi_connector_cnt--;
+
+ if ((sinfo->hdmi_connector_cnt == 0) && (!hdmi_is_released)) {
+ hdmi_is_released = true;
+
+ ret = _sde_splash_free_resource(mmu,
+ sinfo, SPLASH_HDMI);
+ }
+ break;
+ case DRM_MODE_CONNECTOR_DSI:
+ if (!dsi_is_released)
+ sinfo->dsi_connector_cnt--;
+
+ if ((sinfo->dsi_connector_cnt == 0) && (!dsi_is_released)) {
+ dsi_is_released = true;
+
+ ret = _sde_splash_free_resource(mmu,
+ sinfo, SPLASH_DSI);
+ }
+ break;
+ default:
+ ret = -EINVAL;
+ SDE_ERROR("%s: invalid connector_type %d\n",
+ __func__, connector_type);
+ }
+
+ return ret;
+}
+
+/*
+ * In below function, it will
+ * 1. Notify LK to exit and wait for exiting is done.
+ * 2. Set DOMAIN_ATTR_EARLY_MAP to 1 to enable stage 1 translation in iommu.
+ */
+int sde_splash_clean_up_exit_lk(struct msm_kms *kms)
+{
+ struct sde_splash_info *sinfo;
+ struct msm_mmu *mmu;
+ struct sde_kms *sde_kms = to_sde_kms(kms);
+ int ret;
+
+ sinfo = &sde_kms->splash_info;
+
+ if (!sinfo) {
+ SDE_ERROR("%s(%d): invalid splash info\n", __func__, __LINE__);
+ return -EINVAL;
+ }
+
+ /* Monitor LK's status and tell it to exit. */
+ if (sinfo->program_scratch_regs) {
+ if (_sde_splash_lk_check(sde_kms->hw_intr))
+ _sde_splash_notify_lk_exit(sde_kms->hw_intr);
+
+ sinfo->handoff = false;
+ sinfo->program_scratch_regs = false;
+ }
+
+ if (!sde_kms->aspace[0] || !sde_kms->aspace[0]->mmu) {
+ /* We do not return fault value here, to ensure
+ * flag "lk_is_exited" is set.
+ */
+ SDE_ERROR("invalid mmu\n");
+ WARN_ON(1);
+ } else {
+ mmu = sde_kms->aspace[0]->mmu;
+ /* After LK has exited, set early domain map attribute
+ * to 1 to enable stage 1 translation in iommu driver.
+ */
+ if (mmu->funcs && mmu->funcs->set_property) {
+ ret = mmu->funcs->set_property(mmu,
+ DOMAIN_ATTR_EARLY_MAP, &sinfo->handoff);
+
+ if (ret)
+ SDE_ERROR("set_property failed\n");
+ }
+ }
+
+ sinfo->lk_is_exited = true;
+ return 0;
+}
diff --git a/drivers/gpu/drm/msm/sde/sde_splash.h b/drivers/gpu/drm/msm/sde/sde_splash.h
new file mode 100644
index 000000000000..47965693f0b8
--- /dev/null
+++ b/drivers/gpu/drm/msm/sde/sde_splash.h
@@ -0,0 +1,124 @@
+/**
+ * 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.
+ */
+#ifndef SDE_SPLASH_H_
+#define SDE_SPLASH_H_
+
+#include "msm_kms.h"
+#include "msm_mmu.h"
+
+enum splash_connector_type {
+ SPLASH_DSI = 0,
+ SPLASH_HDMI,
+};
+
+struct sde_splash_info {
+ /* handoff flag */
+ bool handoff;
+
+ /* flag of display scratch registers */
+ bool program_scratch_regs;
+
+ /* to indicate LK is totally exited */
+ bool lk_is_exited;
+
+ /* memory node used for display buffer */
+ uint32_t splash_mem_num;
+
+ /* physical address of memory node for display buffer */
+ phys_addr_t *splash_mem_paddr;
+
+ /* size of memory node */
+ size_t *splash_mem_size;
+
+ /* constructed gem objects for smmu mapping */
+ struct drm_gem_object **obj;
+
+ /* physical address of lk pool */
+ phys_addr_t lk_pool_paddr;
+
+ /* memory size of lk pool */
+ size_t lk_pool_size;
+
+ /* registered hdmi connector count */
+ uint32_t hdmi_connector_cnt;
+
+ /* registered dst connector count */
+ uint32_t dsi_connector_cnt;
+};
+
+/* APIs for early splash handoff functions */
+
+/**
+ * sde_splash_get_handoff_status.
+ *
+ * This function will read DISP_INTF_SEL regsiter to get
+ * the status of early splash.
+ */
+int sde_splash_get_handoff_status(struct msm_kms *kms);
+
+/**
+ * sde_splash_init
+ *
+ * This function will do bandwidth vote and reserved memory
+ */
+int sde_splash_init(struct sde_power_handle *phandle, struct msm_kms *kms);
+
+/**
+ *sde_splash_setup_connector_count
+ *
+ * To count connector numbers for DSI and HDMI respectively.
+ */
+void sde_splash_setup_connector_count(struct sde_splash_info *sinfo,
+ int connector_type);
+
+/**
+ * sde_splash_clean_up_exit_lk.
+ *
+ * Tell LK to exit, and clean up the resource.
+ */
+int sde_splash_clean_up_exit_lk(struct msm_kms *kms);
+
+/**
+ * sde_splash_clean_up_free_resource.
+ *
+ * According to input connector_type, free
+ * HDMI's and DSI's resource respectively.
+ */
+int sde_splash_clean_up_free_resource(struct msm_kms *kms,
+ struct sde_power_handle *phandle, int connector_type);
+
+/**
+ * sde_splash_parse_dt.
+ *
+ * Parse reserved memory block from DT for early splash.
+ */
+int sde_splash_parse_dt(struct drm_device *dev);
+
+/**
+ * sde_splash_smmu_map.
+ *
+ * Map the physical memory LK visited into iommu driver.
+ */
+int sde_splash_smmu_map(struct drm_device *dev, struct msm_mmu *mmu,
+ struct sde_splash_info *sinfo);
+
+/**
+ * sde_splash_destroy
+ *
+ * Destroy the resource in failed case.
+ */
+void sde_splash_destroy(struct sde_splash_info *sinfo,
+ struct sde_power_handle *phandle,
+ struct sde_power_client *pclient);
+
+#endif