Linux Audio

Check our new training course

Embedded Linux Audio

Check our new training course
with Creative Commons CC-BY-SA
lecture materials

Bootlin logo

Elixir Cross Referencer

Loading...
/* 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 separate 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);
	}
}