| /* Copyright (c) 2009-2010, Code Aurora Forum. 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. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| * 02110-1301, USA. |
| */ |
| |
| #include "mdp.h" |
| |
| static boolean mdp_ppp_intr_flag = FALSE; |
| static boolean mdp_ppp_busy_flag = FALSE; |
| |
| /* Queue to keep track of the completed jobs for cleaning */ |
| static LIST_HEAD(mdp_ppp_djob_clnrq); |
| static DEFINE_SPINLOCK(mdp_ppp_djob_clnrq_lock); |
| |
| /* Worker to cleanup Display Jobs */ |
| static struct workqueue_struct *mdp_ppp_djob_clnr; |
| |
| /* Display Queue (DQ) for MDP PPP Block */ |
| static LIST_HEAD(mdp_ppp_dq); |
| static DEFINE_SPINLOCK(mdp_ppp_dq_lock); |
| |
| /* Current Display Job for MDP PPP */ |
| static struct mdp_ppp_djob *curr_djob; |
| |
| /* Track ret code for the last opeartion */ |
| static int mdp_ppp_ret_code; |
| |
| inline int mdp_ppp_get_ret_code(void) |
| { |
| return mdp_ppp_ret_code; |
| } |
| |
| /* Push <Reg, Val> pair into DQ (if available) to later |
| * program the MDP PPP Block */ |
| inline void mdp_ppp_outdw(uint32_t addr, uint32_t data) |
| { |
| if (curr_djob) { |
| |
| /* get the last node of the list. */ |
| struct mdp_ppp_roi_cmd_set *node = |
| list_entry(curr_djob->roi_cmd_list.prev, |
| struct mdp_ppp_roi_cmd_set, node); |
| |
| /* If a node is already full, create a new one and add it to |
| * the list (roi_cmd_list). |
| */ |
| if (node->ncmds == MDP_PPP_ROI_NODE_SIZE) { |
| node = kmalloc(sizeof(struct mdp_ppp_roi_cmd_set), |
| GFP_KERNEL); |
| if (!node) { |
| printk(KERN_ERR |
| "MDP_PPP: not enough memory.\n"); |
| mdp_ppp_ret_code = -EINVAL; |
| return; |
| } |
| |
| /* no ROI commands initially */ |
| node->ncmds = 0; |
| |
| /* add one node to roi_cmd_list. */ |
| list_add_tail(&node->node, &curr_djob->roi_cmd_list); |
| } |
| |
| /* register ROI commands */ |
| node->cmd[node->ncmds].reg = addr; |
| node->cmd[node->ncmds].val = data; |
| node->ncmds++; |
| } else |
| /* program MDP PPP block now */ |
| outpdw((addr), (data)); |
| } |
| |
| /* Initialize DQ */ |
| inline void mdp_ppp_dq_init(void) |
| { |
| mdp_ppp_djob_clnr = create_singlethread_workqueue("MDPDJobClnrThrd"); |
| } |
| |
| /* Release resources of a job (DJob). */ |
| static void mdp_ppp_del_djob(struct mdp_ppp_djob *job) |
| { |
| struct mdp_ppp_roi_cmd_set *node, *tmp; |
| |
| /* release mem */ |
| mdp_ppp_put_img(job->p_src_file, job->p_dst_file); |
| |
| /* release roi_cmd_list */ |
| list_for_each_entry_safe(node, tmp, &job->roi_cmd_list, node) { |
| list_del(&node->node); |
| kfree(node); |
| } |
| |
| /* release job struct */ |
| kfree(job); |
| } |
| |
| /* Worker thread to reclaim resources once a display job is done */ |
| static void mdp_ppp_djob_cleaner(struct work_struct *work) |
| { |
| struct mdp_ppp_djob *job; |
| |
| MDP_PPP_DEBUG_MSG("mdp ppp display job cleaner started \n"); |
| |
| /* cleanup display job */ |
| job = container_of(work, struct mdp_ppp_djob, cleaner.work); |
| if (likely(work && job)) |
| mdp_ppp_del_djob(job); |
| } |
| |
| /* Create a new Display Job (DJob) */ |
| inline struct mdp_ppp_djob *mdp_ppp_new_djob(void) |
| { |
| struct mdp_ppp_djob *job; |
| struct mdp_ppp_roi_cmd_set *node; |
| |
| /* create a new djob */ |
| job = kmalloc(sizeof(struct mdp_ppp_djob), GFP_KERNEL); |
| if (!job) |
| return NULL; |
| |
| /* add the first node to curr_djob->roi_cmd_list */ |
| node = kmalloc(sizeof(struct mdp_ppp_roi_cmd_set), GFP_KERNEL); |
| if (!node) { |
| kfree(job); |
| return NULL; |
| } |
| |
| /* make this current djob container to keep track of the curr djob not |
| * used in the async path i.e. no sync needed |
| * |
| * Should not contain any references from the past djob |
| */ |
| BUG_ON(curr_djob); |
| curr_djob = job; |
| INIT_LIST_HEAD(&curr_djob->roi_cmd_list); |
| |
| /* no ROI commands initially */ |
| node->ncmds = 0; |
| INIT_LIST_HEAD(&node->node); |
| list_add_tail(&node->node, &curr_djob->roi_cmd_list); |
| |
| /* register this djob with the djob cleaner |
| * initializes 'work' data struct |
| */ |
| INIT_DELAYED_WORK(&curr_djob->cleaner, mdp_ppp_djob_cleaner); |
| INIT_LIST_HEAD(&curr_djob->entry); |
| |
| curr_djob->p_src_file = 0; |
| curr_djob->p_dst_file = 0; |
| |
| return job; |
| } |
| |
| /* Undo the effect of mdp_ppp_new_djob() */ |
| inline void mdp_ppp_clear_curr_djob(void) |
| { |
| if (likely(curr_djob)) { |
| mdp_ppp_del_djob(curr_djob); |
| curr_djob = NULL; |
| } |
| } |
| |
| /* Cleanup dirty djobs */ |
| static void mdp_ppp_flush_dirty_djobs(void *cond) |
| { |
| unsigned long flags; |
| struct mdp_ppp_djob *job; |
| |
| /* Flush the jobs from the djob clnr queue */ |
| while (cond && test_bit(0, (unsigned long *)cond)) { |
| |
| /* Until we are done with the cleanup queue */ |
| spin_lock_irqsave(&mdp_ppp_djob_clnrq_lock, flags); |
| if (list_empty(&mdp_ppp_djob_clnrq)) { |
| spin_unlock_irqrestore(&mdp_ppp_djob_clnrq_lock, flags); |
| break; |
| } |
| |
| MDP_PPP_DEBUG_MSG("flushing djobs ... loop \n"); |
| |
| /* Retrieve the job that needs to be cleaned */ |
| job = list_entry(mdp_ppp_djob_clnrq.next, |
| struct mdp_ppp_djob, entry); |
| list_del_init(&job->entry); |
| spin_unlock_irqrestore(&mdp_ppp_djob_clnrq_lock, flags); |
| |
| /* Keep mem state coherent */ |
| msm_fb_ensure_mem_coherency_after_dma(job->info, &job->req, 1); |
| |
| /* Schedule jobs for cleanup |
| * A seperate worker thread does this */ |
| queue_delayed_work(mdp_ppp_djob_clnr, &job->cleaner, |
| mdp_timer_duration); |
| } |
| } |
| |
| /* If MDP PPP engine is busy, wait until it is available again */ |
| void mdp_ppp_wait(void) |
| { |
| unsigned long flags; |
| int cond = 1; |
| |
| /* keep flushing dirty djobs as long as MDP PPP engine is busy */ |
| mdp_ppp_flush_dirty_djobs(&mdp_ppp_busy_flag); |
| |
| /* block if MDP PPP engine is still busy */ |
| spin_lock_irqsave(&mdp_ppp_dq_lock, flags); |
| if (test_bit(0, (unsigned long *)&mdp_ppp_busy_flag)) { |
| |
| /* prepare for the wakeup event */ |
| test_and_set_bit(0, (unsigned long *)&mdp_ppp_waiting); |
| INIT_COMPLETION(mdp_ppp_comp); |
| spin_unlock_irqrestore(&mdp_ppp_dq_lock, flags); |
| |
| /* block uninterruptibly until available */ |
| MDP_PPP_DEBUG_MSG("waiting for mdp... \n"); |
| wait_for_completion_killable(&mdp_ppp_comp); |
| |
| /* if MDP PPP engine is still free, |
| * disable INT_MDP if enabled |
| */ |
| spin_lock_irqsave(&mdp_ppp_dq_lock, flags); |
| if (!test_bit(0, (unsigned long *)&mdp_ppp_busy_flag) && |
| test_and_clear_bit(0, (unsigned long *)&mdp_ppp_intr_flag)) |
| mdp_disable_irq(MDP_PPP_TERM); |
| } |
| spin_unlock_irqrestore(&mdp_ppp_dq_lock, flags); |
| |
| /* flush remaining dirty djobs, if any */ |
| mdp_ppp_flush_dirty_djobs(&cond); |
| } |
| |
| /* Program MDP PPP block to process this ROI */ |
| static void mdp_ppp_process_roi(struct list_head *roi_cmd_list) |
| { |
| |
| /* program PPP engine with registered ROI commands */ |
| struct mdp_ppp_roi_cmd_set *node; |
| list_for_each_entry(node, roi_cmd_list, node) { |
| int i = 0; |
| for (; i < node->ncmds; i++) { |
| MDP_PPP_DEBUG_MSG("%d: reg: 0x%x val: 0x%x \n", |
| i, node->cmd[i].reg, node->cmd[i].val); |
| outpdw(node->cmd[i].reg, node->cmd[i].val); |
| } |
| } |
| |
| /* kickoff MDP PPP engine */ |
| MDP_PPP_DEBUG_MSG("kicking off mdp \n"); |
| outpdw(MDP_BASE + 0x30, 0x1000); |
| } |
| |
| /* Submit this display job to MDP PPP engine */ |
| static void mdp_ppp_dispatch_djob(struct mdp_ppp_djob *job) |
| { |
| /* enable INT_MDP if disabled */ |
| if (!test_and_set_bit(0, (unsigned long *)&mdp_ppp_intr_flag)) |
| mdp_enable_irq(MDP_PPP_TERM); |
| |
| /* turn on PPP and CMD blocks */ |
| mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); |
| mdp_pipe_ctrl(MDP_PPP_BLOCK, MDP_BLOCK_POWER_ON, FALSE); |
| |
| /* process this ROI */ |
| mdp_ppp_process_roi(&job->roi_cmd_list); |
| } |
| |
| /* Enqueue this display job to be cleaned up later in "mdp_ppp_djob_done" */ |
| static inline void mdp_ppp_enqueue_djob(struct mdp_ppp_djob *job) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&mdp_ppp_dq_lock, flags); |
| list_add_tail(&job->entry, &mdp_ppp_dq); |
| spin_unlock_irqrestore(&mdp_ppp_dq_lock, flags); |
| } |
| |
| /* First enqueue display job for cleanup and dispatch immediately |
| * if MDP PPP engine is free */ |
| void mdp_ppp_process_curr_djob(void) |
| { |
| /* enqueue djob */ |
| mdp_ppp_enqueue_djob(curr_djob); |
| |
| /* dispatch now if MDP PPP engine is free */ |
| if (!test_and_set_bit(0, (unsigned long *)&mdp_ppp_busy_flag)) |
| mdp_ppp_dispatch_djob(curr_djob); |
| |
| /* done with the current djob */ |
| curr_djob = NULL; |
| } |
| |
| /* Called from mdp_isr - cleanup finished job and start with next |
| * if available else set MDP PPP engine free */ |
| void mdp_ppp_djob_done(void) |
| { |
| struct mdp_ppp_djob *curr, *next; |
| unsigned long flags; |
| |
| /* dequeue current */ |
| spin_lock_irqsave(&mdp_ppp_dq_lock, flags); |
| curr = list_entry(mdp_ppp_dq.next, struct mdp_ppp_djob, entry); |
| list_del_init(&curr->entry); |
| spin_unlock_irqrestore(&mdp_ppp_dq_lock, flags); |
| |
| /* cleanup current - enqueue in the djob clnr queue */ |
| spin_lock_irqsave(&mdp_ppp_djob_clnrq_lock, flags); |
| list_add_tail(&curr->entry, &mdp_ppp_djob_clnrq); |
| spin_unlock_irqrestore(&mdp_ppp_djob_clnrq_lock, flags); |
| |
| /* grab next pending */ |
| spin_lock_irqsave(&mdp_ppp_dq_lock, flags); |
| if (!list_empty(&mdp_ppp_dq)) { |
| next = list_entry(mdp_ppp_dq.next, struct mdp_ppp_djob, |
| entry); |
| spin_unlock_irqrestore(&mdp_ppp_dq_lock, flags); |
| |
| /* process next in the queue */ |
| mdp_ppp_process_roi(&next->roi_cmd_list); |
| } else { |
| /* no pending display job */ |
| spin_unlock_irqrestore(&mdp_ppp_dq_lock, flags); |
| |
| /* turn off PPP and CMD blocks - "in_isr" is TRUE */ |
| mdp_pipe_ctrl(MDP_PPP_BLOCK, MDP_BLOCK_POWER_OFF, TRUE); |
| mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, TRUE); |
| |
| /* notify if waiting */ |
| if (test_and_clear_bit(0, (unsigned long *)&mdp_ppp_waiting)) |
| complete(&mdp_ppp_comp); |
| |
| /* set free */ |
| test_and_clear_bit(0, (unsigned long *)&mdp_ppp_busy_flag); |
| } |
| } |