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);
|
|
}
|