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

Commit 9864fd76 authored by Dave Airlie's avatar Dave Airlie
Browse files

Merge tag 'drm-vc4-next-2016-02-17' of github.com:anholt/linux into drm-next

This pull request brings in overlay plane support for vc4.

* tag 'drm-vc4-next-2016-02-17' of github.com:anholt/linux:
  drm/vc4: Add support for YUV planes.
  drm/vc4: Add support a few more RGB display plane formats.
  drm/vc4: Add support for scaling of display planes.
  drm/vc4: Fix which value is being used for source image size.
  drm/vc4: Add more display planes to each CRTC.
  drm/vc4: Make the CRTCs cooperate on allocating display lists.
  drm/vc4: Add a proper short-circut path for legacy cursor updates.
  drm/vc4: Move the plane clipping/scaling setup to a separate function.
  drm/vc4: Add missing __iomem annotation to hw_dlist.
  drm/vc4: Improve comments on vc4_plane_state members.
parents 5263925c fc04023f
Loading
Loading
Loading
Loading
+104 −67
Original line number Diff line number Diff line
@@ -49,22 +49,27 @@ struct vc4_crtc {
	/* Which HVS channel we're using for our CRTC. */
	int channel;

	/* Pointer to the actual hardware display list memory for the
	 * crtc.
	 */
	u32 __iomem *dlist;

	u32 dlist_size; /* in dwords */

	struct drm_pending_vblank_event *event;
};

struct vc4_crtc_state {
	struct drm_crtc_state base;
	/* Dlist area for this CRTC configuration. */
	struct drm_mm_node mm;
};

static inline struct vc4_crtc *
to_vc4_crtc(struct drm_crtc *crtc)
{
	return (struct vc4_crtc *)crtc;
}

static inline struct vc4_crtc_state *
to_vc4_crtc_state(struct drm_crtc_state *crtc_state)
{
	return (struct vc4_crtc_state *)crtc_state;
}

struct vc4_crtc_data {
	/* Which channel of the HVS this pixelvalve sources from. */
	int hvs_channel;
@@ -319,11 +324,13 @@ static void vc4_crtc_enable(struct drm_crtc *crtc)
static int vc4_crtc_atomic_check(struct drm_crtc *crtc,
				 struct drm_crtc_state *state)
{
	struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(state);
	struct drm_device *dev = crtc->dev;
	struct vc4_dev *vc4 = to_vc4_dev(dev);
	struct drm_plane *plane;
	struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
	unsigned long flags;
	u32 dlist_count = 0;
	int ret;

	/* The pixelvalve can only feed one encoder (and encoders are
	 * 1:1 with connectors.)
@@ -346,18 +353,12 @@ static int vc4_crtc_atomic_check(struct drm_crtc *crtc,

	dlist_count++; /* Account for SCALER_CTL0_END. */

	if (!vc4_crtc->dlist || dlist_count > vc4_crtc->dlist_size) {
		vc4_crtc->dlist = ((u32 __iomem *)vc4->hvs->dlist +
				   HVS_BOOTLOADER_DLIST_END);
		vc4_crtc->dlist_size = ((SCALER_DLIST_SIZE >> 2) -
					HVS_BOOTLOADER_DLIST_END);

		if (dlist_count > vc4_crtc->dlist_size) {
			DRM_DEBUG_KMS("dlist too large for CRTC (%d > %d).\n",
				      dlist_count, vc4_crtc->dlist_size);
			return -EINVAL;
		}
	}
	spin_lock_irqsave(&vc4->hvs->mm_lock, flags);
	ret = drm_mm_insert_node(&vc4->hvs->dlist_mm, &vc4_state->mm,
				 dlist_count, 1, 0);
	spin_unlock_irqrestore(&vc4->hvs->mm_lock, flags);
	if (ret)
		return ret;

	return 0;
}
@@ -368,47 +369,29 @@ static void vc4_crtc_atomic_flush(struct drm_crtc *crtc,
	struct drm_device *dev = crtc->dev;
	struct vc4_dev *vc4 = to_vc4_dev(dev);
	struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
	struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc->state);
	struct drm_plane *plane;
	bool debug_dump_regs = false;
	u32 __iomem *dlist_next = vc4_crtc->dlist;
	u32 __iomem *dlist_start = vc4->hvs->dlist + vc4_state->mm.start;
	u32 __iomem *dlist_next = dlist_start;

	if (debug_dump_regs) {
		DRM_INFO("CRTC %d HVS before:\n", drm_crtc_index(crtc));
		vc4_hvs_dump_state(dev);
	}

	/* Copy all the active planes' dlist contents to the hardware dlist.
	 *
	 * XXX: If the new display list was large enough that it
	 * overlapped a currently-read display list, we need to do
	 * something like disable scanout before putting in the new
	 * list.  For now, we're safe because we only have the two
	 * planes.
	 */
	/* Copy all the active planes' dlist contents to the hardware dlist. */
	drm_atomic_crtc_for_each_plane(plane, crtc) {
		dlist_next += vc4_plane_write_dlist(plane, dlist_next);
	}

	if (dlist_next == vc4_crtc->dlist) {
		/* If no planes were enabled, use the SCALER_CTL0_END
		 * at the start of the display list memory (in the
		 * bootloader section).  We'll rewrite that
		 * SCALER_CTL0_END, just in case, though.
		 */
		writel(SCALER_CTL0_END, vc4->hvs->dlist);
		HVS_WRITE(SCALER_DISPLISTX(vc4_crtc->channel), 0);
	} else {
	writel(SCALER_CTL0_END, dlist_next);
	dlist_next++;

		HVS_WRITE(SCALER_DISPLISTX(vc4_crtc->channel),
			  (u32 __iomem *)vc4_crtc->dlist -
			  (u32 __iomem *)vc4->hvs->dlist);
	WARN_ON_ONCE(dlist_next - dlist_start != vc4_state->mm.size);

		/* Make the next display list start after ours. */
		vc4_crtc->dlist_size -= (dlist_next - vc4_crtc->dlist);
		vc4_crtc->dlist = dlist_next;
	}
	HVS_WRITE(SCALER_DISPLISTX(vc4_crtc->channel),
		  vc4_state->mm.start);

	if (debug_dump_regs) {
		DRM_INFO("CRTC %d HVS after:\n", drm_crtc_index(crtc));
@@ -573,6 +556,36 @@ static int vc4_page_flip(struct drm_crtc *crtc,
		return drm_atomic_helper_page_flip(crtc, fb, event, flags);
}

static struct drm_crtc_state *vc4_crtc_duplicate_state(struct drm_crtc *crtc)
{
	struct vc4_crtc_state *vc4_state;

	vc4_state = kzalloc(sizeof(*vc4_state), GFP_KERNEL);
	if (!vc4_state)
		return NULL;

	__drm_atomic_helper_crtc_duplicate_state(crtc, &vc4_state->base);
	return &vc4_state->base;
}

static void vc4_crtc_destroy_state(struct drm_crtc *crtc,
				   struct drm_crtc_state *state)
{
	struct vc4_dev *vc4 = to_vc4_dev(crtc->dev);
	struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(state);

	if (vc4_state->mm.allocated) {
		unsigned long flags;

		spin_lock_irqsave(&vc4->hvs->mm_lock, flags);
		drm_mm_remove_node(&vc4_state->mm);
		spin_unlock_irqrestore(&vc4->hvs->mm_lock, flags);

	}

	__drm_atomic_helper_crtc_destroy_state(crtc, state);
}

static const struct drm_crtc_funcs vc4_crtc_funcs = {
	.set_config = drm_atomic_helper_set_config,
	.destroy = vc4_crtc_destroy,
@@ -581,8 +594,8 @@ static const struct drm_crtc_funcs vc4_crtc_funcs = {
	.cursor_set = NULL, /* handled by drm_mode_cursor_universal */
	.cursor_move = NULL, /* handled by drm_mode_cursor_universal */
	.reset = drm_atomic_helper_crtc_reset,
	.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
	.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
	.atomic_duplicate_state = vc4_crtc_duplicate_state,
	.atomic_destroy_state = vc4_crtc_destroy_state,
};

static const struct drm_crtc_helper_funcs vc4_crtc_helper_funcs = {
@@ -644,9 +657,9 @@ static int vc4_crtc_bind(struct device *dev, struct device *master, void *data)
	struct vc4_dev *vc4 = to_vc4_dev(drm);
	struct vc4_crtc *vc4_crtc;
	struct drm_crtc *crtc;
	struct drm_plane *primary_plane, *cursor_plane;
	struct drm_plane *primary_plane, *cursor_plane, *destroy_plane, *temp;
	const struct of_device_id *match;
	int ret;
	int ret, i;

	vc4_crtc = devm_kzalloc(dev, sizeof(*vc4_crtc), GFP_KERNEL);
	if (!vc4_crtc)
@@ -675,27 +688,49 @@ static int vc4_crtc_bind(struct device *dev, struct device *master, void *data)
		goto err;
	}

	cursor_plane = vc4_plane_init(drm, DRM_PLANE_TYPE_CURSOR);
	if (IS_ERR(cursor_plane)) {
		dev_err(dev, "failed to construct cursor plane\n");
		ret = PTR_ERR(cursor_plane);
		goto err_primary;
	}

	drm_crtc_init_with_planes(drm, crtc, primary_plane, cursor_plane,
	drm_crtc_init_with_planes(drm, crtc, primary_plane, NULL,
				  &vc4_crtc_funcs, NULL);
	drm_crtc_helper_add(crtc, &vc4_crtc_helper_funcs);
	primary_plane->crtc = crtc;
	cursor_plane->crtc = crtc;
	vc4->crtc[drm_crtc_index(crtc)] = vc4_crtc;
	vc4_crtc->channel = vc4_crtc->data->hvs_channel;

	/* Set up some arbitrary number of planes.  We're not limited
	 * by a set number of physical registers, just the space in
	 * the HVS (16k) and how small an plane can be (28 bytes).
	 * However, each plane we set up takes up some memory, and
	 * increases the cost of looping over planes, which atomic
	 * modesetting does quite a bit.  As a result, we pick a
	 * modest number of planes to expose, that should hopefully
	 * still cover any sane usecase.
	 */
	for (i = 0; i < 8; i++) {
		struct drm_plane *plane =
			vc4_plane_init(drm, DRM_PLANE_TYPE_OVERLAY);

		if (IS_ERR(plane))
			continue;

		plane->possible_crtcs = 1 << drm_crtc_index(crtc);
	}

	/* Set up the legacy cursor after overlay initialization,
	 * since we overlay planes on the CRTC in the order they were
	 * initialized.
	 */
	cursor_plane = vc4_plane_init(drm, DRM_PLANE_TYPE_CURSOR);
	if (!IS_ERR(cursor_plane)) {
		cursor_plane->possible_crtcs = 1 << drm_crtc_index(crtc);
		cursor_plane->crtc = crtc;
		crtc->cursor = cursor_plane;
	}

	CRTC_WRITE(PV_INTEN, 0);
	CRTC_WRITE(PV_INTSTAT, PV_INT_VFP_START);
	ret = devm_request_irq(dev, platform_get_irq(pdev, 0),
			       vc4_crtc_irq_handler, 0, "vc4 crtc", vc4_crtc);
	if (ret)
		goto err_cursor;
		goto err_destroy_planes;

	vc4_set_crtc_possible_masks(drm, crtc);

@@ -703,10 +738,12 @@ static int vc4_crtc_bind(struct device *dev, struct device *master, void *data)

	return 0;

err_cursor:
	cursor_plane->funcs->destroy(cursor_plane);
err_primary:
	primary_plane->funcs->destroy(primary_plane);
err_destroy_planes:
	list_for_each_entry_safe(destroy_plane, temp,
				 &drm->mode_config.plane_list, head) {
		if (destroy_plane->possible_crtcs == 1 << drm_crtc_index(crtc))
		    destroy_plane->funcs->destroy(destroy_plane);
	}
err:
	return ret;
}
+11 −1
Original line number Diff line number Diff line
@@ -149,7 +149,17 @@ struct vc4_v3d {
struct vc4_hvs {
	struct platform_device *pdev;
	void __iomem *regs;
	void __iomem *dlist;
	u32 __iomem *dlist;

	/* Memory manager for CRTCs to allocate space in the display
	 * list.  Units are dwords.
	 */
	struct drm_mm dlist_mm;
	/* Memory manager for the LBM memory used by HVS scaling. */
	struct drm_mm lbm_mm;
	spinlock_t mm_lock;

	struct drm_mm_node mitchell_netravali_filter;
};

struct vc4_plane {
+97 −0
Original line number Diff line number Diff line
@@ -100,12 +100,76 @@ int vc4_hvs_debugfs_regs(struct seq_file *m, void *unused)
}
#endif

/* The filter kernel is composed of dwords each containing 3 9-bit
 * signed integers packed next to each other.
 */
#define VC4_INT_TO_COEFF(coeff) (coeff & 0x1ff)
#define VC4_PPF_FILTER_WORD(c0, c1, c2)				\
	((((c0) & 0x1ff) << 0) |				\
	 (((c1) & 0x1ff) << 9) |				\
	 (((c2) & 0x1ff) << 18))

/* The whole filter kernel is arranged as the coefficients 0-16 going
 * up, then a pad, then 17-31 going down and reversed within the
 * dwords.  This means that a linear phase kernel (where it's
 * symmetrical at the boundary between 15 and 16) has the last 5
 * dwords matching the first 5, but reversed.
 */
#define VC4_LINEAR_PHASE_KERNEL(c0, c1, c2, c3, c4, c5, c6, c7, c8,	\
				c9, c10, c11, c12, c13, c14, c15)	\
	{VC4_PPF_FILTER_WORD(c0, c1, c2),				\
	 VC4_PPF_FILTER_WORD(c3, c4, c5),				\
	 VC4_PPF_FILTER_WORD(c6, c7, c8),				\
	 VC4_PPF_FILTER_WORD(c9, c10, c11),				\
	 VC4_PPF_FILTER_WORD(c12, c13, c14),				\
	 VC4_PPF_FILTER_WORD(c15, c15, 0)}

#define VC4_LINEAR_PHASE_KERNEL_DWORDS 6
#define VC4_KERNEL_DWORDS (VC4_LINEAR_PHASE_KERNEL_DWORDS * 2 - 1)

/* Recommended B=1/3, C=1/3 filter choice from Mitchell/Netravali.
 * http://www.cs.utexas.edu/~fussell/courses/cs384g/lectures/mitchell/Mitchell.pdf
 */
static const u32 mitchell_netravali_1_3_1_3_kernel[] =
	VC4_LINEAR_PHASE_KERNEL(0, -2, -6, -8, -10, -8, -3, 2, 18,
				50, 82, 119, 155, 187, 213, 227);

static int vc4_hvs_upload_linear_kernel(struct vc4_hvs *hvs,
					struct drm_mm_node *space,
					const u32 *kernel)
{
	int ret, i;
	u32 __iomem *dst_kernel;

	ret = drm_mm_insert_node(&hvs->dlist_mm, space, VC4_KERNEL_DWORDS, 1,
				 0);
	if (ret) {
		DRM_ERROR("Failed to allocate space for filter kernel: %d\n",
			  ret);
		return ret;
	}

	dst_kernel = hvs->dlist + space->start;

	for (i = 0; i < VC4_KERNEL_DWORDS; i++) {
		if (i < VC4_LINEAR_PHASE_KERNEL_DWORDS)
			writel(kernel[i], &dst_kernel[i]);
		else {
			writel(kernel[VC4_KERNEL_DWORDS - i - 1],
			       &dst_kernel[i]);
		}
	}

	return 0;
}

static int vc4_hvs_bind(struct device *dev, struct device *master, void *data)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct drm_device *drm = dev_get_drvdata(master);
	struct vc4_dev *vc4 = drm->dev_private;
	struct vc4_hvs *hvs = NULL;
	int ret;

	hvs = devm_kzalloc(&pdev->dev, sizeof(*hvs), GFP_KERNEL);
	if (!hvs)
@@ -119,6 +183,33 @@ static int vc4_hvs_bind(struct device *dev, struct device *master, void *data)

	hvs->dlist = hvs->regs + SCALER_DLIST_START;

	spin_lock_init(&hvs->mm_lock);

	/* Set up the HVS display list memory manager.  We never
	 * overwrite the setup from the bootloader (just 128b out of
	 * our 16K), since we don't want to scramble the screen when
	 * transitioning from the firmware's boot setup to runtime.
	 */
	drm_mm_init(&hvs->dlist_mm,
		    HVS_BOOTLOADER_DLIST_END,
		    (SCALER_DLIST_SIZE >> 2) - HVS_BOOTLOADER_DLIST_END);

	/* Set up the HVS LBM memory manager.  We could have some more
	 * complicated data structure that allowed reuse of LBM areas
	 * between planes when they don't overlap on the screen, but
	 * for now we just allocate globally.
	 */
	drm_mm_init(&hvs->lbm_mm, 0, 96 * 1024);

	/* Upload filter kernels.  We only have the one for now, so we
	 * keep it around for the lifetime of the driver.
	 */
	ret = vc4_hvs_upload_linear_kernel(hvs,
					   &hvs->mitchell_netravali_filter,
					   mitchell_netravali_1_3_1_3_kernel);
	if (ret)
		return ret;

	vc4->hvs = hvs;
	return 0;
}
@@ -129,6 +220,12 @@ static void vc4_hvs_unbind(struct device *dev, struct device *master,
	struct drm_device *drm = dev_get_drvdata(master);
	struct vc4_dev *vc4 = drm->dev_private;

	if (vc4->hvs->mitchell_netravali_filter.allocated)
		drm_mm_remove_node(&vc4->hvs->mitchell_netravali_filter);

	drm_mm_takedown(&vc4->hvs->dlist_mm);
	drm_mm_takedown(&vc4->hvs->lbm_mm);

	vc4->hvs = NULL;
}

+9 −0
Original line number Diff line number Diff line
@@ -49,6 +49,15 @@ vc4_atomic_complete_commit(struct vc4_commit *c)

	drm_atomic_helper_commit_modeset_enables(dev, state);

	/* Make sure that drm_atomic_helper_wait_for_vblanks()
	 * actually waits for vblank.  If we're doing a full atomic
	 * modeset (as opposed to a vc4_update_plane() short circuit),
	 * then we need to wait for scanout to be done with our
	 * display lists before we free it and potentially reallocate
	 * and overwrite the dlist memory with a new modeset.
	 */
	state->legacy_cursor_update = false;

	drm_atomic_helper_wait_for_vblanks(dev, state);

	drm_atomic_helper_cleanup_planes(dev, state);
+550 −53

File changed.

Preview size limit exceeded, changes collapsed.

Loading