blob: 829806d55cfd9e2b3568090611749092d30f48f3 [file] [log] [blame]
/* Copyright (c) 2013-2014, 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/errno.h>
#include <linux/kernel.h>
#include <linux/kthread.h>
#include <linux/memblock.h>
#include <linux/bootmem.h>
#include <linux/iommu.h>
#include <linux/fb.h>
#include "mdss_fb.h"
#include "mdss_mdp.h"
#include "splash.h"
#include "mdss_mdp_splash_logo.h"
#define INVALID_PIPE_INDEX 0xFFFF
#define MAX_FRAME_DONE_COUNT_WAIT 2
static int mdss_mdp_splash_alloc_memory(struct msm_fb_data_type *mfd,
uint32_t size)
{
int rc;
struct msm_fb_splash_info *sinfo;
unsigned long buf_size = size;
struct mdss_data_type *mdata;
if (!mfd || !size)
return -EINVAL;
mdata = mfd_to_mdata(mfd);
sinfo = &mfd->splash_info;
if (!mdata || !mdata->iclient || sinfo->splash_buffer)
return -EINVAL;
sinfo->ion_handle = ion_alloc(mdata->iclient, size, SZ_4K,
ION_HEAP(ION_SYSTEM_HEAP_ID), 0);
if (IS_ERR_OR_NULL(sinfo->ion_handle)) {
pr_err("ion memory allocation failed\n");
rc = PTR_RET(sinfo->ion_handle);
goto end;
}
rc = ion_map_iommu(mdata->iclient, sinfo->ion_handle,
mdss_get_iommu_domain(MDSS_IOMMU_DOMAIN_UNSECURE),
0, SZ_4K, 0, (unsigned long *)&sinfo->iova,
(unsigned long *)&buf_size, 0, 0);
if (rc) {
pr_err("ion memory map failed\n");
goto imap_err;
}
sinfo->splash_buffer = ion_map_kernel(mdata->iclient,
sinfo->ion_handle);
if (IS_ERR_OR_NULL(sinfo->splash_buffer)) {
pr_err("ion kernel memory mapping failed\n");
rc = IS_ERR(sinfo->splash_buffer);
goto kmap_err;
}
return rc;
kmap_err:
ion_unmap_iommu(mdata->iclient, sinfo->ion_handle,
mdss_get_iommu_domain(MDSS_IOMMU_DOMAIN_UNSECURE), 0);
imap_err:
ion_free(mdata->iclient, sinfo->ion_handle);
end:
return rc;
}
static void mdss_mdp_splash_free_memory(struct msm_fb_data_type *mfd)
{
struct msm_fb_splash_info *sinfo;
struct mdss_data_type *mdata;
if (!mfd)
return;
sinfo = &mfd->splash_info;
mdata = mfd_to_mdata(mfd);
if (!mdata || !mdata->iclient || !sinfo->ion_handle)
return;
ion_unmap_kernel(mdata->iclient, sinfo->ion_handle);
ion_unmap_iommu(mdata->iclient, sinfo->ion_handle,
mdss_get_iommu_domain(MDSS_IOMMU_DOMAIN_UNSECURE), 0);
ion_free(mdata->iclient, sinfo->ion_handle);
sinfo->splash_buffer = NULL;
}
static int mdss_mdp_splash_iommu_attach(struct msm_fb_data_type *mfd)
{
struct iommu_domain *domain;
struct mdss_overlay_private *mdp5_data = mfd_to_mdp5_data(mfd);
int rc, ret;
/*
* iommu dynamic attach for following conditions.
* 1. it is still not attached
* 2. MDP hardware version supports the feature
* 3. configuration is with valid splash buffer
*/
if (is_mdss_iommu_attached() ||
!mfd->panel_info->cont_splash_enabled ||
!mdss_mdp_iommu_dyn_attach_supported(mdp5_data->mdata) ||
!mdp5_data->splash_mem_addr ||
!mdp5_data->splash_mem_size) {
pr_debug("dynamic attach is not supported\n");
return -EPERM;
}
domain = msm_get_iommu_domain(mdss_get_iommu_domain(
MDSS_IOMMU_DOMAIN_UNSECURE));
if (!domain) {
pr_debug("mdss iommu domain get failed\n");
return -EINVAL;
}
rc = iommu_map(domain, mdp5_data->splash_mem_addr,
mdp5_data->splash_mem_addr,
mdp5_data->splash_mem_size, IOMMU_READ);
if (rc) {
pr_debug("iommu memory mapping failed rc=%d\n", rc);
} else {
ret = mdss_iommu_ctrl(1);
if (IS_ERR_VALUE(ret)) {
pr_err("mdss iommu attach failed\n");
iommu_unmap(domain, mdp5_data->splash_mem_addr,
mdp5_data->splash_mem_size);
} else {
mfd->splash_info.iommu_dynamic_attached = true;
}
}
return rc;
}
static void mdss_mdp_splash_unmap_splash_mem(struct msm_fb_data_type *mfd)
{
struct iommu_domain *domain;
struct mdss_overlay_private *mdp5_data = mfd_to_mdp5_data(mfd);
if (mfd->splash_info.iommu_dynamic_attached) {
domain = msm_get_iommu_domain(mdss_get_iommu_domain(
MDSS_IOMMU_DOMAIN_UNSECURE));
if (!domain) {
pr_err("mdss iommu domain get failed\n");
return;
}
iommu_unmap(domain, mdp5_data->splash_mem_addr,
mdp5_data->splash_mem_size);
mdss_iommu_ctrl(0);
mfd->splash_info.iommu_dynamic_attached = false;
}
}
void mdss_mdp_release_splash_pipe(struct msm_fb_data_type *mfd)
{
struct msm_fb_splash_info *sinfo;
if (!mfd || !mfd->splash_info.splash_pipe_allocated)
return;
sinfo = &mfd->splash_info;
if (sinfo->pipe_ndx[0] != INVALID_PIPE_INDEX)
mdss_mdp_overlay_release(mfd, sinfo->pipe_ndx[0]);
if (sinfo->pipe_ndx[1] != INVALID_PIPE_INDEX)
mdss_mdp_overlay_release(mfd, sinfo->pipe_ndx[1]);
sinfo->splash_pipe_allocated = false;
}
int mdss_mdp_splash_cleanup(struct msm_fb_data_type *mfd,
bool use_borderfill)
{
struct mdss_overlay_private *mdp5_data = mfd_to_mdp5_data(mfd);
struct mdss_mdp_ctl *ctl = mdp5_data->ctl;
int rc = 0;
if (!mfd || !mdp5_data)
return -EINVAL;
if (mfd->splash_info.iommu_dynamic_attached ||
!mfd->panel_info->cont_splash_enabled)
goto end;
if (use_borderfill && mdp5_data->handoff) {
/*
* Set up border-fill on the handed off pipes.
* This is needed to ensure that there are no memory
* accesses prior to attaching iommu during continuous
* splash screen case. However, for command mode
* displays, this is not necessary since the panels can
* refresh from their internal memory if no data is sent
* out on the dsi lanes.
*/
if (mdp5_data->handoff && ctl && ctl->is_video_mode) {
rc = mdss_mdp_display_commit(ctl, NULL);
if (!IS_ERR_VALUE(rc)) {
mdss_mdp_display_wait4comp(ctl);
} else {
/*
* Since border-fill setup failed, we
* need to ensure that we turn off the
* MDP timing generator before attaching
* iommu
*/
pr_err("failed to set BF at handoff\n");
mdp5_data->handoff = false;
}
}
}
if (rc || mdp5_data->handoff) {
/* Add all the handed off pipes to the cleanup list */
mdss_mdp_handoff_cleanup_pipes(mfd, MDSS_MDP_PIPE_TYPE_RGB);
mdss_mdp_handoff_cleanup_pipes(mfd, MDSS_MDP_PIPE_TYPE_VIG);
mdss_mdp_handoff_cleanup_pipes(mfd, MDSS_MDP_PIPE_TYPE_DMA);
}
mdss_mdp_ctl_splash_finish(ctl, mdp5_data->handoff);
if (mdp5_data->splash_mem_addr) {
/* Give back the reserved memory to the system */
memblock_free(mdp5_data->splash_mem_addr,
mdp5_data->splash_mem_size);
free_bootmem_late(mdp5_data->splash_mem_addr,
mdp5_data->splash_mem_size);
}
mdss_mdp_footswitch_ctrl_splash(0);
end:
return rc;
}
static struct mdss_mdp_pipe *mdss_mdp_splash_get_pipe(
struct msm_fb_data_type *mfd,
struct mdp_overlay *req)
{
struct mdss_mdp_pipe *pipe;
int ret;
struct mdss_mdp_data *buf;
uint32_t image_size = SPLASH_IMAGE_WIDTH * SPLASH_IMAGE_HEIGHT
* SPLASH_IMAGE_BPP;
ret = mdss_mdp_overlay_pipe_setup(mfd, req, &pipe);
if (ret)
return NULL;
if (mdss_mdp_pipe_map(pipe)) {
pr_err("unable to map base pipe\n");
return NULL;
}
buf = &pipe->back_buf;
buf->p[0].addr = mfd->splash_info.iova;
buf->p[0].len = image_size;
buf->num_planes = 1;
mdss_mdp_pipe_unmap(pipe);
return pipe;
}
static int mdss_mdp_splash_kickoff(struct msm_fb_data_type *mfd,
struct mdss_mdp_img_rect *src_rect,
struct mdss_mdp_img_rect *dest_rect)
{
struct mdss_mdp_pipe *pipe;
struct fb_info *fbi;
struct mdp_overlay req;
struct mdss_overlay_private *mdp5_data;
struct mdss_data_type *mdata;
struct mdss_mdp_mixer *mixer;
int ret;
bool use_single_pipe = false;
struct msm_fb_splash_info *sinfo;
if (!mfd)
return -EINVAL;
fbi = mfd->fbi;
mdp5_data = mfd_to_mdp5_data(mfd);
mdata = mfd_to_mdata(mfd);
sinfo = &mfd->splash_info;
if (!mdp5_data || !mdp5_data->ctl)
return -EINVAL;
if (mutex_lock_interruptible(&mdp5_data->ov_lock))
return -EINVAL;
ret = mdss_mdp_overlay_start(mfd);
if (ret) {
pr_err("unable to start overlay %d (%d)\n", mfd->index, ret);
goto end;
}
mixer = mdss_mdp_mixer_get(mdp5_data->ctl, MDSS_MDP_MIXER_MUX_LEFT);
if (!mixer) {
pr_err("unable to retrieve mixer\n");
ret = -EINVAL;
goto end;
}
memset(&req, 0, sizeof(struct mdp_overlay));
/*
* use single pipe for
* 1. split display disabled
* 2. splash image is only on one side of panel
*/
use_single_pipe =
!mfd->split_display ||
(mfd->split_display &&
((dest_rect->x + dest_rect->w) < mfd->split_fb_left ||
dest_rect->x > mfd->split_fb_left));
req.src.width = src_rect->w;
if (use_single_pipe)
req.src_rect.w = src_rect->w;
else
req.src_rect.w = min_t(u16, mixer->width, src_rect->w >> 1);
req.dst_rect.w = req.src_rect.w;
req.src.height = req.dst_rect.h = req.src_rect.h =
src_rect->h;
req.src.format = SPLASH_IMAGE_FORMAT;
req.id = MSMFB_NEW_REQUEST;
req.z_order = MDSS_MDP_STAGE_0;
req.alpha = 0xff;
req.transp_mask = MDP_TRANSP_NOP;
req.dst_rect.x = dest_rect->x;
req.dst_rect.y = dest_rect->y;
pipe = mdss_mdp_splash_get_pipe(mfd, &req);
if (!pipe) {
pr_err("unable to allocate base pipe\n");
ret = -EINVAL;
goto end;
}
sinfo->pipe_ndx[0] = pipe->ndx;
if (!use_single_pipe) {
req.id = MSMFB_NEW_REQUEST;
req.src_rect.x = src_rect->x + min_t(u16, mixer->width,
src_rect->w - req.src_rect.w);
req.dst_rect.x = mixer->width;
pipe = mdss_mdp_splash_get_pipe(mfd, &req);
if (!pipe) {
pr_err("unable to allocate right base pipe\n");
mdss_mdp_overlay_release(mfd, sinfo->pipe_ndx[0]);
ret = -EINVAL;
goto end;
}
sinfo->pipe_ndx[1] = pipe->ndx;
}
mutex_unlock(&mdp5_data->ov_lock);
ret = mfd->mdp.kickoff_fnc(mfd, NULL);
if (ret) {
pr_err("error in displaying image\n");
mdss_mdp_overlay_release(mfd, sinfo->pipe_ndx[0] |
sinfo->pipe_ndx[1]);
}
return ret;
end:
sinfo->pipe_ndx[0] = INVALID_PIPE_INDEX;
sinfo->pipe_ndx[1] = INVALID_PIPE_INDEX;
mutex_unlock(&mdp5_data->ov_lock);
return ret;
}
static int mdss_mdp_display_splash_image(struct msm_fb_data_type *mfd)
{
int rc = 0;
struct fb_info *fbi;
uint32_t image_len = SPLASH_IMAGE_WIDTH * SPLASH_IMAGE_HEIGHT
* SPLASH_IMAGE_BPP;
struct mdss_mdp_img_rect src_rect, dest_rect;
struct msm_fb_splash_info *sinfo;
if (!mfd || !mfd->fbi) {
pr_err("invalid input parameter\n");
rc = -EINVAL;
goto end;
}
fbi = mfd->fbi;
sinfo = &mfd->splash_info;
if (SPLASH_IMAGE_WIDTH > fbi->var.xres ||
SPLASH_IMAGE_HEIGHT > fbi->var.yres ||
SPLASH_IMAGE_BPP > (fbi->var.bits_per_pixel >> 3)) {
pr_err("invalid splash parameter configuration\n");
rc = -EINVAL;
goto end;
}
sinfo->pipe_ndx[0] = INVALID_PIPE_INDEX;
sinfo->pipe_ndx[1] = INVALID_PIPE_INDEX;
src_rect.x = 0;
src_rect.y = 0;
dest_rect.w = src_rect.w = SPLASH_IMAGE_WIDTH;
dest_rect.h = src_rect.h = SPLASH_IMAGE_HEIGHT;
dest_rect.x = (fbi->var.xres >> 1) - (SPLASH_IMAGE_WIDTH >> 1);
dest_rect.y = (fbi->var.yres >> 1) - (SPLASH_IMAGE_HEIGHT >> 1);
rc = mdss_mdp_splash_alloc_memory(mfd, image_len);
if (rc) {
pr_err("splash buffer allocation failed\n");
goto end;
}
memcpy(sinfo->splash_buffer, splash_bgr888_image, image_len);
rc = mdss_mdp_splash_iommu_attach(mfd);
if (rc)
pr_debug("iommu dynamic attach failed\n");
rc = mdss_mdp_splash_kickoff(mfd, &src_rect, &dest_rect);
if (rc)
pr_err("splash image display failed\n");
else
sinfo->splash_pipe_allocated = true;
end:
return rc;
}
static int mdss_mdp_splash_ctl_cb(struct notifier_block *self,
unsigned long event, void *data)
{
struct msm_fb_splash_info *sinfo = container_of(self,
struct msm_fb_splash_info, notifier);
struct msm_fb_data_type *mfd;
if (!sinfo)
goto done;
mfd = container_of(sinfo, struct msm_fb_data_type, splash_info);
if (!mfd)
goto done;
if (event != MDP_NOTIFY_FRAME_DONE)
goto done;
if (!sinfo->frame_done_count) {
mdss_mdp_splash_unmap_splash_mem(mfd);
mdss_mdp_splash_cleanup(mfd, false);
/* wait for 2 frame done events before releasing memory */
} else if (sinfo->frame_done_count > MAX_FRAME_DONE_COUNT_WAIT &&
sinfo->splash_thread) {
complete(&sinfo->frame_done);
sinfo->splash_thread = NULL;
}
/* increase frame done count after pipes are staged from other client */
if (!sinfo->splash_pipe_allocated)
sinfo->frame_done_count++;
done:
return NOTIFY_OK;
}
static int mdss_mdp_splash_thread(void *data)
{
struct msm_fb_data_type *mfd = data;
struct mdss_overlay_private *mdp5_data = mfd_to_mdp5_data(mfd);
int ret = -EINVAL;
if (!mfd) {
pr_err("invalid input parameter\n");
goto end;
}
lock_fb_info(mfd->fbi);
ret = fb_blank(mfd->fbi, FB_BLANK_UNBLANK);
if (ret) {
pr_err("can't turn on fb!\n");
goto end;
}
unlock_fb_info(mfd->fbi);
mutex_lock(&mfd->bl_lock);
mfd->bl_updated = true;
mdss_fb_set_backlight(mfd, mfd->panel_info->bl_max >> 1);
mutex_unlock(&mfd->bl_lock);
init_completion(&mfd->splash_info.frame_done);
mfd->splash_info.notifier.notifier_call = mdss_mdp_splash_ctl_cb;
mdss_mdp_ctl_notifier_register(mdp5_data->ctl,
&mfd->splash_info.notifier);
ret = mdss_mdp_display_splash_image(mfd);
if (ret) {
/*
* keep thread alive to release dynamically allocated
* resources
*/
pr_err("splash image display failed\n");
}
/* wait for second display complete to release splash resources */
ret = wait_for_completion_killable(&mfd->splash_info.frame_done);
mdss_mdp_splash_free_memory(mfd);
mdss_mdp_ctl_notifier_unregister(mdp5_data->ctl,
&mfd->splash_info.notifier);
end:
return ret;
}
static __ref int mdss_mdp_splash_parse_dt(struct msm_fb_data_type *mfd)
{
struct platform_device *pdev = mfd->pdev;
struct mdss_overlay_private *mdp5_mdata = mfd_to_mdp5_data(mfd);
int len = 0, rc = 0;
u32 offsets[2];
mfd->splash_info.splash_logo_enabled =
of_property_read_bool(pdev->dev.of_node,
"qcom,mdss-fb-splash-logo-enabled");
of_find_property(pdev->dev.of_node, "qcom,memblock-reserve", &len);
if (len < 1) {
pr_debug("mem reservation for splash screen fb not present\n");
rc = -EINVAL;
goto error;
}
len = len / sizeof(u32);
rc = of_property_read_u32_array(pdev->dev.of_node,
"qcom,memblock-reserve", offsets, len);
if (rc) {
pr_debug("error reading mem reserve settings for fb\n");
goto error;
}
if (!memblock_is_reserved(offsets[0])) {
pr_debug("failed to reserve memory for fb splash\n");
rc = -EINVAL;
goto error;
}
mdp5_mdata->splash_mem_addr = offsets[0];
mdp5_mdata->splash_mem_size = offsets[1];
pr_debug("memaddr=%x size=%x\n", mdp5_mdata->splash_mem_addr,
mdp5_mdata->splash_mem_size);
error:
if (!rc && !mfd->panel_info->cont_splash_enabled &&
mdp5_mdata->splash_mem_addr) {
pr_debug("mem reservation not reqd if cont splash disabled\n");
memblock_free(mdp5_mdata->splash_mem_addr,
mdp5_mdata->splash_mem_size);
free_bootmem_late(mdp5_mdata->splash_mem_addr,
mdp5_mdata->splash_mem_size);
} else if (rc && mfd->panel_info->cont_splash_enabled) {
pr_err("no rsvd mem found in DT for splash screen\n");
} else {
rc = 0;
}
return rc;
}
int mdss_mdp_splash_init(struct msm_fb_data_type *mfd)
{
int rc;
if (!mfd) {
rc = -EINVAL;
goto end;
}
rc = mdss_mdp_splash_parse_dt(mfd);
if (rc) {
pr_err("splash memory reserve failed\n");
goto end;
}
if (!mfd->splash_info.splash_logo_enabled) {
rc = -EINVAL;
goto end;
}
mfd->splash_info.splash_thread = kthread_run(mdss_mdp_splash_thread,
mfd, "mdss_fb_splash");
if (IS_ERR(mfd->splash_info.splash_thread)) {
pr_err("unable to start splash thread %d\n", mfd->index);
mfd->splash_info.splash_thread = NULL;
}
end:
return rc;
}