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 +#include +#include + +#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