diff options
Diffstat (limited to 'drivers/staging/msm/mdp_ppp_dq.c')
-rw-r--r-- | drivers/staging/msm/mdp_ppp_dq.c | 347 |
1 files changed, 347 insertions, 0 deletions
diff --git a/drivers/staging/msm/mdp_ppp_dq.c b/drivers/staging/msm/mdp_ppp_dq.c new file mode 100644 index 0000000..3dc1c0c --- /dev/null +++ b/drivers/staging/msm/mdp_ppp_dq.c @@ -0,0 +1,347 @@ +/* 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); + } +} |