174 lines
		
	
	
	
		
			5.2 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
		
		
			
		
	
	
			174 lines
		
	
	
	
		
			5.2 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
|   | /*
 | ||
|  |  * Copyright (C) 2013 Red Hat | ||
|  |  * Author: Rob Clark <robdclark@gmail.com> | ||
|  |  * | ||
|  |  * This program is free software; you can redistribute it and/or modify it | ||
|  |  * under the terms of the GNU General Public License 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, see <http://www.gnu.org/licenses/>.
 | ||
|  |  */ | ||
|  | 
 | ||
|  | 
 | ||
|  | #include "mdp5_kms.h"
 | ||
|  | #include "mdp5_smp.h"
 | ||
|  | 
 | ||
|  | 
 | ||
|  | /* SMP - Shared Memory Pool
 | ||
|  |  * | ||
|  |  * These are shared between all the clients, where each plane in a | ||
|  |  * scanout buffer is a SMP client.  Ie. scanout of 3 plane I420 on | ||
|  |  * pipe VIG0 => 3 clients: VIG0_Y, VIG0_CB, VIG0_CR. | ||
|  |  * | ||
|  |  * Based on the size of the attached scanout buffer, a certain # of | ||
|  |  * blocks must be allocated to that client out of the shared pool. | ||
|  |  * | ||
|  |  * For each block, it can be either free, or pending/in-use by a | ||
|  |  * client.  The updates happen in three steps: | ||
|  |  * | ||
|  |  *  1) mdp5_smp_request(): | ||
|  |  *     When plane scanout is setup, calculate required number of | ||
|  |  *     blocks needed per client, and request.  Blocks not inuse or | ||
|  |  *     pending by any other client are added to client's pending | ||
|  |  *     set. | ||
|  |  * | ||
|  |  *  2) mdp5_smp_configure(): | ||
|  |  *     As hw is programmed, before FLUSH, MDP5_SMP_ALLOC registers | ||
|  |  *     are configured for the union(pending, inuse) | ||
|  |  * | ||
|  |  *  3) mdp5_smp_commit(): | ||
|  |  *     After next vblank, copy pending -> inuse.  Optionally update | ||
|  |  *     MDP5_SMP_ALLOC registers if there are newly unused blocks | ||
|  |  * | ||
|  |  * On the next vblank after changes have been committed to hw, the | ||
|  |  * client's pending blocks become it's in-use blocks (and no-longer | ||
|  |  * in-use blocks become available to other clients). | ||
|  |  * | ||
|  |  * btw, hurray for confusing overloaded acronyms!  :-/ | ||
|  |  * | ||
|  |  * NOTE: for atomic modeset/pageflip NONBLOCK operations, step #1 | ||
|  |  * should happen at (or before)? atomic->check().  And we'd need | ||
|  |  * an API to discard previous requests if update is aborted or | ||
|  |  * (test-only). | ||
|  |  * | ||
|  |  * TODO would perhaps be nice to have debugfs to dump out kernel | ||
|  |  * inuse and pending state of all clients.. | ||
|  |  */ | ||
|  | 
 | ||
|  | static DEFINE_SPINLOCK(smp_lock); | ||
|  | 
 | ||
|  | 
 | ||
|  | /* step #1: update # of blocks pending for the client: */ | ||
|  | int mdp5_smp_request(struct mdp5_kms *mdp5_kms, | ||
|  | 		enum mdp5_client_id cid, int nblks) | ||
|  | { | ||
|  | 	struct mdp5_client_smp_state *ps = &mdp5_kms->smp_client_state[cid]; | ||
|  | 	int i, ret, avail, cur_nblks, cnt = mdp5_kms->smp_blk_cnt; | ||
|  | 	unsigned long flags; | ||
|  | 
 | ||
|  | 	spin_lock_irqsave(&smp_lock, flags); | ||
|  | 
 | ||
|  | 	avail = cnt - bitmap_weight(mdp5_kms->smp_state, cnt); | ||
|  | 	if (nblks > avail) { | ||
|  | 		ret = -ENOSPC; | ||
|  | 		goto fail; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	cur_nblks = bitmap_weight(ps->pending, cnt); | ||
|  | 	if (nblks > cur_nblks) { | ||
|  | 		/* grow the existing pending reservation: */ | ||
|  | 		for (i = cur_nblks; i < nblks; i++) { | ||
|  | 			int blk = find_first_zero_bit(mdp5_kms->smp_state, cnt); | ||
|  | 			set_bit(blk, ps->pending); | ||
|  | 			set_bit(blk, mdp5_kms->smp_state); | ||
|  | 		} | ||
|  | 	} else { | ||
|  | 		/* shrink the existing pending reservation: */ | ||
|  | 		for (i = cur_nblks; i > nblks; i--) { | ||
|  | 			int blk = find_first_bit(ps->pending, cnt); | ||
|  | 			clear_bit(blk, ps->pending); | ||
|  | 			/* don't clear in global smp_state until _commit() */ | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | fail: | ||
|  | 	spin_unlock_irqrestore(&smp_lock, flags); | ||
|  | 	return 0; | ||
|  | } | ||
|  | 
 | ||
|  | static void update_smp_state(struct mdp5_kms *mdp5_kms, | ||
|  | 		enum mdp5_client_id cid, mdp5_smp_state_t *assigned) | ||
|  | { | ||
|  | 	int cnt = mdp5_kms->smp_blk_cnt; | ||
|  | 	uint32_t blk, val; | ||
|  | 
 | ||
|  | 	for_each_set_bit(blk, *assigned, cnt) { | ||
|  | 		int idx = blk / 3; | ||
|  | 		int fld = blk % 3; | ||
|  | 
 | ||
|  | 		val = mdp5_read(mdp5_kms, REG_MDP5_SMP_ALLOC_W_REG(idx)); | ||
|  | 
 | ||
|  | 		switch (fld) { | ||
|  | 		case 0: | ||
|  | 			val &= ~MDP5_SMP_ALLOC_W_REG_CLIENT0__MASK; | ||
|  | 			val |= MDP5_SMP_ALLOC_W_REG_CLIENT0(cid); | ||
|  | 			break; | ||
|  | 		case 1: | ||
|  | 			val &= ~MDP5_SMP_ALLOC_W_REG_CLIENT1__MASK; | ||
|  | 			val |= MDP5_SMP_ALLOC_W_REG_CLIENT1(cid); | ||
|  | 			break; | ||
|  | 		case 2: | ||
|  | 			val &= ~MDP5_SMP_ALLOC_W_REG_CLIENT2__MASK; | ||
|  | 			val |= MDP5_SMP_ALLOC_W_REG_CLIENT2(cid); | ||
|  | 			break; | ||
|  | 		} | ||
|  | 
 | ||
|  | 		mdp5_write(mdp5_kms, REG_MDP5_SMP_ALLOC_W_REG(idx), val); | ||
|  | 		mdp5_write(mdp5_kms, REG_MDP5_SMP_ALLOC_R_REG(idx), val); | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | /* step #2: configure hw for union(pending, inuse): */ | ||
|  | void mdp5_smp_configure(struct mdp5_kms *mdp5_kms, enum mdp5_client_id cid) | ||
|  | { | ||
|  | 	struct mdp5_client_smp_state *ps = &mdp5_kms->smp_client_state[cid]; | ||
|  | 	int cnt = mdp5_kms->smp_blk_cnt; | ||
|  | 	mdp5_smp_state_t assigned; | ||
|  | 
 | ||
|  | 	bitmap_or(assigned, ps->inuse, ps->pending, cnt); | ||
|  | 	update_smp_state(mdp5_kms, cid, &assigned); | ||
|  | } | ||
|  | 
 | ||
|  | /* step #3: after vblank, copy pending -> inuse: */ | ||
|  | void mdp5_smp_commit(struct mdp5_kms *mdp5_kms, enum mdp5_client_id cid) | ||
|  | { | ||
|  | 	struct mdp5_client_smp_state *ps = &mdp5_kms->smp_client_state[cid]; | ||
|  | 	int cnt = mdp5_kms->smp_blk_cnt; | ||
|  | 	mdp5_smp_state_t released; | ||
|  | 
 | ||
|  | 	/*
 | ||
|  | 	 * Figure out if there are any blocks we where previously | ||
|  | 	 * using, which can be released and made available to other | ||
|  | 	 * clients: | ||
|  | 	 */ | ||
|  | 	if (bitmap_andnot(released, ps->inuse, ps->pending, cnt)) { | ||
|  | 		unsigned long flags; | ||
|  | 
 | ||
|  | 		spin_lock_irqsave(&smp_lock, flags); | ||
|  | 		/* clear released blocks: */ | ||
|  | 		bitmap_andnot(mdp5_kms->smp_state, mdp5_kms->smp_state, | ||
|  | 				released, cnt); | ||
|  | 		spin_unlock_irqrestore(&smp_lock, flags); | ||
|  | 
 | ||
|  | 		update_smp_state(mdp5_kms, CID_UNUSED, &released); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	bitmap_copy(ps->inuse, ps->pending, cnt); | ||
|  | } |