Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5784022a authored by Xiaowen Wu's avatar Xiaowen Wu
Browse files

drm/msm/shd: add shared display support



Add support to shared display for panel stacking and split screen.
The current DRM framework only allows one atomic commit for
each display, this change will create virtual display pipelines
based on the physical display pipeline, and each virtual display
can support independent async atomic commit.

CRs-Fixed: 2391344
Change-Id: I06c55b6f894eef6ded852fc315c84af8568d8a75
Signed-off-by: default avatarXiaowen Wu <wxiaowen@codeaurora.org>
parent d53987ea
Loading
Loading
Loading
Loading
+101 −0
Original line number Diff line number Diff line
QTI Snapdragon Display Engine (SDE) shared display

Required properties:
- compatible:		"qcom,shared-display"
- qcom,shared-display-base: node handle of qcom,shared-display-base, see below.
- qcom,blend-stage-range: A pair of u32 property <start size> defines the blend
			stage start and size for the shared display. For example
			<4 5> indicates this shared display's blending stage starts
			from 4 and there are 5 stages in total. Different shared
			display located on the same base display should not overlap
			with their blend stages. The maximum stages should be within
			the range of layer mixer hardware capability.
- qcom,shared-display-src-mode: source resolution of the shared display, which is also
			the framebuffer size. It contains two properties:
			qcom,mode-h-active: A u32 property defines horizontal resolution.
			qcom,mode-v-active: A u32 property defines vertical resolution.
- qcom,shared-display-dst-mode: destination rectangle in the shared display, which also
			includes the padding lines if line padding feature is enabled.
			It contains 4 properties:
			qcom,mode-x-offset: A u32 property of x offset.
			qcom,mode-y-offset: A u32 property of y offset.
			qcom,mode-width: A u32 property of width.
			qcom,mode-height: A u32 property of height, including line padding.


qcom,shared-display-base properties:
- qcom,shared-display-base-intf: A u32 property defines intf index of the base display.
- qcom,shared-display-base-mst: A u32 property defines the DP MST branch port, needed if
			the base display sits on DP MST display.
- qcom,shared-display-base-mode: timing of the physical base display, contains the
			following properties:
			qcom,mode-h-active: A u32 property defines the horizontal active size.
			qcom,mode-h-front-porch: A u32 property defines the horizontal front porch.
			qcom,mode-h-pulse-width: A u32 property defines the horizontal pulse.
			qcom,mode-h-back-porch: A u32 property defines the horizontal back porch.
			qcom,mode-h-active-high: A boolean property defines if horizontal polarity
						is high.
			qcom,mode-v-active: A u32 property defines the vertical active size.
			qcom,mode-v-front-porch: A u32 property defines the vertical front portch.
			qcom,mode-v-pulse-width: A u32 property defines the vertical pulse width.
			qcom,mode-v-back-porch: A u32 property defines the vertical back porch.
			qcom,mode-v-active-high: A boolean property defines if vertical polarity
						is high.
			qcom,mode-refresh-rate: A u32 property defines vertial refresh rate.
			qcom,mode-clock-in-khz: A u32 property defines clock in kHz.

Example:

/ {
	...

	sde_sh_base0: qcom,shared-display-base@0 {
		qcom,shared-display-base-intf = <0>;
		qcom,shared-display-base-mode {
			qcom,mode-h-active = <3840>;
			qcom,mode-h-front-porch = <176>;
			qcom,mode-h-pulse-width = <88>;
			qcom,mode-h-back-porch = <296>;
			qcom,mode-h-active-high;
			qcom,mode-v-active = <2160>;
			qcom,mode-v-front-porch = <8>;
			qcom,mode-v-pulse-width = <10>;
			qcom,mode-v-back-porch = <72>;
			qcom,mode-v-active-high;
			qcom,mode-refresh-rate = <30>;
			qcom,mode-clock-in-khz = <297000>;
		};
	};

	sde_sh0: qcom,shared-display@0 {
		compatible = "qcom,shared-display";
		qcom,shared-display-base = <&sde_sh_base0>;
		qcom,blend-stage-range = <0 5>;
		qcom,shared-display-src-mode {
			qcom,mode-h-active = <1920>;
			qcom,mode-v-active = <2160>;
		};
		qcom,shared-display-dst-mode {
			qcom,mode-x-offset = <0>;
			qcom,mode-y-offset = <0>;
			qcom,mode-width = <1920>;
			qcom,mode-height = <2160>;
		};
	};

	sde_sh1: qcom,shared-display@1 {
		compatible = "qcom,shared-display";
		qcom,shared-display-base = <&sde_sh_base0>;
		qcom,blend-stage-range = <5 5>;
		qcom,shared-display-src-mode {
			qcom,mode-h-active = <1920>;
			qcom,mode-v-active = <2160>;
		};
		qcom,shared-display-dst-mode {
			qcom,mode-x-offset = <1920>;
			qcom,mode-y-offset = <0>;
			qcom,mode-width = <1920>;
			qcom,mode-height = <2160>;
		};
	};
};
+10 −0
Original line number Diff line number Diff line
@@ -152,6 +152,16 @@ config DRM_SDE_WB
	  the output image is written back to memory in the format
	  selected by the connector's mode and property settings.

config DRM_SDE_SHD
	bool "Enable Shared display support in SDE DRM"
	depends on DRM_MSM
	help
	  Choose this option for shared display support.
	  This option enables multiple logical displays
	  to share one base physical encoder/connector.
	  Each logical display will appear as different
	  connectors and report back to user.

config DRM_MSM_DSI_14NM_PHY
	bool "Enable DSI 14nm PHY driver in MSM DRM (used by MSM8996/APQ8096)"
	depends on DRM_MSM_DSI
+5 −0
Original line number Diff line number Diff line
@@ -6,6 +6,7 @@ ccflags-$(CONFIG_DRM_MSM_DSI_PLL) += -Idrivers/gpu/drm/msm/dsi
ccflags-y += -Idrivers/gpu/drm/msm/sde
ccflags-y += -Idrivers/media/platform/msm/sde/rotator
ccflags-y += -Idrivers/gpu/drm/msm/hdmi
ccflags-$(CONFIG_DRM_SDE_SHD) += -Idrivers/gpu/drm/msm/shd

msm_drm-y := \
	dp/dp_usbpd.o \
@@ -186,6 +187,10 @@ msm_drm-$(CONFIG_DRM_MSM) += \
msm_drm-$(CONFIG_DRM_SDE_WB) += sde/sde_wb.o \
	sde/sde_encoder_phys_wb.o

msm_drm-$(CONFIG_DRM_SDE_SHD) += shd/shd_drm.o \
	shd/shd_hw.o \
	shd/sde_encoder_phys_shd.o

msm_drm-$(CONFIG_DRM_MSM) += \
	msm_atomic.o \
	msm_drv.o \
+711 −0
Original line number Diff line number Diff line
/* Copyright (c) 2015-2019, The Linux Foundation. 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.
 */

#define pr_fmt(fmt)	"[drm-shd:%s:%d] " fmt, __func__, __LINE__

#include <linux/debugfs.h>
#include <uapi/drm/sde_drm.h>

#include "sde_encoder_phys.h"
#include "sde_formats.h"
#include "sde_hw_top.h"
#include "sde_hw_interrupts.h"
#include "sde_core_irq.h"
#include "sde_crtc.h"
#include "sde_trace.h"
#include "sde_plane.h"
#include "shd_drm.h"
#include "shd_hw.h"

#define SDE_ERROR_PHYS(p, fmt, ...) SDE_ERROR("enc%d intf%d " fmt,\
		(p) ? (p)->parent->base.id : -1, \
		(p) ? (p)->intf_idx - INTF_0 : -1, \
	##__VA_ARGS__)

#define SDE_DEBUG_PHYS(p, fmt, ...) SDE_DEBUG("enc%d intf%d " fmt,\
			(p) ? (p)->parent->base.id : -1, \
			(p) ? (p)->intf_idx - INTF_0 : -1, \
	##__VA_ARGS__)

/**
 * struct sde_encoder_phys_shd - sub-class of sde_encoder_phys to handle shared
 *	mode specific operations
 * @base:	Baseclass physical encoder structure
 * @hw_lm:	HW LM blocks created by this shared encoder
 * @hw_ctl:	HW CTL blocks created by this shared encoder
 * @num_mixers:	Number of LM blocks
 * @num_ctls:	Number of CTL blocks
 */
struct sde_encoder_phys_shd {
	struct sde_encoder_phys base;
	struct sde_hw_mixer *hw_lm[CRTC_DUAL_MIXERS];
	struct sde_hw_ctl *hw_ctl[CRTC_DUAL_MIXERS];
	u32 num_mixers;
	u32 num_ctls;
};

#define to_sde_encoder_phys_shd(x) \
	container_of(x, struct sde_encoder_phys_shd, base)

static inline
bool sde_encoder_phys_shd_is_master(struct sde_encoder_phys *phys_enc)
{
	return true;
}

static void sde_encoder_phys_shd_vblank_irq(void *arg, int irq_idx)
{
	struct sde_encoder_phys *phys_enc = arg;
	struct sde_hw_ctl *hw_ctl;
	struct sde_shd_hw_ctl *shd_ctl;
	unsigned long lock_flags;
	u32 flush_register = ~0;
	int new_cnt = -1, old_cnt = -1;
	u32 event = 0;

	if (!phys_enc)
		return;

	hw_ctl = phys_enc->hw_ctl;
	if (!hw_ctl)
		return;

	SDE_ATRACE_BEGIN("vblank_irq");

	/*
	 * only decrement the pending flush count if we've actually flushed
	 * hardware. due to sw irq latency, vblank may have already happened
	 * so we need to double-check with hw that it accepted the flush bits
	 */
	spin_lock_irqsave(phys_enc->enc_spinlock, lock_flags);

	old_cnt = atomic_read(&phys_enc->pending_kickoff_cnt);

	if (hw_ctl && hw_ctl->ops.get_flush_register)
		flush_register = hw_ctl->ops.get_flush_register(hw_ctl);

	shd_ctl = container_of(hw_ctl, struct sde_shd_hw_ctl, base);

	if (flush_register)
		SDE_DEBUG("%d irq flush=0x%x mask=0x%x\n",
			DRMID(phys_enc->parent),
			flush_register, shd_ctl->flush_mask);

	if (flush_register & shd_ctl->flush_mask)
		goto not_flushed;

	new_cnt = atomic_add_unless(&phys_enc->pending_kickoff_cnt, -1, 0);

	if (atomic_add_unless(&phys_enc->pending_retire_fence_cnt, -1, 0))
		event |= SDE_ENCODER_FRAME_EVENT_SIGNAL_RETIRE_FENCE |
			SDE_ENCODER_FRAME_EVENT_SIGNAL_RELEASE_FENCE;

not_flushed:
	spin_unlock_irqrestore(phys_enc->enc_spinlock, lock_flags);

	if (event && phys_enc->parent_ops.handle_frame_done)
		phys_enc->parent_ops.handle_frame_done(phys_enc->parent,
			phys_enc, event);

	if (phys_enc->parent_ops.handle_vblank_virt)
		phys_enc->parent_ops.handle_vblank_virt(phys_enc->parent,
				phys_enc);

	SDE_EVT32_IRQ(DRMID(phys_enc->parent), phys_enc->hw_intf->idx - INTF_0,
		old_cnt, new_cnt,
		flush_register, event);

	/* Signal any waiting atomic commit thread */
	wake_up_all(&phys_enc->pending_kickoff_wq);

	SDE_ATRACE_END("vblank_irq");
}

static int _sde_encoder_phys_shd_register_irq(
		struct sde_encoder_phys *phys_enc,
		enum sde_intr_idx intr_idx,
		bool enable)
{
	struct sde_encoder_irq *irq = &phys_enc->irq[intr_idx];
	int ret = 0;

	SDE_DEBUG("%d enable %d\n", DRMID(phys_enc->parent), enable);

	if (enable) {
		if (irq->irq_idx >= 0) {
			SDE_DEBUG_PHYS(phys_enc,
				"skipping already registered irq %s type %d\n",
				irq->name, irq->intr_type);
			return 0;
		}

		irq->irq_idx = sde_core_irq_idx_lookup(phys_enc->sde_kms,
				irq->intr_type, irq->hw_idx);
		if (irq->irq_idx < 0) {
			SDE_ERROR_PHYS(phys_enc,
				"failed to lookup IRQ index for %s type:%d\n",
				irq->name, irq->intr_type);
			return -EINVAL;
		}

		ret = sde_core_irq_register_callback(phys_enc->sde_kms,
				irq->irq_idx, &irq->cb);
		if (ret) {
			SDE_ERROR_PHYS(phys_enc,
				"failed to register IRQ callback for %s\n",
				irq->name);
			irq->irq_idx = -EINVAL;
		}
	} else {
		if (irq->irq_idx < 0) {
			SDE_DEBUG_PHYS(phys_enc,
				"extra unregister irq, enc%d intr_idx:0x%x\n",
				DRMID(phys_enc->parent), INTR_IDX_VSYNC);
			return 0;
		}

		ret = sde_core_irq_unregister_callback(phys_enc->sde_kms,
				irq->irq_idx, &irq->cb);
		if (ret) {
			SDE_ERROR_PHYS(phys_enc,
				"failed to unregister IRQ callback for %s\n",
				irq->name);
		}

		irq->irq_idx = -EINVAL;
	}

	return ret;
}

static inline
void _sde_encoder_phys_shd_setup_irq_hw_idx(
		struct sde_encoder_phys *phys_enc)
{
	struct sde_encoder_irq *irq;

	irq = &phys_enc->irq[INTR_IDX_VSYNC];

	if (irq->irq_idx < 0)
		irq->hw_idx = phys_enc->intf_idx;
}

static int _sde_encoder_phys_shd_rm_reserve(
		struct sde_encoder_phys *phys_enc,
		struct shd_display *display)
{
	struct sde_encoder_phys_shd *shd_enc;
	struct sde_rm *rm;
	struct sde_rm_hw_iter ctl_iter, lm_iter, pp_iter;
	struct drm_encoder *encoder;
	struct sde_shd_hw_ctl *hw_ctl;
	struct sde_shd_hw_mixer *hw_lm;
	struct sde_hw_pingpong *hw_pp;
	int i, rc = 0;

	encoder = display->base->encoder;
	rm = &phys_enc->sde_kms->rm;
	shd_enc = to_sde_encoder_phys_shd(phys_enc);

	sde_rm_init_hw_iter(&ctl_iter, encoder->base.id, SDE_HW_BLK_CTL);
	sde_rm_init_hw_iter(&lm_iter, encoder->base.id, SDE_HW_BLK_LM);
	sde_rm_init_hw_iter(&pp_iter, encoder->base.id, SDE_HW_BLK_PINGPONG);

	shd_enc->num_mixers = 0;
	shd_enc->num_ctls = 0;

	for (i = 0; i < CRTC_DUAL_MIXERS; i++) {
		/* reserve lm */
		if (!sde_rm_get_hw(rm, &lm_iter))
			break;
		hw_lm = container_of(shd_enc->hw_lm[i],
				struct sde_shd_hw_mixer, base);
		hw_lm->base = *(struct sde_hw_mixer *)lm_iter.hw;
		hw_lm->range = display->stage_range;
		hw_lm->roi = display->roi;
		hw_lm->orig = lm_iter.hw;
		sde_shd_hw_lm_init_op(&hw_lm->base);

		SDE_DEBUG("reserve LM%d %pK from enc %d to %d\n",
			hw_lm->base.idx, hw_lm,
			DRMID(encoder),
			DRMID(phys_enc->parent));

		rc = sde_rm_ext_blk_create_reserve(rm,
			&hw_lm->base.base, phys_enc->parent);
		if (rc) {
			SDE_ERROR("failed to create & reserve lm\n");
			break;
		}
		shd_enc->num_mixers++;

		/* reserve ctl */
		if (!sde_rm_get_hw(rm, &ctl_iter))
			break;
		hw_ctl = container_of(shd_enc->hw_ctl[i],
				struct sde_shd_hw_ctl, base);
		hw_ctl->base = *(struct sde_hw_ctl *)ctl_iter.hw;
		hw_ctl->range = display->stage_range;
		hw_ctl->orig = ctl_iter.hw;
		sde_shd_hw_ctl_init_op(&hw_ctl->base);

		SDE_DEBUG("reserve CTL%d %pK from enc %d to %d\n",
			hw_ctl->base.idx, hw_ctl,
			DRMID(encoder),
			DRMID(phys_enc->parent));

		rc = sde_rm_ext_blk_create_reserve(rm,
			&hw_ctl->base.base, phys_enc->parent);
		if (rc) {
			SDE_ERROR("failed to create & reserve ctl\n");
			break;
		}
		shd_enc->num_ctls++;

		/* reserve pingpong */
		if (!sde_rm_get_hw(rm, &pp_iter))
			break;
		hw_pp = pp_iter.hw;

		SDE_DEBUG("reserve PP%d from enc %d to %d\n",
			hw_pp->idx,
			DRMID(encoder),
			DRMID(phys_enc->parent));

		rc = sde_rm_ext_blk_create_reserve(rm,
			&hw_pp->base, phys_enc->parent);
		if (rc) {
			SDE_ERROR("failed to create & reserve pingpong\n");
			break;
		}
	}

	return rc;
}

static inline void _sde_encoder_phys_shd_rm_release(
		struct sde_encoder_phys *phys_enc,
		struct shd_display *display)
{
	struct sde_rm *rm;

	rm = &phys_enc->sde_kms->rm;

	sde_rm_ext_blk_destroy(rm, phys_enc->parent);
}

static void sde_encoder_phys_shd_mode_set(
		struct sde_encoder_phys *phys_enc,
		struct drm_display_mode *mode,
		struct drm_display_mode *adj_mode)
{
	struct drm_connector *connector;
	struct sde_connector *sde_conn;
	struct shd_display *display;
	struct drm_encoder *encoder;
	struct sde_rm_hw_iter iter;
	struct sde_rm *rm;

	SDE_DEBUG("%d\n", phys_enc->parent->base.id);

	phys_enc->cached_mode = *adj_mode;

	connector = phys_enc->connector;
	if (!connector || connector->encoder != phys_enc->parent) {
		SDE_ERROR("failed to find connector\n");
		return;
	}

	sde_conn = to_sde_connector(connector);
	display = sde_conn->display;
	encoder = display->base->encoder;

	if (_sde_encoder_phys_shd_rm_reserve(phys_enc, display))
		return;

	rm = &phys_enc->sde_kms->rm;

	sde_rm_init_hw_iter(&iter, DRMID(phys_enc->parent), SDE_HW_BLK_CTL);
	if (sde_rm_get_hw(rm, &iter))
		phys_enc->hw_ctl = (struct sde_hw_ctl *)iter.hw;
	if (IS_ERR_OR_NULL(phys_enc->hw_ctl)) {
		SDE_DEBUG("failed to init ctl, %ld\n",
				PTR_ERR(phys_enc->hw_ctl));
		phys_enc->hw_ctl = NULL;
		return;
	}

	sde_rm_init_hw_iter(&iter, DRMID(encoder), SDE_HW_BLK_INTF);
	if (sde_rm_get_hw(rm, &iter))
		phys_enc->hw_intf = (struct sde_hw_intf *)iter.hw;
	if (IS_ERR_OR_NULL(phys_enc->hw_intf)) {
		SDE_DEBUG("failed to init intf: %ld\n",
				PTR_ERR(phys_enc->hw_intf));
		phys_enc->hw_intf = NULL;
		return;
	}

	sde_rm_init_hw_iter(&iter, DRMID(encoder), SDE_HW_BLK_PINGPONG);
	if (sde_rm_get_hw(rm, &iter))
		phys_enc->hw_pp = (struct sde_hw_pingpong *)iter.hw;
	if (IS_ERR_OR_NULL(phys_enc->hw_pp)) {
		SDE_DEBUG("failed to init pingpong: %ld\n",
				PTR_ERR(phys_enc->hw_pp));
		phys_enc->hw_pp = NULL;
		return;
	}

	_sde_encoder_phys_shd_setup_irq_hw_idx(phys_enc);
}

static int _sde_encoder_phys_shd_wait_for_vblank(
		struct sde_encoder_phys *phys_enc, bool notify)
{
	struct sde_encoder_wait_info wait_info;
	int ret = 0;
	u32 event = 0;
	u32 event_helper = 0;

	if (!phys_enc) {
		pr_err("invalid encoder\n");
		return -EINVAL;
	}

	wait_info.wq = &phys_enc->pending_kickoff_wq;
	wait_info.atomic_cnt = &phys_enc->pending_kickoff_cnt;
	wait_info.timeout_ms = KICKOFF_TIMEOUT_MS;

	/* Wait for kickoff to complete */
	ret = sde_encoder_helper_wait_for_irq(phys_enc, INTR_IDX_VSYNC,
			&wait_info);

	event_helper = SDE_ENCODER_FRAME_EVENT_SIGNAL_RELEASE_FENCE
			| SDE_ENCODER_FRAME_EVENT_SIGNAL_RETIRE_FENCE;

	if (notify) {
		if (ret == -ETIMEDOUT) {
			event = SDE_ENCODER_FRAME_EVENT_ERROR;
			if (atomic_add_unless(
				&phys_enc->pending_retire_fence_cnt, -1, 0))
				event |= event_helper;
		} else if (!ret) {
			event = SDE_ENCODER_FRAME_EVENT_DONE;
		}
	}

	SDE_EVT32(DRMID(phys_enc->parent), event, notify, ret,
			ret ? SDE_EVTLOG_FATAL : 0);
	if (phys_enc->parent_ops.handle_frame_done && event)
		phys_enc->parent_ops.handle_frame_done(
				phys_enc->parent, phys_enc,
				event);
	return ret;
}

static inline
int sde_encoder_phys_shd_wait_for_vblank(
		struct sde_encoder_phys *phys_enc)
{
	return _sde_encoder_phys_shd_wait_for_vblank(phys_enc, true);
}

static inline
int sde_encoder_phys_shd_wait_for_vblank_no_notify(
		struct sde_encoder_phys *phys_enc)
{
	return _sde_encoder_phys_shd_wait_for_vblank(phys_enc, false);
}

static inline
int sde_encoder_phys_shd_prepare_for_kickoff(
		struct sde_encoder_phys *phys_enc,
		struct sde_encoder_kickoff_params *params)
{
	return 0;
}

static inline
void sde_encoder_phys_shd_handle_post_kickoff(
	struct sde_encoder_phys *phys_enc)
{
	if (!phys_enc || !phys_enc->hw_intf) {
		SDE_ERROR("invalid encoder\n");
		return;
	}

	if (phys_enc->enable_state == SDE_ENC_ENABLING) {
		SDE_EVT32(DRMID(phys_enc->parent),
				phys_enc->hw_intf->idx - INTF_0);
		phys_enc->enable_state = SDE_ENC_ENABLED;
	}
}

static inline
void sde_encoder_phys_shd_trigger_flush(
	struct sde_encoder_phys *phys_enc)
{
	struct sde_encoder_phys_shd *shd_enc;

	shd_enc = container_of(phys_enc, struct sde_encoder_phys_shd, base);

	sde_shd_hw_flush(phys_enc->hw_ctl,
			shd_enc->hw_lm, shd_enc->num_mixers);
}

static void sde_encoder_phys_shd_enable(struct sde_encoder_phys *phys_enc)
{
	struct drm_connector *connector;

	SDE_DEBUG("%d\n", phys_enc->parent->base.id);

	if (!phys_enc->parent || !phys_enc->parent->dev) {
		SDE_ERROR("invalid drm device\n");
		return;
	}

	connector = phys_enc->connector;
	if (!connector || connector->encoder != phys_enc->parent) {
		SDE_ERROR("failed to find connector\n");
		return;
	}

	if (phys_enc->enable_state == SDE_ENC_DISABLED)
		phys_enc->enable_state = SDE_ENC_ENABLING;

	SDE_EVT32(DRMID(phys_enc->parent),
		atomic_read(&phys_enc->pending_retire_fence_cnt));
}

static void sde_encoder_phys_shd_disable(struct sde_encoder_phys *phys_enc)
{
	struct sde_connector *sde_conn;
	struct shd_display *display;

	SDE_DEBUG("%d\n", phys_enc->parent->base.id);

	if (!phys_enc || !phys_enc->parent || !phys_enc->parent->dev ||
			!phys_enc->parent->dev->dev_private) {
		SDE_ERROR("invalid encoder/device\n");
		return;
	}

	if (!phys_enc->hw_intf || !phys_enc->hw_ctl) {
		SDE_ERROR("invalid hw_intf %d hw_ctl %d\n",
				phys_enc->hw_intf != 0, phys_enc->hw_ctl != 0);
		return;
	}

	if (phys_enc->enable_state == SDE_ENC_DISABLED) {
		SDE_ERROR("already disabled\n");
		return;
	}

	sde_encoder_helper_reset_mixers(phys_enc, NULL);

	sde_encoder_phys_shd_trigger_flush(phys_enc);

	sde_encoder_phys_shd_wait_for_vblank_no_notify(phys_enc);

	phys_enc->enable_state = SDE_ENC_DISABLED;

	if (!phys_enc->connector)
		return;

	sde_conn = to_sde_connector(phys_enc->connector);
	display = sde_conn->display;

	_sde_encoder_phys_shd_rm_release(phys_enc, display);

	SDE_EVT32(DRMID(phys_enc->parent),
		atomic_read(&phys_enc->pending_retire_fence_cnt));
}

static inline
void sde_encoder_phys_shd_destroy(struct sde_encoder_phys *phys_enc)
{
	struct sde_encoder_phys_shd *shd_enc =
		to_sde_encoder_phys_shd(phys_enc);

	if (!phys_enc)
		return;

	kfree(shd_enc);
}

static int sde_encoder_phys_shd_control_vblank_irq(
		struct sde_encoder_phys *phys_enc,
		bool enable)
{
	int ret = 0;
	struct sde_encoder_phys_shd *shd_enc;
	int refcount;

	if (!phys_enc || !phys_enc->hw_intf) {
		SDE_ERROR("invalid encoder\n");
		return -EINVAL;
	}

	refcount = atomic_read(&phys_enc->vblank_refcount);
	shd_enc = to_sde_encoder_phys_shd(phys_enc);

	/* protect against negative */
	if (!enable && refcount == 0) {
		ret = -EINVAL;
		goto end;
	}

	SDE_DEBUG("[%pS] %d enable=%d/%d\n",
			__builtin_return_address(0), DRMID(phys_enc->parent),
			enable, atomic_read(&phys_enc->vblank_refcount));

	SDE_EVT32(DRMID(phys_enc->parent), enable,
			atomic_read(&phys_enc->vblank_refcount));

	if (enable && atomic_inc_return(&phys_enc->vblank_refcount) == 1) {
		ret = _sde_encoder_phys_shd_register_irq(phys_enc,
				INTR_IDX_VSYNC, true);
		if (ret)
			atomic_dec_return(&phys_enc->vblank_refcount);
	} else if (!enable &&
			atomic_dec_return(&phys_enc->vblank_refcount) == 0) {
		ret = _sde_encoder_phys_shd_register_irq(phys_enc,
				INTR_IDX_VSYNC, false);
		if (ret)
			atomic_inc_return(&phys_enc->vblank_refcount);
	}

end:
	if (ret) {
		SDE_DEBUG("control vblank irq error %d, enable %d\n",
				ret, enable);
		SDE_EVT32(DRMID(phys_enc->parent),
				phys_enc->hw_intf->idx - INTF_0,
				enable, refcount, SDE_EVTLOG_ERROR);
	}
	return ret;
}

static inline
void sde_encoder_phys_shd_irq_ctrl(
		struct sde_encoder_phys *phys_enc, bool enable)
{
	sde_encoder_phys_shd_control_vblank_irq(phys_enc, enable);
}

static inline
int sde_encoder_phys_shd_get_line_count(
		struct sde_encoder_phys *phys)
{
	return 0;
}

/**
 * sde_encoder_phys_shd_init_ops - initialize writeback operations
 * @ops:	Pointer to encoder operation table
 */
static void sde_encoder_phys_shd_init_ops(struct sde_encoder_phys_ops *ops)
{
	ops->is_master = sde_encoder_phys_shd_is_master;
	ops->mode_set = sde_encoder_phys_shd_mode_set;
	ops->enable = sde_encoder_phys_shd_enable;
	ops->disable = sde_encoder_phys_shd_disable;
	ops->destroy = sde_encoder_phys_shd_destroy;
	ops->wait_for_commit_done = sde_encoder_phys_shd_wait_for_vblank;
	ops->wait_for_vblank = sde_encoder_phys_shd_wait_for_vblank_no_notify;
	ops->prepare_for_kickoff = sde_encoder_phys_shd_prepare_for_kickoff;
	ops->handle_post_kickoff = sde_encoder_phys_shd_handle_post_kickoff;
	ops->trigger_flush = sde_encoder_phys_shd_trigger_flush;
	ops->control_vblank_irq = sde_encoder_phys_shd_control_vblank_irq;
	ops->wait_for_tx_complete = sde_encoder_phys_shd_wait_for_vblank;
	ops->irq_control = sde_encoder_phys_shd_irq_ctrl;
	ops->get_line_count = sde_encoder_phys_shd_get_line_count;
}

void *sde_encoder_phys_shd_init(enum sde_intf_type type,
			u32 controller_id, void *phys_init_params)
{
	struct sde_enc_phys_init_params *p = phys_init_params;
	struct sde_encoder_phys *phys_enc;
	struct sde_encoder_phys_shd *shd_enc;
	struct sde_encoder_irq *irq;
	struct sde_shd_hw_ctl *hw_ctl;
	struct sde_shd_hw_mixer *hw_lm;
	int ret = 0, i;

	SDE_DEBUG("\n");

	shd_enc = kzalloc(sizeof(*shd_enc), GFP_KERNEL);
	if (!shd_enc) {
		ret = -ENOMEM;
		goto fail_alloc;
	}

	for (i = 0; i < CRTC_DUAL_MIXERS; i++) {
		hw_ctl = kzalloc(sizeof(*hw_ctl), GFP_KERNEL);
		if (!hw_ctl) {
			ret = -ENOMEM;
			goto fail_ctl;
		}
		shd_enc->hw_ctl[i] = &hw_ctl->base;

		hw_lm = kzalloc(sizeof(*hw_lm), GFP_KERNEL);
		if (!hw_lm) {
			ret = -ENOMEM;
			goto fail_ctl;
		}
		shd_enc->hw_lm[i] = &hw_lm->base;
	}

	phys_enc = &shd_enc->base;

	sde_encoder_phys_shd_init_ops(&phys_enc->ops);
	phys_enc->parent = p->parent;
	phys_enc->parent_ops = p->parent_ops;
	phys_enc->sde_kms = p->sde_kms;
	phys_enc->split_role = p->split_role;
	phys_enc->intf_mode = INTF_MODE_NONE;
	phys_enc->intf_idx = INTF_0 + controller_id;
	phys_enc->enc_spinlock = p->enc_spinlock;
	atomic_set(&phys_enc->pending_retire_fence_cnt, 0);

	irq = &phys_enc->irq[INTR_IDX_VSYNC];
	irq->name = "vsync_irq";
	irq->intr_type = SDE_IRQ_TYPE_INTF_VSYNC;
	irq->intr_idx = INTR_IDX_VSYNC;
	irq->cb.func = sde_encoder_phys_shd_vblank_irq;

	for (i = 0; i < INTR_IDX_MAX; i++) {
		irq = &phys_enc->irq[i];
		INIT_LIST_HEAD(&irq->cb.list);
		irq->irq_idx = -EINVAL;
		irq->hw_idx = -EINVAL;
		irq->cb.arg = phys_enc;
	}

	atomic_set(&phys_enc->vblank_refcount, 0);
	atomic_set(&phys_enc->pending_kickoff_cnt, 0);
	atomic_set(&phys_enc->pending_retire_fence_cnt, 0);
	init_waitqueue_head(&phys_enc->pending_kickoff_wq);
	phys_enc->enable_state = SDE_ENC_DISABLED;

	return phys_enc;

fail_ctl:
	for (i = 0; i < CRTC_DUAL_MIXERS; i++) {
		kfree(shd_enc->hw_ctl[i]);
		kfree(shd_enc->hw_lm[i]);
	}
	kfree(shd_enc);
fail_alloc:
	return ERR_PTR(ret);
}
+1405 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading