aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/staging/msm/mdp_ppp_dq.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/staging/msm/mdp_ppp_dq.c')
-rw-r--r--drivers/staging/msm/mdp_ppp_dq.c347
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 000000000000..3dc1c0cc61f9
--- /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);
+ }
+}