text: /*
   * Copyright (C) 2016 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"
  
  struct mdp5_hw_pipe *mdp5_pipe_assign(struct drm_atomic_state *s,
  		struct drm_plane *plane, uint32_t caps, uint32_t blkcfg)
  {
  	struct msm_drm_private *priv = s->dev->dev_private;
  	struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(priv->kms));
  	struct mdp5_state *state;
  	struct mdp5_hw_pipe_state *old_state, *new_state;
  	struct mdp5_hw_pipe *hwpipe = NULL;
  	int i;
  
  	state = mdp5_get_state(s);
  	if (IS_ERR(state))
  		return ERR_CAST(state);
  
  	/* grab old_state after mdp5_get_state(), since now we hold lock: */
  	old_state = &mdp5_kms->state->hwpipe;
  	new_state = &state->hwpipe;
  
  	for (i = 0; i < mdp5_kms->num_hwpipes; i++) {
  		struct mdp5_hw_pipe *cur = mdp5_kms->hwpipes[i];
  
  		/* skip if already in-use.. check both new and old state,
  		 * since we cannot immediately re-use a pipe that is
  		 * released in the current update in some cases:
  		 *  (1) mdp5 can have SMP (non-double-buffered)
  		 *  (2) hw pipe previously assigned to different CRTC
  		 *      (vblanks might not be aligned)
  		 */
  		if (new_state->hwpipe_to_plane[cur->idx] ||
  				old_state->hwpipe_to_plane[cur->idx])
  			continue;
  
  		/* skip if doesn't support some required caps: */
  		if (caps & ~cur->caps)
  			continue;
  
  		/*
  		 * don't assign a cursor pipe to a plane that isn't going to
  		 * be used as a cursor
  		 */
  		if (cur->caps & MDP_PIPE_CAP_CURSOR &&
  				plane->type != DRM_PLANE_TYPE_CURSOR)
  			continue;
  
  		/* possible candidate, take the one with the
  		 * fewest unneeded caps bits set:
  		 */
  		if (!hwpipe || (hweight_long(cur->caps & ~caps) <
  				hweight_long(hwpipe->caps & ~caps)))
  			hwpipe = cur;
  	}
  
  	if (!hwpipe)
  		return ERR_PTR(-ENOMEM);
  
  	if (mdp5_kms->smp) {
  		int ret;
  
  		DBG("%s: alloc SMP blocks", hwpipe->name);
  		ret = mdp5_smp_assign(mdp5_kms->smp, &state->smp,
  				hwpipe->pipe, blkcfg);
  		if (ret)
  			return ERR_PTR(-ENOMEM);
  
  		hwpipe->blkcfg = blkcfg;
  	}
  
  	DBG("%s: assign to plane %s for caps %x",
  			hwpipe->name, plane->name, caps);
  	new_state->hwpipe_to_plane[hwpipe->idx] = plane;
  
  	return hwpipe;
  }
  
  void mdp5_pipe_release(struct drm_atomic_state *s, struct mdp5_hw_pipe *hwpipe)
  {
  	struct msm_drm_private *priv = s->dev->dev_private;
  	struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(priv->kms));
  	struct mdp5_state *state = mdp5_get_state(s);
  	struct mdp5_hw_pipe_state *new_state = &state->hwpipe;
  
  	if (!hwpipe)
  		return;
  
  	if (WARN_ON(!new_state->hwpipe_to_plane[hwpipe->idx]))
  		return;
  
  	DBG("%s: release from plane %s", hwpipe->name,
  		new_state->hwpipe_to_plane[hwpipe->idx]->name);
  
  	if (mdp5_kms->smp) {
  		DBG("%s: free SMP blocks", hwpipe->name);
  		mdp5_smp_release(mdp5_kms->smp, &state->smp, hwpipe->pipe);
  	}
  
  	new_state->hwpipe_to_plane[hwpipe->idx] = NULL;
  }
  
  void mdp5_pipe_destroy(struct mdp5_hw_pipe *hwpipe)
  {
  	kfree(hwpipe);
  }
  
  struct mdp5_hw_pipe *mdp5_pipe_init(enum mdp5_pipe pipe,
  		uint32_t reg_offset, uint32_t caps)
  {
  	struct mdp5_hw_pipe *hwpipe;
  
  	hwpipe = kzalloc(sizeof(*hwpipe), GFP_KERNEL);
  	if (!hwpipe)
  		return ERR_PTR(-ENOMEM);
  
  	hwpipe->name = pipe2name(pipe);
  	hwpipe->pipe = pipe;
  	hwpipe->reg_offset = reg_offset;
  	hwpipe->caps = caps;
  	hwpipe->flush_mask = mdp_ctl_flush_mask_pipe(pipe);
  
  	spin_lock_init(&hwpipe->pipe_lock);
  
  	return hwpipe;
  }
  