/* Copyright (c) 2011-2013, 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 #include #include #include #include #include #include #include #include #include #include #include #include #include "mdp.h" #include "msm_fb.h" #include "mdp4.h" enum { WB_OPEN, WB_START, WB_STOPING, WB_STOP }; enum { REGISTERED, IN_FREE_QUEUE, IN_BUSY_QUEUE, WITH_CLIENT }; #define MAX_CONTROLLER 1 #define VSYNC_EXPIRE_TICK 0 static struct vsycn_ctrl { struct device *dev; int inited; int update_ndx; u32 ov_koff; u32 ov_done; atomic_t suspend; struct mutex update_lock; struct completion ov_comp; spinlock_t spin_lock; struct msm_fb_data_type *mfd; struct mdp4_overlay_pipe *base_pipe; struct vsync_update vlist[2]; struct work_struct clk_work; } vsync_ctrl_db[MAX_CONTROLLER]; static void vsync_irq_enable(int intr, int term) { unsigned long flag; spin_lock_irqsave(&mdp_spin_lock, flag); /* no need to clrear other interrupts for comamnd mode */ mdp_intr_mask |= intr; outp32(MDP_INTR_ENABLE, mdp_intr_mask); mdp_enable_irq(term); spin_unlock_irqrestore(&mdp_spin_lock, flag); } static void vsync_irq_disable(int intr, int term) { unsigned long flag; spin_lock_irqsave(&mdp_spin_lock, flag); /* no need to clrear other interrupts for comamnd mode */ mdp_intr_mask &= ~intr; outp32(MDP_INTR_ENABLE, mdp_intr_mask); mdp_disable_irq_nosync(term); spin_unlock_irqrestore(&mdp_spin_lock, flag); } static int mdp4_overlay_writeback_update(struct msm_fb_data_type *mfd); static void mdp4_wfd_queue_wakeup(struct msm_fb_data_type *mfd, struct msmfb_writeback_data_list *node); static void mdp4_wfd_dequeue_update(struct msm_fb_data_type *mfd, struct msmfb_writeback_data_list **wfdnode); int mdp4_overlay_writeback_on(struct platform_device *pdev) { struct msm_fb_data_type *mfd; struct fb_info *fbi; uint8 *buf; struct mdp4_overlay_pipe *pipe; int bpp; int ret; uint32 data; struct vsycn_ctrl *vctrl; int cndx = 0; mfd = (struct msm_fb_data_type *)platform_get_drvdata(pdev); if (!mfd) return -ENODEV; if (mfd->key != MFD_KEY) return -EINVAL; vctrl = &vsync_ctrl_db[cndx]; vctrl->mfd = mfd; vctrl->dev = mfd->fbi->dev; fbi = mfd->fbi; bpp = fbi->var.bits_per_pixel / 8; buf = (uint8 *) fbi->fix.smem_start; buf += fbi->var.xoffset * bpp + fbi->var.yoffset * fbi->fix.line_length; /* MDP cmd block enable */ mdp_clk_ctrl(1); if (vctrl->base_pipe == NULL) { pipe = mdp4_overlay_pipe_alloc(OVERLAY_TYPE_BF, MDP4_MIXER2); if (pipe == NULL) { pr_info("%s: pipe_alloc failed\n", __func__); return -EIO; } pipe->pipe_used++; pipe->mixer_stage = MDP4_MIXER_STAGE_BASE; pipe->mixer_num = MDP4_MIXER2; pipe->src_format = MDP_ARGB_8888; mdp4_overlay_panel_mode(pipe->mixer_num, MDP4_PANEL_WRITEBACK); ret = mdp4_overlay_format2pipe(pipe); if (ret < 0) pr_info("%s: format2type failed\n", __func__); vctrl->base_pipe = pipe; /* keep it */ } else { pipe = vctrl->base_pipe; } ret = panel_next_on(pdev); /* MDP_LAYERMIXER_WB_MUX_SEL to use mixer1 axi for mixer2 writeback */ if (hdmi_prim_display) data = 0x01; else data = 0x02; outpdw(MDP_BASE + 0x100F4, data); MDP_OUTP(MDP_BASE + MDP4_OVERLAYPROC1_BASE + 0x5004, ((0x0 & 0xFFF) << 16) | /* 12-bit B */ (0x0 & 0xFFF)); /* 12-bit G */ /* MSP_BORDER_COLOR */ MDP_OUTP(MDP_BASE + MDP4_OVERLAYPROC1_BASE + 0x5008, (0x0 & 0xFFF)); /* 12-bit R */ mdp_clk_ctrl(0); return ret; } int mdp4_overlay_writeback_off(struct platform_device *pdev) { int cndx = 0; struct msm_fb_data_type *mfd; struct vsycn_ctrl *vctrl; struct mdp4_overlay_pipe *pipe; int ret = 0; int undx; struct vsync_update *vp; pr_debug("%s+:\n", __func__); mfd = (struct msm_fb_data_type *)platform_get_drvdata(pdev); vctrl = &vsync_ctrl_db[cndx]; pipe = vctrl->base_pipe; if (pipe == NULL) { pr_err("%s: NO base pipe\n", __func__); return ret; } /* sanity check, free pipes besides base layer */ mdp4_overlay_unset_mixer(pipe->mixer_num); mdp4_mixer_stage_down(pipe, 1); mdp4_overlay_pipe_free(pipe); vctrl->base_pipe = NULL; undx = vctrl->update_ndx; vp = &vctrl->vlist[undx]; if (vp->update_cnt) { /* * pipe's iommu will be freed at next overlay play * and iommu_drop statistic will be increased by one */ vp->update_cnt = 0; /* empty queue */ } ret = panel_next_off(pdev); mdp_clk_ctrl(1); /* MDP_LAYERMIXER_WB_MUX_SEL to restore to default cfg*/ outpdw(MDP_BASE + 0x100F4, 0x0); mdp_clk_ctrl(0); pr_debug("%s-:\n", __func__); return ret; } static int mdp4_overlay_writeback_update(struct msm_fb_data_type *mfd) { struct fb_info *fbi; uint8 *buf; unsigned int buf_offset; struct mdp4_overlay_pipe *pipe; int bpp; int cndx = 0; struct vsycn_ctrl *vctrl; if (mfd->key != MFD_KEY) return -ENODEV; fbi = mfd->fbi; vctrl = &vsync_ctrl_db[cndx]; pipe = vctrl->base_pipe; if (!pipe) { pr_err("%s: no base layer pipe\n", __func__); return -EINVAL; } bpp = fbi->var.bits_per_pixel / 8; buf = (uint8 *) fbi->fix.smem_start; buf_offset = fbi->var.xoffset * bpp + fbi->var.yoffset * fbi->fix.line_length; /* MDP cmd block enable */ mdp_clk_ctrl(1); pipe->src_height = fbi->var.yres; pipe->src_width = fbi->var.xres; pipe->src_h = fbi->var.yres; pipe->src_w = fbi->var.xres; pipe->dst_h = fbi->var.yres; pipe->dst_w = fbi->var.xres; pipe->srcp0_ystride = fbi->fix.line_length; pipe->src_y = 0; pipe->src_x = 0; pipe->dst_y = 0; pipe->dst_x = 0; mdp4_overlay_mdp_pipe_req(pipe, mfd); mdp4_calc_blt_mdp_bw(mfd, pipe); if (mfd->display_iova) pipe->srcp0_addr = mfd->display_iova + buf_offset; else pipe->srcp0_addr = (uint32)(buf + buf_offset); mdp4_mixer_stage_up(pipe, 0); mdp4_overlayproc_cfg(pipe); if (hdmi_prim_display) outpdw(MDP_BASE + 0x100F4, 0x01); else outpdw(MDP_BASE + 0x100F4, 0x02); /* MDP cmd block disable */ mdp_clk_ctrl(0); wmb(); return 0; } /* * mdp4_wfd_piep_queue: * called from thread context */ void mdp4_wfd_pipe_queue(int cndx, struct mdp4_overlay_pipe *pipe) { struct vsycn_ctrl *vctrl; struct vsync_update *vp; struct mdp4_overlay_pipe *pp; int undx; if (cndx >= MAX_CONTROLLER) { pr_err("%s: out or range: cndx=%d\n", __func__, cndx); return; } vctrl = &vsync_ctrl_db[cndx]; if (atomic_read(&vctrl->suspend) > 0) return; mutex_lock(&vctrl->update_lock); undx = vctrl->update_ndx; vp = &vctrl->vlist[undx]; pp = &vp->plist[pipe->pipe_ndx - 1]; /* ndx start form 1 */ pr_debug("%s: vndx=%d pipe_ndx=%d pid=%d\n", __func__, undx, pipe->pipe_ndx, current->pid); *pp = *pipe; /* clone it */ vp->update_cnt++; mutex_unlock(&vctrl->update_lock); mdp4_stat.overlay_play[pipe->mixer_num]++; } static void mdp4_wfd_wait4ov(int cndx); int mdp4_wfd_pipe_commit(struct msm_fb_data_type *mfd, int cndx, int wait) { int i, undx; int mixer = 0; struct vsycn_ctrl *vctrl; struct vsync_update *vp; struct mdp4_overlay_pipe *pipe; struct mdp4_overlay_pipe *real_pipe; unsigned long flags; int cnt = 0; struct msmfb_writeback_data_list *node = NULL; vctrl = &vsync_ctrl_db[cndx]; mutex_lock(&vctrl->update_lock); undx = vctrl->update_ndx; vp = &vctrl->vlist[undx]; pipe = vctrl->base_pipe; mixer = pipe->mixer_num; if (vp->update_cnt == 0) { mutex_unlock(&vctrl->update_lock); return cnt; } vctrl->update_ndx++; vctrl->update_ndx &= 0x01; vp->update_cnt = 0; /* reset */ mutex_unlock(&vctrl->update_lock); mdp4_wfd_dequeue_update(mfd, &node); /* free previous committed iommu back to pool */ mdp4_overlay_iommu_unmap_freelist(mixer); pipe = vp->plist; for (i = 0; i < OVERLAY_PIPE_MAX; i++, pipe++) { if (pipe->pipe_used) { cnt++; real_pipe = mdp4_overlay_ndx2pipe(pipe->pipe_ndx); if (real_pipe && real_pipe->pipe_used) { /* pipe not unset */ mdp4_overlay_vsync_commit(pipe); } /* free previous iommu to freelist * which will be freed at next * pipe_commit */ mdp4_overlay_iommu_pipe_free(pipe->pipe_ndx, 0); pipe->pipe_used = 0; /* clear */ } } mdp_clk_ctrl(1); mdp4_mixer_stage_commit(mixer); pipe = vctrl->base_pipe; spin_lock_irqsave(&vctrl->spin_lock, flags); vctrl->ov_koff++; INIT_COMPLETION(vctrl->ov_comp); vsync_irq_enable(INTR_OVERLAY2_DONE, MDP_OVERLAY2_TERM); pr_debug("%s: kickoff\n", __func__); /* kickoff overlay engine */ mdp4_stat.kickoff_ov2++; outpdw(MDP_BASE + 0x00D0, 0); mb(); /* make sure kickoff executed */ spin_unlock_irqrestore(&vctrl->spin_lock, flags); mdp4_stat.overlay_commit[pipe->mixer_num]++; if (wait) mdp4_wfd_wait4ov(cndx); mdp4_wfd_queue_wakeup(mfd, node); return cnt; } static void clk_ctrl_work(struct work_struct *work) { struct vsycn_ctrl *vctrl = container_of(work, typeof(*vctrl), clk_work); mdp_clk_ctrl(0); } void mdp4_wfd_init(int cndx) { struct vsycn_ctrl *vctrl; if (cndx >= MAX_CONTROLLER) { pr_err("%s: out or range: cndx=%d\n", __func__, cndx); return; } vctrl = &vsync_ctrl_db[cndx]; if (vctrl->inited) return; vctrl->inited = 1; vctrl->update_ndx = 0; mutex_init(&vctrl->update_lock); init_completion(&vctrl->ov_comp); spin_lock_init(&vctrl->spin_lock); INIT_WORK(&vctrl->clk_work, clk_ctrl_work); } static void mdp4_wfd_wait4ov(int cndx) { struct vsycn_ctrl *vctrl; if (cndx >= MAX_CONTROLLER) { pr_err("%s: out or range: cndx=%d\n", __func__, cndx); return; } vctrl = &vsync_ctrl_db[cndx]; if (atomic_read(&vctrl->suspend) > 0) return; wait_for_completion(&vctrl->ov_comp); } void mdp4_overlay2_done_wfd(struct mdp_dma_data *dma) { struct vsycn_ctrl *vctrl; struct mdp4_overlay_pipe *pipe; int cndx = 0; vctrl = &vsync_ctrl_db[cndx]; pipe = vctrl->base_pipe; spin_lock(&vctrl->spin_lock); vsync_irq_disable(INTR_OVERLAY2_DONE, MDP_OVERLAY2_TERM); vctrl->ov_done++; complete(&vctrl->ov_comp); schedule_work(&vctrl->clk_work); pr_debug("%s ovdone interrupt\n", __func__); spin_unlock(&vctrl->spin_lock); } void mdp4_writeback_overlay(struct msm_fb_data_type *mfd) { struct vsycn_ctrl *vctrl; struct mdp4_overlay_pipe *pipe; if (mfd && !mfd->panel_power_on) return; pr_debug("%s:+ mfd=%x\n", __func__, (int)mfd); vctrl = &vsync_ctrl_db[0]; pipe = vctrl->base_pipe; mutex_lock(&mfd->dma->ov_mutex); if (pipe->pipe_type == OVERLAY_TYPE_RGB) mdp4_wfd_pipe_queue(0, pipe); mdp4_overlay_mdp_perf_upd(mfd, 1); mdp4_wfd_pipe_commit(mfd, 0, 1); mdp4_overlay_mdp_perf_upd(mfd, 0); mutex_unlock(&mfd->dma->ov_mutex); } static int mdp4_overlay_writeback_register_buffer( struct msm_fb_data_type *mfd, struct msmfb_writeback_data_list *node) { if (!node) { pr_err("Cannot register a NULL node\n"); return -EINVAL; } node->state = REGISTERED; list_add_tail(&node->registered_entry, &mfd->writeback_register_queue); return 0; } static struct msmfb_writeback_data_list *get_if_registered( struct msm_fb_data_type *mfd, struct msmfb_data *data) { struct msmfb_writeback_data_list *temp; bool found = false; int domain; if (!list_empty(&mfd->writeback_register_queue)) { list_for_each_entry(temp, &mfd->writeback_register_queue, registered_entry) { if (temp && temp->buf_info.iova == data->iova) { found = true; break; } } } if (!found) { temp = kzalloc(sizeof(struct msmfb_writeback_data_list), GFP_KERNEL); if (temp == NULL) { pr_err("%s: out of memory\n", __func__); goto register_alloc_fail; } temp->ihdl = NULL; if (data->iova) temp->addr = (void *)(data->iova + data->offset); else if (mfd->iclient) { struct ion_handle *srcp_ihdl; ulong len; srcp_ihdl = ion_import_dma_buf(mfd->iclient, data->memory_id); if (IS_ERR_OR_NULL(srcp_ihdl)) { pr_err("%s: ion import fd failed\n", __func__); goto register_ion_fail; } if (mdp_iommu_split_domain) domain = DISPLAY_WRITE_DOMAIN; else domain = DISPLAY_READ_DOMAIN; if (ion_map_iommu(mfd->iclient, srcp_ihdl, domain, GEN_POOL, SZ_4K, 0, (ulong *)&temp->addr, (ulong *)&len, 0, ION_IOMMU_UNMAP_DELAYED)) { ion_free(mfd->iclient, srcp_ihdl); pr_err("%s: unable to get ion mapping addr\n", __func__); goto register_ion_fail; } temp->addr += data->offset; temp->ihdl = srcp_ihdl; } else { pr_err("%s: only support ion memory\n", __func__); goto register_ion_fail; } memcpy(&temp->buf_info, data, sizeof(struct msmfb_data)); if (mdp4_overlay_writeback_register_buffer(mfd, temp)) { pr_err("%s: error registering node\n", __func__); goto register_ion_fail; } } return temp; register_ion_fail: kfree(temp); register_alloc_fail: return NULL; } int mdp4_writeback_start( struct fb_info *info) { struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; mutex_lock(&mfd->writeback_mutex); mfd->writeback_state = WB_START; mutex_unlock(&mfd->writeback_mutex); wake_up(&mfd->wait_q); return 0; } int mdp4_writeback_queue_buffer(struct fb_info *info, struct msmfb_data *data) { struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; struct msmfb_writeback_data_list *node = NULL; int rv = 0; mutex_lock(&mfd->writeback_mutex); node = get_if_registered(mfd, data); if (!node || node->state == IN_BUSY_QUEUE || node->state == IN_FREE_QUEUE) { pr_err("memory not registered or Buffer already with us\n"); rv = -EINVAL; goto exit; } list_add_tail(&node->active_entry, &mfd->writeback_free_queue); node->state = IN_FREE_QUEUE; exit: mutex_unlock(&mfd->writeback_mutex); return rv; } static int is_buffer_ready(struct msm_fb_data_type *mfd) { int rc; mutex_lock(&mfd->writeback_mutex); rc = !list_empty(&mfd->writeback_busy_queue) || (mfd->writeback_state == WB_STOPING); mutex_unlock(&mfd->writeback_mutex); return rc; } int mdp4_writeback_dequeue_buffer(struct fb_info *info, struct msmfb_data *data) { struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; struct msmfb_writeback_data_list *node = NULL; int rc = 0, domain; rc = wait_event_interruptible(mfd->wait_q, is_buffer_ready(mfd)); if (rc) { pr_err("failed to get dequeued buffer\n"); return -ENOBUFS; } mutex_lock(&mfd->writeback_mutex); if (mfd->writeback_state == WB_STOPING) { mfd->writeback_state = WB_STOP; mutex_unlock(&mfd->writeback_mutex); return -ENOBUFS; } else if (!list_empty(&mfd->writeback_busy_queue)) { node = list_first_entry(&mfd->writeback_busy_queue, struct msmfb_writeback_data_list, active_entry); } if (node) { list_del(&node->active_entry); node->state = WITH_CLIENT; memcpy(data, &node->buf_info, sizeof(struct msmfb_data)); if (!data->iova) if (mfd->iclient && node->ihdl) { if (mdp_iommu_split_domain) domain = DISPLAY_WRITE_DOMAIN; else domain = DISPLAY_READ_DOMAIN; ion_unmap_iommu(mfd->iclient, node->ihdl, domain, GEN_POOL); ion_free(mfd->iclient, node->ihdl); } } else { pr_err("node is NULL. Somebody else dequeued?\n"); rc = -ENOBUFS; } mutex_unlock(&mfd->writeback_mutex); return rc; } static bool is_writeback_inactive(struct msm_fb_data_type *mfd) { bool active; mutex_lock(&mfd->writeback_mutex); active = !mfd->writeback_active_cnt; mutex_unlock(&mfd->writeback_mutex); return active; } int mdp4_writeback_stop(struct fb_info *info) { struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; mutex_lock(&mfd->writeback_mutex); mfd->writeback_state = WB_STOPING; mutex_unlock(&mfd->writeback_mutex); /* Wait for all pending writebacks to finish */ wait_event_interruptible(mfd->wait_q, is_writeback_inactive(mfd)); /* Wake up dequeue thread in case of no UI update*/ wake_up(&mfd->wait_q); return 0; } int mdp4_writeback_init(struct fb_info *info) { struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; mutex_init(&mfd->writeback_mutex); mutex_init(&mfd->unregister_mutex); INIT_LIST_HEAD(&mfd->writeback_free_queue); INIT_LIST_HEAD(&mfd->writeback_busy_queue); INIT_LIST_HEAD(&mfd->writeback_register_queue); mfd->writeback_state = WB_OPEN; init_waitqueue_head(&mfd->wait_q); return 0; } int mdp4_writeback_terminate(struct fb_info *info) { struct list_head *ptr, *next; struct msmfb_writeback_data_list *temp; struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; int rc = 0; mutex_lock(&mfd->unregister_mutex); mutex_lock(&mfd->writeback_mutex); if (mfd->writeback_state != WB_STOPING && mfd->writeback_state != WB_STOP) { pr_err("%s called without stopping\n", __func__); rc = -EPERM; goto terminate_err; } if (!list_empty(&mfd->writeback_register_queue)) { list_for_each_safe(ptr, next, &mfd->writeback_register_queue) { temp = list_entry(ptr, struct msmfb_writeback_data_list, registered_entry); list_del(&temp->registered_entry); kfree(temp); } } INIT_LIST_HEAD(&mfd->writeback_register_queue); INIT_LIST_HEAD(&mfd->writeback_busy_queue); INIT_LIST_HEAD(&mfd->writeback_free_queue); terminate_err: mutex_unlock(&mfd->writeback_mutex); mutex_unlock(&mfd->unregister_mutex); return rc; } static void mdp4_wfd_dequeue_update(struct msm_fb_data_type *mfd, struct msmfb_writeback_data_list **wfdnode) { struct vsycn_ctrl *vctrl; struct mdp4_overlay_pipe *pipe; struct msmfb_writeback_data_list *node = NULL; if (mfd && !mfd->panel_power_on) return; pr_debug("%s:+ mfd=%x\n", __func__, (int)mfd); vctrl = &vsync_ctrl_db[0]; pipe = vctrl->base_pipe; mutex_lock(&mfd->unregister_mutex); mutex_lock(&mfd->writeback_mutex); if (!list_empty(&mfd->writeback_free_queue) && mfd->writeback_state != WB_STOPING && mfd->writeback_state != WB_STOP) { node = list_first_entry(&mfd->writeback_free_queue, struct msmfb_writeback_data_list, active_entry); } if (node) { list_del(&(node->active_entry)); node->state = IN_BUSY_QUEUE; mfd->writeback_active_cnt++; } mutex_unlock(&mfd->writeback_mutex); pipe->ov_blt_addr = (ulong) (node ? node->addr : NULL); if (!pipe->ov_blt_addr) { pr_err("%s: no writeback buffer 0x%x, %p\n", __func__, (unsigned int)pipe->ov_blt_addr, node); mutex_unlock(&mfd->unregister_mutex); return; } mdp4_overlay_writeback_update(mfd); *wfdnode = node; mutex_unlock(&mfd->unregister_mutex); } static void mdp4_wfd_queue_wakeup(struct msm_fb_data_type *mfd, struct msmfb_writeback_data_list *node) { if (mfd && !mfd->panel_power_on) return; if (node == NULL) return; pr_debug("%s: mfd=%x node: %p", __func__, (int)mfd, node); mutex_lock(&mfd->writeback_mutex); list_add_tail(&node->active_entry, &mfd->writeback_busy_queue); mfd->writeback_active_cnt--; mutex_unlock(&mfd->writeback_mutex); wake_up(&mfd->wait_q); } int mdp4_writeback_set_mirroring_hint(struct fb_info *info, int hint) { struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; if (mfd->panel.type != WRITEBACK_PANEL) return -ENOTSUPP; switch (hint) { case MDP_WRITEBACK_MIRROR_ON: case MDP_WRITEBACK_MIRROR_PAUSE: case MDP_WRITEBACK_MIRROR_RESUME: case MDP_WRITEBACK_MIRROR_OFF: pr_info("wfd state switched to %d\n", hint); switch_set_state(&mfd->writeback_sdev, hint); return 0; default: return -EINVAL; } }