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