mirror of
				https://github.com/Ysurac/openmptcprouter.git
				synced 2025-03-09 15:40:20 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			2476 lines
		
	
	
	
		
			70 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
			
		
		
	
	
			2476 lines
		
	
	
	
		
			70 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
| From 8624a2fed0d56d403a16d050d8980595e48a681b Mon Sep 17 00:00:00 2001
 | |
| From: Dave Stevenson <dave.stevenson@raspberrypi.com>
 | |
| Date: Mon, 7 Sep 2020 17:32:27 +0100
 | |
| Subject: [PATCH] drm/vc4: Add firmware-kms mode
 | |
| 
 | |
| This is a squash of all firmware-kms related patches from previous
 | |
| branches, up to and including
 | |
| "drm/vc4: Set the possible crtcs mask correctly for planes with FKMS"
 | |
| plus a couple of minor fixups for the 5.9 branch.
 | |
| Please refer to earlier branches for full history.
 | |
| 
 | |
| This patch includes work by Eric Anholt, James Hughes, Phil Elwell,
 | |
| Dave Stevenson, Dom Cobley, and Jonathon Bell.
 | |
| 
 | |
| Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
 | |
| 
 | |
| drm/vc4: Fixup firmware-kms after "drm/atomic: Pass the full state to CRTC atomic enable/disable"
 | |
| 
 | |
| Prototype for those calls changed, so amend fkms (which isn't
 | |
| upstream) to match.
 | |
| 
 | |
| Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
 | |
| 
 | |
| drm/vc4: Fixup fkms for API change
 | |
| 
 | |
| Atomic flush and check changed API, so fix up the downstream-only
 | |
| FKMS driver.
 | |
| 
 | |
| Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
 | |
| 
 | |
| drm/vc4: Make normalize_zpos conditional on using fkms
 | |
| 
 | |
| Eric's view was that there was no point in having zpos
 | |
| support on vc4 as all the planes had the same functionality.
 | |
| 
 | |
| Can be later squashed into (and fixes):
 | |
| drm/vc4: Add firmware-kms mode
 | |
| 
 | |
| Signed-off-by: Dom Cobley <popcornmix@gmail.com>
 | |
| 
 | |
| drm/vc4: FKMS: Change of Broadcast RGB mode needs a mode change
 | |
| 
 | |
| The Broadcast RGB (aka HDMI limited/full range) property is only
 | |
| notified to the firmware on mode change, so this needs to be
 | |
| signalled when set.
 | |
| 
 | |
| https://github.com/raspberrypi/firmware/issues/1580
 | |
| 
 | |
| Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
 | |
| 
 | |
| vc4/drv: Only notify firmware of display done with kms
 | |
| 
 | |
| fkms driver still wants firmware display to be active
 | |
| 
 | |
| Signed-off-by: Dom Cobley <popcornmix@gmail.com>
 | |
| 
 | |
| ydrm/vc4: fkms: Fix margin calculations for the right/bottom edges
 | |
| 
 | |
| The calculations clipped the right/bottom edge of the clipped
 | |
| range based on the left/top margins.
 | |
| 
 | |
| https://github.com/raspberrypi/linux/issues/4447
 | |
| 
 | |
| Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
 | |
| 
 | |
| drm/vc4: fkms: Use new devm_rpi_firmware_get api
 | |
| 
 | |
| drm/kms: Add allow_fb_modifiers
 | |
| 
 | |
| Signed-off-by: Dom Cobley <popcornmix@gmail.com>
 | |
| ---
 | |
|  drivers/gpu/drm/vc4/Makefile           |    1 +
 | |
|  drivers/gpu/drm/vc4/vc4_debugfs.c      |    3 +-
 | |
|  drivers/gpu/drm/vc4/vc4_drv.c          |   29 +-
 | |
|  drivers/gpu/drm/vc4/vc4_drv.h          |    7 +
 | |
|  drivers/gpu/drm/vc4/vc4_firmware_kms.c | 1997 ++++++++++++++++++++++++
 | |
|  drivers/gpu/drm/vc4/vc4_kms.c          |   35 +-
 | |
|  drivers/gpu/drm/vc4/vc_image_types.h   |  175 +++
 | |
|  7 files changed, 2233 insertions(+), 14 deletions(-)
 | |
|  create mode 100644 drivers/gpu/drm/vc4/vc4_firmware_kms.c
 | |
|  create mode 100644 drivers/gpu/drm/vc4/vc_image_types.h
 | |
| 
 | |
| --- a/drivers/gpu/drm/vc4/Makefile
 | |
| +++ b/drivers/gpu/drm/vc4/Makefile
 | |
| @@ -9,6 +9,7 @@ vc4-y := \
 | |
|  	vc4_dpi.o \
 | |
|  	vc4_dsi.o \
 | |
|  	vc4_fence.o \
 | |
| +	vc4_firmware_kms.o \
 | |
|  	vc4_kms.o \
 | |
|  	vc4_gem.o \
 | |
|  	vc4_hdmi.o \
 | |
| --- a/drivers/gpu/drm/vc4/vc4_debugfs.c
 | |
| +++ b/drivers/gpu/drm/vc4/vc4_debugfs.c
 | |
| @@ -24,7 +24,8 @@ vc4_debugfs_init(struct drm_minor *minor
 | |
|  	struct vc4_dev *vc4 = to_vc4_dev(minor->dev);
 | |
|  	struct drm_device *drm = &vc4->base;
 | |
|  
 | |
| -	drm_WARN_ON(drm, vc4_hvs_debugfs_init(minor));
 | |
| +	if (vc4->hvs)
 | |
| +		drm_WARN_ON(drm, vc4_hvs_debugfs_init(minor));
 | |
|  
 | |
|  	if (vc4->v3d) {
 | |
|  		drm_WARN_ON(drm, vc4_bo_debugfs_init(minor));
 | |
| --- a/drivers/gpu/drm/vc4/vc4_drv.c
 | |
| +++ b/drivers/gpu/drm/vc4/vc4_drv.c
 | |
| @@ -284,6 +284,18 @@ static const struct of_device_id vc4_dma
 | |
|  	{}
 | |
|  };
 | |
|  
 | |
| +/*
 | |
| + * we need this helper function for determining presence of fkms
 | |
| + * before it's been bound
 | |
| + */
 | |
| +static bool firmware_kms(void)
 | |
| +{
 | |
| +	return of_device_is_available(of_find_compatible_node(NULL, NULL,
 | |
| +	       "raspberrypi,rpi-firmware-kms")) ||
 | |
| +	       of_device_is_available(of_find_compatible_node(NULL, NULL,
 | |
| +	       "raspberrypi,rpi-firmware-kms-2711"));
 | |
| +}
 | |
| +
 | |
|  static int vc4_drm_bind(struct device *dev)
 | |
|  {
 | |
|  	struct platform_device *pdev = to_platform_device(dev);
 | |
| @@ -357,7 +369,7 @@ static int vc4_drm_bind(struct device *d
 | |
|  	if (ret)
 | |
|  		return ret;
 | |
|  
 | |
| -	if (firmware) {
 | |
| +	if (firmware && !firmware_kms()) {
 | |
|  		ret = rpi_firmware_property(firmware,
 | |
|  					    RPI_FIRMWARE_NOTIFY_DISPLAY_DONE,
 | |
|  					    NULL, 0);
 | |
| @@ -375,16 +387,20 @@ static int vc4_drm_bind(struct device *d
 | |
|  	if (ret)
 | |
|  		return ret;
 | |
|  
 | |
| -	ret = vc4_plane_create_additional_planes(drm);
 | |
| -	if (ret)
 | |
| -		goto unbind_all;
 | |
| +	if (!vc4->firmware_kms) {
 | |
| +		ret = vc4_plane_create_additional_planes(drm);
 | |
| +		if (ret)
 | |
| +			return ret;
 | |
| +	}
 | |
|  
 | |
|  	ret = vc4_kms_load(drm);
 | |
|  	if (ret < 0)
 | |
|  		goto unbind_all;
 | |
|  
 | |
| -	drm_for_each_crtc(crtc, drm)
 | |
| -		vc4_crtc_disable_at_boot(crtc);
 | |
| +	if (!vc4->firmware_kms) {
 | |
| +		drm_for_each_crtc(crtc, drm)
 | |
| +			vc4_crtc_disable_at_boot(crtc);
 | |
| +	}
 | |
|  
 | |
|  	ret = drm_dev_register(drm, 0);
 | |
|  	if (ret < 0)
 | |
| @@ -428,6 +444,7 @@ static struct platform_driver *const com
 | |
|  	&vc4_dsi_driver,
 | |
|  	&vc4_txp_driver,
 | |
|  	&vc4_crtc_driver,
 | |
| +	&vc4_firmware_kms_driver,
 | |
|  	&vc4_v3d_driver,
 | |
|  };
 | |
|  
 | |
| --- a/drivers/gpu/drm/vc4/vc4_drv.h
 | |
| +++ b/drivers/gpu/drm/vc4/vc4_drv.h
 | |
| @@ -82,8 +82,12 @@ struct vc4_dev {
 | |
|  
 | |
|  	unsigned int irq;
 | |
|  
 | |
| +	bool firmware_kms;
 | |
| +	struct rpi_firmware *firmware;
 | |
| +
 | |
|  	struct vc4_hvs *hvs;
 | |
|  	struct vc4_v3d *v3d;
 | |
| +	struct vc4_fkms *fkms;
 | |
|  
 | |
|  	struct vc4_hang_state *hang_state;
 | |
|  
 | |
| @@ -905,6 +909,9 @@ extern struct platform_driver vc4_dsi_dr
 | |
|  /* vc4_fence.c */
 | |
|  extern const struct dma_fence_ops vc4_fence_ops;
 | |
|  
 | |
| +/* vc4_firmware_kms.c */
 | |
| +extern struct platform_driver vc4_firmware_kms_driver;
 | |
| +
 | |
|  /* vc4_gem.c */
 | |
|  int vc4_gem_init(struct drm_device *dev);
 | |
|  int vc4_submit_cl_ioctl(struct drm_device *dev, void *data,
 | |
| --- /dev/null
 | |
| +++ b/drivers/gpu/drm/vc4/vc4_firmware_kms.c
 | |
| @@ -0,0 +1,1997 @@
 | |
| +// SPDX-License-Identifier: GPL-2.0-only
 | |
| +/*
 | |
| + * Copyright (C) 2016 Broadcom
 | |
| + *
 | |
| + * 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.
 | |
| + */
 | |
| +
 | |
| +/**
 | |
| + * DOC: VC4 firmware KMS module.
 | |
| + *
 | |
| + * As a hack to get us from the current closed source driver world
 | |
| + * toward a totally open stack, implement KMS on top of the Raspberry
 | |
| + * Pi's firmware display stack.
 | |
| + */
 | |
| +
 | |
| +#include <drm/drm_atomic_helper.h>
 | |
| +#include <drm/drm_crtc_helper.h>
 | |
| +#include <drm/drm_blend.h>
 | |
| +#include <drm/drm_drv.h>
 | |
| +#include <drm/drm_edid.h>
 | |
| +#include <drm/drm_fb_dma_helper.h>
 | |
| +#include <drm/drm_fourcc.h>
 | |
| +#include <drm/drm_framebuffer.h>
 | |
| +#include <drm/drm_gem_atomic_helper.h>
 | |
| +#include <drm/drm_plane_helper.h>
 | |
| +#include <drm/drm_probe_helper.h>
 | |
| +#include <drm/drm_vblank.h>
 | |
| +
 | |
| +#include <linux/component.h>
 | |
| +#include <linux/clk.h>
 | |
| +#include <linux/debugfs.h>
 | |
| +#include <linux/module.h>
 | |
| +
 | |
| +#include <soc/bcm2835/raspberrypi-firmware.h>
 | |
| +
 | |
| +#include "vc4_drv.h"
 | |
| +#include "vc4_regs.h"
 | |
| +#include "vc_image_types.h"
 | |
| +
 | |
| +int fkms_max_refresh_rate = 85;
 | |
| +module_param(fkms_max_refresh_rate, int, 0644);
 | |
| +MODULE_PARM_DESC(fkms_max_refresh_rate, "Max supported refresh rate");
 | |
| +
 | |
| +struct get_display_cfg {
 | |
| +	u32  max_pixel_clock[2];  //Max pixel clock for each display
 | |
| +};
 | |
| +
 | |
| +struct vc4_fkms {
 | |
| +	struct get_display_cfg cfg;
 | |
| +	bool bcm2711;
 | |
| +};
 | |
| +
 | |
| +#define PLANES_PER_CRTC		8
 | |
| +
 | |
| +struct set_plane {
 | |
| +	u8 display;
 | |
| +	u8 plane_id;
 | |
| +	u8 vc_image_type;
 | |
| +	s8 layer;
 | |
| +
 | |
| +	u16 width;
 | |
| +	u16 height;
 | |
| +
 | |
| +	u16 pitch;
 | |
| +	u16 vpitch;
 | |
| +
 | |
| +	u32 src_x;	/* 16p16 */
 | |
| +	u32 src_y;	/* 16p16 */
 | |
| +
 | |
| +	u32 src_w;	/* 16p16 */
 | |
| +	u32 src_h;	/* 16p16 */
 | |
| +
 | |
| +	s16 dst_x;
 | |
| +	s16 dst_y;
 | |
| +
 | |
| +	u16 dst_w;
 | |
| +	u16 dst_h;
 | |
| +
 | |
| +	u8 alpha;
 | |
| +	u8 num_planes;
 | |
| +	u8 is_vu;
 | |
| +	u8 color_encoding;
 | |
| +
 | |
| +	u32 planes[4];  /* DMA address of each plane */
 | |
| +
 | |
| +	u32 transform;
 | |
| +};
 | |
| +
 | |
| +/* Values for the transform field */
 | |
| +#define TRANSFORM_NO_ROTATE	0
 | |
| +#define TRANSFORM_ROTATE_180	BIT(1)
 | |
| +#define TRANSFORM_FLIP_HRIZ	BIT(16)
 | |
| +#define TRANSFORM_FLIP_VERT	BIT(17)
 | |
| +
 | |
| +struct mailbox_set_plane {
 | |
| +	struct rpi_firmware_property_tag_header tag;
 | |
| +	struct set_plane plane;
 | |
| +};
 | |
| +
 | |
| +struct mailbox_blank_display {
 | |
| +	struct rpi_firmware_property_tag_header tag1;
 | |
| +	u32 display;
 | |
| +	struct rpi_firmware_property_tag_header tag2;
 | |
| +	u32 blank;
 | |
| +};
 | |
| +
 | |
| +struct mailbox_display_pwr {
 | |
| +	struct rpi_firmware_property_tag_header tag1;
 | |
| +	u32 display;
 | |
| +	u32 state;
 | |
| +};
 | |
| +
 | |
| +struct mailbox_get_edid {
 | |
| +	struct rpi_firmware_property_tag_header tag1;
 | |
| +	u32 block;
 | |
| +	u32 display_number;
 | |
| +	u8 edid[128];
 | |
| +};
 | |
| +
 | |
| +struct set_timings {
 | |
| +	u8 display;
 | |
| +	u8 padding;
 | |
| +	u16 video_id_code;
 | |
| +
 | |
| +	u32 clock;		/* in kHz */
 | |
| +
 | |
| +	u16 hdisplay;
 | |
| +	u16 hsync_start;
 | |
| +
 | |
| +	u16 hsync_end;
 | |
| +	u16 htotal;
 | |
| +
 | |
| +	u16 hskew;
 | |
| +	u16 vdisplay;
 | |
| +
 | |
| +	u16 vsync_start;
 | |
| +	u16 vsync_end;
 | |
| +
 | |
| +	u16 vtotal;
 | |
| +	u16 vscan;
 | |
| +
 | |
| +	u16 vrefresh;
 | |
| +	u16 padding2;
 | |
| +
 | |
| +	u32 flags;
 | |
| +#define  TIMINGS_FLAGS_H_SYNC_POS	BIT(0)
 | |
| +#define  TIMINGS_FLAGS_H_SYNC_NEG	0
 | |
| +#define  TIMINGS_FLAGS_V_SYNC_POS	BIT(1)
 | |
| +#define  TIMINGS_FLAGS_V_SYNC_NEG	0
 | |
| +#define  TIMINGS_FLAGS_INTERLACE	BIT(2)
 | |
| +
 | |
| +#define TIMINGS_FLAGS_ASPECT_MASK	GENMASK(7, 4)
 | |
| +#define TIMINGS_FLAGS_ASPECT_NONE	(0 << 4)
 | |
| +#define TIMINGS_FLAGS_ASPECT_4_3	(1 << 4)
 | |
| +#define TIMINGS_FLAGS_ASPECT_16_9	(2 << 4)
 | |
| +#define TIMINGS_FLAGS_ASPECT_64_27	(3 << 4)
 | |
| +#define TIMINGS_FLAGS_ASPECT_256_135	(4 << 4)
 | |
| +
 | |
| +/* Limited range RGB flag. Not set corresponds to full range. */
 | |
| +#define TIMINGS_FLAGS_RGB_LIMITED	BIT(8)
 | |
| +/* DVI monitor, therefore disable infoframes. Not set corresponds to HDMI. */
 | |
| +#define TIMINGS_FLAGS_DVI		BIT(9)
 | |
| +/* Double clock */
 | |
| +#define TIMINGS_FLAGS_DBL_CLK		BIT(10)
 | |
| +};
 | |
| +
 | |
| +struct mailbox_set_mode {
 | |
| +	struct rpi_firmware_property_tag_header tag1;
 | |
| +	struct set_timings timings;
 | |
| +};
 | |
| +
 | |
| +static const struct vc_image_format {
 | |
| +	u32 drm;	/* DRM_FORMAT_* */
 | |
| +	u32 vc_image;	/* VC_IMAGE_* */
 | |
| +	u32 is_vu;
 | |
| +} vc_image_formats[] = {
 | |
| +	{
 | |
| +		.drm = DRM_FORMAT_XRGB8888,
 | |
| +		.vc_image = VC_IMAGE_XRGB8888,
 | |
| +	},
 | |
| +	{
 | |
| +		.drm = DRM_FORMAT_ARGB8888,
 | |
| +		.vc_image = VC_IMAGE_ARGB8888,
 | |
| +	},
 | |
| +/*
 | |
| + *	FIXME: Need to resolve which DRM format goes to which vc_image format
 | |
| + *	for the remaining RGBA and RGBX formats.
 | |
| + *	{
 | |
| + *		.drm = DRM_FORMAT_ABGR8888,
 | |
| + *		.vc_image = VC_IMAGE_RGBA8888,
 | |
| + *	},
 | |
| + *	{
 | |
| + *		.drm = DRM_FORMAT_XBGR8888,
 | |
| + *		.vc_image = VC_IMAGE_RGBA8888,
 | |
| + *	},
 | |
| + */
 | |
| +	{
 | |
| +		.drm = DRM_FORMAT_RGB565,
 | |
| +		.vc_image = VC_IMAGE_RGB565,
 | |
| +	},
 | |
| +	{
 | |
| +		.drm = DRM_FORMAT_RGB888,
 | |
| +		.vc_image = VC_IMAGE_BGR888,
 | |
| +	},
 | |
| +	{
 | |
| +		.drm = DRM_FORMAT_BGR888,
 | |
| +		.vc_image = VC_IMAGE_RGB888,
 | |
| +	},
 | |
| +	{
 | |
| +		.drm = DRM_FORMAT_YUV422,
 | |
| +		.vc_image = VC_IMAGE_YUV422PLANAR,
 | |
| +	},
 | |
| +	{
 | |
| +		.drm = DRM_FORMAT_YUV420,
 | |
| +		.vc_image = VC_IMAGE_YUV420,
 | |
| +	},
 | |
| +	{
 | |
| +		.drm = DRM_FORMAT_YVU420,
 | |
| +		.vc_image = VC_IMAGE_YUV420,
 | |
| +		.is_vu = 1,
 | |
| +	},
 | |
| +	{
 | |
| +		.drm = DRM_FORMAT_NV12,
 | |
| +		.vc_image = VC_IMAGE_YUV420SP,
 | |
| +	},
 | |
| +	{
 | |
| +		.drm = DRM_FORMAT_NV21,
 | |
| +		.vc_image = VC_IMAGE_YUV420SP,
 | |
| +		.is_vu = 1,
 | |
| +	},
 | |
| +	{
 | |
| +		.drm = DRM_FORMAT_P030,
 | |
| +		.vc_image = VC_IMAGE_YUV10COL,
 | |
| +	},
 | |
| +};
 | |
| +
 | |
| +static const struct vc_image_format *vc4_get_vc_image_fmt(u32 drm_format)
 | |
| +{
 | |
| +	unsigned int i;
 | |
| +
 | |
| +	for (i = 0; i < ARRAY_SIZE(vc_image_formats); i++) {
 | |
| +		if (vc_image_formats[i].drm == drm_format)
 | |
| +			return &vc_image_formats[i];
 | |
| +	}
 | |
| +
 | |
| +	return NULL;
 | |
| +}
 | |
| +
 | |
| +/* The firmware delivers a vblank interrupt to us through the SMI
 | |
| + * hardware, which has only this one register.
 | |
| + */
 | |
| +#define SMICS 0x0
 | |
| +#define SMIDSW0 0x14
 | |
| +#define SMIDSW1 0x1C
 | |
| +#define SMICS_INTERRUPTS (BIT(9) | BIT(10) | BIT(11))
 | |
| +
 | |
| +/* Flag to denote that the firmware is giving multiple display callbacks */
 | |
| +#define SMI_NEW 0xabcd0000
 | |
| +
 | |
| +#define vc4_crtc vc4_kms_crtc
 | |
| +#define to_vc4_crtc to_vc4_kms_crtc
 | |
| +struct vc4_crtc {
 | |
| +	struct drm_crtc base;
 | |
| +	struct drm_encoder *encoder;
 | |
| +	struct drm_connector *connector;
 | |
| +	void __iomem *regs;
 | |
| +
 | |
| +	struct drm_pending_vblank_event *event;
 | |
| +	bool vblank_enabled;
 | |
| +	u32 display_number;
 | |
| +	u32 display_type;
 | |
| +};
 | |
| +
 | |
| +static inline struct vc4_crtc *to_vc4_crtc(struct drm_crtc *crtc)
 | |
| +{
 | |
| +	return container_of(crtc, struct vc4_crtc, base);
 | |
| +}
 | |
| +
 | |
| +struct vc4_fkms_encoder {
 | |
| +	struct drm_encoder base;
 | |
| +	bool hdmi_monitor;
 | |
| +	bool rgb_range_selectable;
 | |
| +	int display_num;
 | |
| +};
 | |
| +
 | |
| +static inline struct vc4_fkms_encoder *
 | |
| +to_vc4_fkms_encoder(struct drm_encoder *encoder)
 | |
| +{
 | |
| +	return container_of(encoder, struct vc4_fkms_encoder, base);
 | |
| +}
 | |
| +
 | |
| +/* "Broadcast RGB" property.
 | |
| + * Allows overriding of HDMI full or limited range RGB
 | |
| + */
 | |
| +#define VC4_BROADCAST_RGB_AUTO 0
 | |
| +#define VC4_BROADCAST_RGB_FULL 1
 | |
| +#define VC4_BROADCAST_RGB_LIMITED 2
 | |
| +
 | |
| +/* VC4 FKMS connector KMS struct */
 | |
| +struct vc4_fkms_connector {
 | |
| +	struct drm_connector base;
 | |
| +
 | |
| +	/* Since the connector is attached to just the one encoder,
 | |
| +	 * this is the reference to it so we can do the best_encoder()
 | |
| +	 * hook.
 | |
| +	 */
 | |
| +	struct drm_encoder *encoder;
 | |
| +	struct vc4_dev *vc4_dev;
 | |
| +	u32 display_number;
 | |
| +	u32 display_type;
 | |
| +
 | |
| +	struct drm_property *broadcast_rgb_property;
 | |
| +};
 | |
| +
 | |
| +static inline struct vc4_fkms_connector *
 | |
| +to_vc4_fkms_connector(struct drm_connector *connector)
 | |
| +{
 | |
| +	return container_of(connector, struct vc4_fkms_connector, base);
 | |
| +}
 | |
| +
 | |
| +/* VC4 FKMS connector state */
 | |
| +struct vc4_fkms_connector_state {
 | |
| +	struct drm_connector_state base;
 | |
| +
 | |
| +	int broadcast_rgb;
 | |
| +};
 | |
| +
 | |
| +#define to_vc4_fkms_connector_state(x) \
 | |
| +			container_of(x, struct vc4_fkms_connector_state, base)
 | |
| +
 | |
| +static u32 vc4_get_display_type(u32 display_number)
 | |
| +{
 | |
| +	const u32 display_types[] = {
 | |
| +		/* The firmware display (DispmanX) IDs map to specific types in
 | |
| +		 * a fixed manner.
 | |
| +		 */
 | |
| +		DRM_MODE_ENCODER_DSI,	/* MAIN_LCD - DSI or DPI */
 | |
| +		DRM_MODE_ENCODER_DSI,	/* AUX_LCD */
 | |
| +		DRM_MODE_ENCODER_TMDS,	/* HDMI0 */
 | |
| +		DRM_MODE_ENCODER_TVDAC,	/* VEC */
 | |
| +		DRM_MODE_ENCODER_NONE,	/* FORCE_LCD */
 | |
| +		DRM_MODE_ENCODER_NONE,	/* FORCE_TV */
 | |
| +		DRM_MODE_ENCODER_NONE,	/* FORCE_OTHER */
 | |
| +		DRM_MODE_ENCODER_TMDS,	/* HDMI1 */
 | |
| +		DRM_MODE_ENCODER_NONE,	/* FORCE_TV2 */
 | |
| +	};
 | |
| +	return display_number > ARRAY_SIZE(display_types) - 1 ?
 | |
| +			DRM_MODE_ENCODER_NONE : display_types[display_number];
 | |
| +}
 | |
| +
 | |
| +/* Firmware's structure for making an FB mbox call. */
 | |
| +struct fbinfo_s {
 | |
| +	u32 xres, yres, xres_virtual, yres_virtual;
 | |
| +	u32 pitch, bpp;
 | |
| +	u32 xoffset, yoffset;
 | |
| +	u32 base;
 | |
| +	u32 screen_size;
 | |
| +	u16 cmap[256];
 | |
| +};
 | |
| +
 | |
| +struct vc4_fkms_plane {
 | |
| +	struct drm_plane base;
 | |
| +	struct fbinfo_s *fbinfo;
 | |
| +	dma_addr_t fbinfo_bus_addr;
 | |
| +	u32 pitch;
 | |
| +	struct mailbox_set_plane mb;
 | |
| +};
 | |
| +
 | |
| +static inline struct vc4_fkms_plane *to_vc4_fkms_plane(struct drm_plane *plane)
 | |
| +{
 | |
| +	return (struct vc4_fkms_plane *)plane;
 | |
| +}
 | |
| +
 | |
| +static int vc4_plane_set_blank(struct drm_plane *plane, bool blank)
 | |
| +{
 | |
| +	struct vc4_dev *vc4 = to_vc4_dev(plane->dev);
 | |
| +	struct vc4_fkms_plane *vc4_plane = to_vc4_fkms_plane(plane);
 | |
| +	struct mailbox_set_plane blank_mb = {
 | |
| +		.tag = { RPI_FIRMWARE_SET_PLANE, sizeof(struct set_plane), 0 },
 | |
| +		.plane = {
 | |
| +			.display = vc4_plane->mb.plane.display,
 | |
| +			.plane_id = vc4_plane->mb.plane.plane_id,
 | |
| +		}
 | |
| +	};
 | |
| +	static const char * const plane_types[] = {
 | |
| +							"overlay",
 | |
| +							"primary",
 | |
| +							"cursor"
 | |
| +						  };
 | |
| +	int ret;
 | |
| +
 | |
| +	DRM_DEBUG_ATOMIC("[PLANE:%d:%s] %s plane %s",
 | |
| +			 plane->base.id, plane->name, plane_types[plane->type],
 | |
| +			 blank ? "blank" : "unblank");
 | |
| +
 | |
| +	if (blank)
 | |
| +		ret = rpi_firmware_property_list(vc4->firmware, &blank_mb,
 | |
| +						 sizeof(blank_mb));
 | |
| +	else
 | |
| +		ret = rpi_firmware_property_list(vc4->firmware, &vc4_plane->mb,
 | |
| +						 sizeof(vc4_plane->mb));
 | |
| +
 | |
| +	WARN_ONCE(ret, "%s: firmware call failed. Please update your firmware",
 | |
| +		  __func__);
 | |
| +	return ret;
 | |
| +}
 | |
| +
 | |
| +static void vc4_fkms_crtc_get_margins(struct drm_crtc_state *state,
 | |
| +				      unsigned int *left, unsigned int *right,
 | |
| +				      unsigned int *top, unsigned int *bottom)
 | |
| +{
 | |
| +	struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(state);
 | |
| +	struct drm_connector_state *conn_state;
 | |
| +	struct drm_connector *conn;
 | |
| +	int i;
 | |
| +
 | |
| +	*left = vc4_state->margins.left;
 | |
| +	*right = vc4_state->margins.right;
 | |
| +	*top = vc4_state->margins.top;
 | |
| +	*bottom = vc4_state->margins.bottom;
 | |
| +
 | |
| +	/* We have to interate over all new connector states because
 | |
| +	 * vc4_fkms_crtc_get_margins() might be called before
 | |
| +	 * vc4_fkms_crtc_atomic_check() which means margins info in
 | |
| +	 * vc4_crtc_state might be outdated.
 | |
| +	 */
 | |
| +	for_each_new_connector_in_state(state->state, conn, conn_state, i) {
 | |
| +		if (conn_state->crtc != state->crtc)
 | |
| +			continue;
 | |
| +
 | |
| +		*left = conn_state->tv.margins.left;
 | |
| +		*right = conn_state->tv.margins.right;
 | |
| +		*top = conn_state->tv.margins.top;
 | |
| +		*bottom = conn_state->tv.margins.bottom;
 | |
| +		break;
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +static int vc4_fkms_margins_adj(struct drm_plane_state *pstate,
 | |
| +				struct set_plane *plane)
 | |
| +{
 | |
| +	unsigned int left, right, top, bottom;
 | |
| +	int adjhdisplay, adjvdisplay;
 | |
| +	struct drm_crtc_state *crtc_state;
 | |
| +
 | |
| +	crtc_state = drm_atomic_get_new_crtc_state(pstate->state,
 | |
| +						   pstate->crtc);
 | |
| +
 | |
| +	vc4_fkms_crtc_get_margins(crtc_state, &left, &right, &top, &bottom);
 | |
| +
 | |
| +	if (!left && !right && !top && !bottom)
 | |
| +		return 0;
 | |
| +
 | |
| +	if (left + right >= crtc_state->mode.hdisplay ||
 | |
| +	    top + bottom >= crtc_state->mode.vdisplay)
 | |
| +		return -EINVAL;
 | |
| +
 | |
| +	adjhdisplay = crtc_state->mode.hdisplay - (left + right);
 | |
| +	plane->dst_x = DIV_ROUND_CLOSEST(plane->dst_x * adjhdisplay,
 | |
| +					 (int)crtc_state->mode.hdisplay);
 | |
| +	plane->dst_x += left;
 | |
| +	if (plane->dst_x > (int)(crtc_state->mode.hdisplay - right))
 | |
| +		plane->dst_x = crtc_state->mode.hdisplay - right;
 | |
| +
 | |
| +	adjvdisplay = crtc_state->mode.vdisplay - (top + bottom);
 | |
| +	plane->dst_y = DIV_ROUND_CLOSEST(plane->dst_y * adjvdisplay,
 | |
| +					 (int)crtc_state->mode.vdisplay);
 | |
| +	plane->dst_y += top;
 | |
| +	if (plane->dst_y > (int)(crtc_state->mode.vdisplay - bottom))
 | |
| +		plane->dst_y = crtc_state->mode.vdisplay - bottom;
 | |
| +
 | |
| +	plane->dst_w = DIV_ROUND_CLOSEST(plane->dst_w * adjhdisplay,
 | |
| +					 crtc_state->mode.hdisplay);
 | |
| +	plane->dst_h = DIV_ROUND_CLOSEST(plane->dst_h * adjvdisplay,
 | |
| +					 crtc_state->mode.vdisplay);
 | |
| +
 | |
| +	if (!plane->dst_w || !plane->dst_h)
 | |
| +		return -EINVAL;
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +static void vc4_plane_atomic_update(struct drm_plane *plane,
 | |
| +				    struct drm_atomic_state *old_state)
 | |
| +{
 | |
| +	struct drm_plane_state *state = plane->state;
 | |
| +
 | |
| +	/*
 | |
| +	 * Do NOT set now, as we haven't checked if the crtc is active or not.
 | |
| +	 * Set from vc4_plane_set_blank instead.
 | |
| +	 *
 | |
| +	 * If the CRTC is on (or going to be on) and we're enabled,
 | |
| +	 * then unblank.  Otherwise, stay blank until CRTC enable.
 | |
| +	 */
 | |
| +	if (state->crtc->state->active)
 | |
| +		vc4_plane_set_blank(plane, false);
 | |
| +}
 | |
| +
 | |
| +static void vc4_plane_atomic_disable(struct drm_plane *plane,
 | |
| +				     struct drm_atomic_state *old_state)
 | |
| +{
 | |
| +	struct drm_plane_state *state = plane->state;
 | |
| +	struct vc4_fkms_plane *vc4_plane = to_vc4_fkms_plane(plane);
 | |
| +
 | |
| +	DRM_DEBUG_ATOMIC("[PLANE:%d:%s] plane disable %dx%d@%d +%d,%d\n",
 | |
| +			 plane->base.id, plane->name,
 | |
| +			 state->crtc_w,
 | |
| +			 state->crtc_h,
 | |
| +			 vc4_plane->mb.plane.vc_image_type,
 | |
| +			 state->crtc_x,
 | |
| +			 state->crtc_y);
 | |
| +	vc4_plane_set_blank(plane, true);
 | |
| +}
 | |
| +
 | |
| +static bool plane_enabled(struct drm_plane_state *state)
 | |
| +{
 | |
| +	return state->fb && state->crtc;
 | |
| +}
 | |
| +
 | |
| +static int vc4_plane_to_mb(struct drm_plane *plane,
 | |
| +			   struct mailbox_set_plane *mb,
 | |
| +			   struct drm_plane_state *state)
 | |
| +{
 | |
| +	struct drm_framebuffer *fb = state->fb;
 | |
| +	struct drm_gem_dma_object *bo = drm_fb_dma_get_gem_obj(fb, 0);
 | |
| +	const struct drm_format_info *drm_fmt = fb->format;
 | |
| +	const struct vc_image_format *vc_fmt =
 | |
| +					vc4_get_vc_image_fmt(drm_fmt->format);
 | |
| +	int num_planes = fb->format->num_planes;
 | |
| +	unsigned int rotation;
 | |
| +
 | |
| +	mb->plane.vc_image_type = vc_fmt->vc_image;
 | |
| +	mb->plane.width = fb->width;
 | |
| +	mb->plane.height = fb->height;
 | |
| +	mb->plane.pitch = fb->pitches[0];
 | |
| +	mb->plane.src_w = state->src_w;
 | |
| +	mb->plane.src_h = state->src_h;
 | |
| +	mb->plane.src_x = state->src_x;
 | |
| +	mb->plane.src_y = state->src_y;
 | |
| +	mb->plane.dst_w = state->crtc_w;
 | |
| +	mb->plane.dst_h = state->crtc_h;
 | |
| +	mb->plane.dst_x = state->crtc_x;
 | |
| +	mb->plane.dst_y = state->crtc_y;
 | |
| +	mb->plane.alpha = state->alpha >> 8;
 | |
| +	mb->plane.layer = state->normalized_zpos ?
 | |
| +					state->normalized_zpos : -127;
 | |
| +	mb->plane.num_planes = num_planes;
 | |
| +	mb->plane.is_vu = vc_fmt->is_vu;
 | |
| +	mb->plane.planes[0] = bo->dma_addr + fb->offsets[0];
 | |
| +
 | |
| +	rotation = drm_rotation_simplify(state->rotation,
 | |
| +					 DRM_MODE_ROTATE_0 |
 | |
| +					 DRM_MODE_REFLECT_X |
 | |
| +					 DRM_MODE_REFLECT_Y);
 | |
| +
 | |
| +	mb->plane.transform = TRANSFORM_NO_ROTATE;
 | |
| +	if (rotation & DRM_MODE_REFLECT_X)
 | |
| +		mb->plane.transform |= TRANSFORM_FLIP_HRIZ;
 | |
| +	if (rotation & DRM_MODE_REFLECT_Y)
 | |
| +		mb->plane.transform |= TRANSFORM_FLIP_VERT;
 | |
| +
 | |
| +	vc4_fkms_margins_adj(state, &mb->plane);
 | |
| +
 | |
| +	if (num_planes > 1) {
 | |
| +		/* Assume this must be YUV */
 | |
| +		/* Makes assumptions on the stride for the chroma planes as we
 | |
| +		 * can't easily plumb in non-standard pitches.
 | |
| +		 */
 | |
| +		mb->plane.planes[1] = bo->dma_addr + fb->offsets[1];
 | |
| +		if (num_planes > 2)
 | |
| +			mb->plane.planes[2] = bo->dma_addr + fb->offsets[2];
 | |
| +		else
 | |
| +			mb->plane.planes[2] = 0;
 | |
| +
 | |
| +		/* Special case the YUV420 with U and V as line interleaved
 | |
| +		 * planes as we have special handling for that case.
 | |
| +		 */
 | |
| +		if (num_planes == 3 &&
 | |
| +		    (fb->offsets[2] - fb->offsets[1]) == fb->pitches[1])
 | |
| +			mb->plane.vc_image_type = VC_IMAGE_YUV420_S;
 | |
| +
 | |
| +		switch (state->color_encoding) {
 | |
| +		default:
 | |
| +		case DRM_COLOR_YCBCR_BT601:
 | |
| +			if (state->color_range == DRM_COLOR_YCBCR_LIMITED_RANGE)
 | |
| +				mb->plane.color_encoding =
 | |
| +						VC_IMAGE_YUVINFO_CSC_ITUR_BT601;
 | |
| +			else
 | |
| +				mb->plane.color_encoding =
 | |
| +						VC_IMAGE_YUVINFO_CSC_JPEG_JFIF;
 | |
| +			break;
 | |
| +		case DRM_COLOR_YCBCR_BT709:
 | |
| +			/* Currently no support for a full range BT709 */
 | |
| +			mb->plane.color_encoding =
 | |
| +						VC_IMAGE_YUVINFO_CSC_ITUR_BT709;
 | |
| +			break;
 | |
| +		case DRM_COLOR_YCBCR_BT2020:
 | |
| +			/* Currently no support for a full range BT2020 */
 | |
| +			mb->plane.color_encoding =
 | |
| +					VC_IMAGE_YUVINFO_CSC_REC_2020;
 | |
| +			break;
 | |
| +		}
 | |
| +	} else {
 | |
| +		mb->plane.planes[1] = 0;
 | |
| +		mb->plane.planes[2] = 0;
 | |
| +	}
 | |
| +	mb->plane.planes[3] = 0;
 | |
| +
 | |
| +	switch (fourcc_mod_broadcom_mod(fb->modifier)) {
 | |
| +	case DRM_FORMAT_MOD_BROADCOM_VC4_T_TILED:
 | |
| +		switch (mb->plane.vc_image_type) {
 | |
| +		case VC_IMAGE_XRGB8888:
 | |
| +			mb->plane.vc_image_type = VC_IMAGE_TF_RGBX32;
 | |
| +			break;
 | |
| +		case VC_IMAGE_ARGB8888:
 | |
| +			mb->plane.vc_image_type = VC_IMAGE_TF_RGBA32;
 | |
| +			break;
 | |
| +		case VC_IMAGE_RGB565:
 | |
| +			mb->plane.vc_image_type = VC_IMAGE_TF_RGB565;
 | |
| +			break;
 | |
| +		}
 | |
| +		break;
 | |
| +	case DRM_FORMAT_MOD_BROADCOM_SAND128:
 | |
| +		switch (mb->plane.vc_image_type) {
 | |
| +		case VC_IMAGE_YUV420SP:
 | |
| +			mb->plane.vc_image_type = VC_IMAGE_YUV_UV;
 | |
| +			break;
 | |
| +		/* VC_IMAGE_YUV10COL could be included in here, but it is only
 | |
| +		 * valid as a SAND128 format, so the table at the top will have
 | |
| +		 * already set the correct format.
 | |
| +		 */
 | |
| +		}
 | |
| +		/* Note that the column pitch is passed across in lines, not
 | |
| +		 * bytes.
 | |
| +		 */
 | |
| +		mb->plane.pitch = fourcc_mod_broadcom_param(fb->modifier);
 | |
| +		break;
 | |
| +	}
 | |
| +
 | |
| +	DRM_DEBUG_ATOMIC("[PLANE:%d:%s] plane update %dx%d@%d +dst(%d,%d, %d,%d) +src(%d,%d, %d,%d) 0x%08x/%08x/%08x/%d, alpha %u zpos %u\n",
 | |
| +			 plane->base.id, plane->name,
 | |
| +			 mb->plane.width,
 | |
| +			 mb->plane.height,
 | |
| +			 mb->plane.vc_image_type,
 | |
| +			 state->crtc_x,
 | |
| +			 state->crtc_y,
 | |
| +			 state->crtc_w,
 | |
| +			 state->crtc_h,
 | |
| +			 mb->plane.src_x,
 | |
| +			 mb->plane.src_y,
 | |
| +			 mb->plane.src_w,
 | |
| +			 mb->plane.src_h,
 | |
| +			 mb->plane.planes[0],
 | |
| +			 mb->plane.planes[1],
 | |
| +			 mb->plane.planes[2],
 | |
| +			 fb->pitches[0],
 | |
| +			 state->alpha,
 | |
| +			 state->normalized_zpos);
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +static int vc4_plane_atomic_check(struct drm_plane *plane,
 | |
| +				  struct drm_atomic_state *state)
 | |
| +{
 | |
| +	struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state,
 | |
| +										 plane);
 | |
| +	struct vc4_fkms_plane *vc4_plane = to_vc4_fkms_plane(plane);
 | |
| +
 | |
| +	if (!plane_enabled(new_plane_state))
 | |
| +		return 0;
 | |
| +
 | |
| +	return vc4_plane_to_mb(plane, &vc4_plane->mb, new_plane_state);
 | |
| +}
 | |
| +
 | |
| +/* Called during init to allocate the plane's atomic state. */
 | |
| +static void vc4_plane_reset(struct drm_plane *plane)
 | |
| +{
 | |
| +	struct vc4_plane_state *vc4_state;
 | |
| +
 | |
| +	WARN_ON(plane->state);
 | |
| +
 | |
| +	vc4_state = kzalloc(sizeof(*vc4_state), GFP_KERNEL);
 | |
| +	if (!vc4_state)
 | |
| +		return;
 | |
| +
 | |
| +	__drm_atomic_helper_plane_reset(plane, &vc4_state->base);
 | |
| +}
 | |
| +
 | |
| +static void vc4_plane_destroy(struct drm_plane *plane)
 | |
| +{
 | |
| +	drm_plane_cleanup(plane);
 | |
| +}
 | |
| +
 | |
| +static bool vc4_fkms_format_mod_supported(struct drm_plane *plane,
 | |
| +					  uint32_t format,
 | |
| +					  uint64_t modifier)
 | |
| +{
 | |
| +	/* Support T_TILING for RGB formats only. */
 | |
| +	switch (format) {
 | |
| +	case DRM_FORMAT_XRGB8888:
 | |
| +	case DRM_FORMAT_ARGB8888:
 | |
| +	case DRM_FORMAT_RGB565:
 | |
| +		switch (modifier) {
 | |
| +		case DRM_FORMAT_MOD_BROADCOM_VC4_T_TILED:
 | |
| +		case DRM_FORMAT_MOD_LINEAR:
 | |
| +			return true;
 | |
| +		default:
 | |
| +			return false;
 | |
| +		}
 | |
| +	case DRM_FORMAT_NV12:
 | |
| +		switch (fourcc_mod_broadcom_mod(modifier)) {
 | |
| +		case DRM_FORMAT_MOD_LINEAR:
 | |
| +		case DRM_FORMAT_MOD_BROADCOM_SAND128:
 | |
| +			return true;
 | |
| +		default:
 | |
| +			return false;
 | |
| +		}
 | |
| +	case DRM_FORMAT_P030:
 | |
| +		switch (fourcc_mod_broadcom_mod(modifier)) {
 | |
| +		case DRM_FORMAT_MOD_BROADCOM_SAND128:
 | |
| +			return true;
 | |
| +		default:
 | |
| +			return false;
 | |
| +		}
 | |
| +	case DRM_FORMAT_NV21:
 | |
| +	case DRM_FORMAT_RGB888:
 | |
| +	case DRM_FORMAT_BGR888:
 | |
| +	case DRM_FORMAT_YUV422:
 | |
| +	case DRM_FORMAT_YUV420:
 | |
| +	case DRM_FORMAT_YVU420:
 | |
| +	default:
 | |
| +		return (modifier == DRM_FORMAT_MOD_LINEAR);
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +static struct drm_plane_state *vc4_plane_duplicate_state(struct drm_plane *plane)
 | |
| +{
 | |
| +	struct vc4_plane_state *vc4_state;
 | |
| +
 | |
| +	if (WARN_ON(!plane->state))
 | |
| +		return NULL;
 | |
| +
 | |
| +	vc4_state = kzalloc(sizeof(*vc4_state), GFP_KERNEL);
 | |
| +	if (!vc4_state)
 | |
| +		return NULL;
 | |
| +
 | |
| +	__drm_atomic_helper_plane_duplicate_state(plane, &vc4_state->base);
 | |
| +
 | |
| +	return &vc4_state->base;
 | |
| +}
 | |
| +
 | |
| +static const struct drm_plane_funcs vc4_plane_funcs = {
 | |
| +	.update_plane = drm_atomic_helper_update_plane,
 | |
| +	.disable_plane = drm_atomic_helper_disable_plane,
 | |
| +	.destroy = vc4_plane_destroy,
 | |
| +	.set_property = NULL,
 | |
| +	.reset = vc4_plane_reset,
 | |
| +	.atomic_duplicate_state = vc4_plane_duplicate_state,
 | |
| +	.atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
 | |
| +	.format_mod_supported = vc4_fkms_format_mod_supported,
 | |
| +};
 | |
| +
 | |
| +static const struct drm_plane_helper_funcs vc4_plane_helper_funcs = {
 | |
| +	.prepare_fb = drm_gem_plane_helper_prepare_fb,
 | |
| +	.cleanup_fb = NULL,
 | |
| +	.atomic_check = vc4_plane_atomic_check,
 | |
| +	.atomic_update = vc4_plane_atomic_update,
 | |
| +	.atomic_disable = vc4_plane_atomic_disable,
 | |
| +};
 | |
| +
 | |
| +static struct drm_plane *vc4_fkms_plane_init(struct drm_device *dev,
 | |
| +					     enum drm_plane_type type,
 | |
| +					     u8 display_num,
 | |
| +					     u8 plane_id)
 | |
| +{
 | |
| +	struct drm_plane *plane = NULL;
 | |
| +	struct vc4_fkms_plane *vc4_plane;
 | |
| +	u32 formats[ARRAY_SIZE(vc_image_formats)];
 | |
| +	unsigned int default_zpos = 0;
 | |
| +	u32 num_formats = 0;
 | |
| +	int ret = 0;
 | |
| +	static const uint64_t modifiers[] = {
 | |
| +		DRM_FORMAT_MOD_LINEAR,
 | |
| +		/* VC4_T_TILED should come after linear, because we
 | |
| +		 * would prefer to scan out linear (less bus traffic).
 | |
| +		 */
 | |
| +		DRM_FORMAT_MOD_BROADCOM_VC4_T_TILED,
 | |
| +		DRM_FORMAT_MOD_BROADCOM_SAND128,
 | |
| +		DRM_FORMAT_MOD_INVALID,
 | |
| +	};
 | |
| +	int i;
 | |
| +
 | |
| +	vc4_plane = devm_kzalloc(dev->dev, sizeof(*vc4_plane),
 | |
| +				 GFP_KERNEL);
 | |
| +	if (!vc4_plane) {
 | |
| +		ret = -ENOMEM;
 | |
| +		goto fail;
 | |
| +	}
 | |
| +
 | |
| +	for (i = 0; i < ARRAY_SIZE(vc_image_formats); i++)
 | |
| +		formats[num_formats++] = vc_image_formats[i].drm;
 | |
| +
 | |
| +	plane = &vc4_plane->base;
 | |
| +	ret = drm_universal_plane_init(dev, plane, 0,
 | |
| +				       &vc4_plane_funcs,
 | |
| +				       formats, num_formats, modifiers,
 | |
| +				       type, NULL);
 | |
| +
 | |
| +	/* FIXME: Do we need to be checking return values from all these calls?
 | |
| +	 */
 | |
| +	drm_plane_helper_add(plane, &vc4_plane_helper_funcs);
 | |
| +
 | |
| +	drm_plane_create_alpha_property(plane);
 | |
| +	drm_plane_create_rotation_property(plane, DRM_MODE_ROTATE_0,
 | |
| +					   DRM_MODE_ROTATE_0 |
 | |
| +					   DRM_MODE_ROTATE_180 |
 | |
| +					   DRM_MODE_REFLECT_X |
 | |
| +					   DRM_MODE_REFLECT_Y);
 | |
| +	drm_plane_create_color_properties(plane,
 | |
| +					  BIT(DRM_COLOR_YCBCR_BT601) |
 | |
| +					  BIT(DRM_COLOR_YCBCR_BT709) |
 | |
| +					  BIT(DRM_COLOR_YCBCR_BT2020),
 | |
| +					  BIT(DRM_COLOR_YCBCR_LIMITED_RANGE) |
 | |
| +					  BIT(DRM_COLOR_YCBCR_FULL_RANGE),
 | |
| +					  DRM_COLOR_YCBCR_BT709,
 | |
| +					  DRM_COLOR_YCBCR_LIMITED_RANGE);
 | |
| +
 | |
| +	/*
 | |
| +	 * Default frame buffer setup is with FB on -127, and raspistill etc
 | |
| +	 * tend to drop overlays on layer 2. Cursor plane was on layer +127.
 | |
| +	 *
 | |
| +	 * For F-KMS the mailbox call allows for a s8.
 | |
| +	 * Remap zpos 0 to -127 for the background layer, but leave all the
 | |
| +	 * other layers as requested by KMS.
 | |
| +	 */
 | |
| +	switch (type) {
 | |
| +	default:
 | |
| +	case DRM_PLANE_TYPE_PRIMARY:
 | |
| +		default_zpos = 0;
 | |
| +		break;
 | |
| +	case DRM_PLANE_TYPE_OVERLAY:
 | |
| +		default_zpos = 1;
 | |
| +		break;
 | |
| +	case DRM_PLANE_TYPE_CURSOR:
 | |
| +		default_zpos = 2;
 | |
| +		break;
 | |
| +	}
 | |
| +	drm_plane_create_zpos_property(plane, default_zpos, 0, 127);
 | |
| +
 | |
| +	/* Prepare the static elements of the mailbox structure */
 | |
| +	vc4_plane->mb.tag.tag = RPI_FIRMWARE_SET_PLANE;
 | |
| +	vc4_plane->mb.tag.buf_size = sizeof(struct set_plane);
 | |
| +	vc4_plane->mb.tag.req_resp_size = 0;
 | |
| +	vc4_plane->mb.plane.display = display_num;
 | |
| +	vc4_plane->mb.plane.plane_id = plane_id;
 | |
| +	vc4_plane->mb.plane.layer = default_zpos ? default_zpos : -127;
 | |
| +
 | |
| +	return plane;
 | |
| +fail:
 | |
| +	if (plane)
 | |
| +		vc4_plane_destroy(plane);
 | |
| +
 | |
| +	return ERR_PTR(ret);
 | |
| +}
 | |
| +
 | |
| +static void vc4_crtc_mode_set_nofb(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 drm_display_mode *mode = &crtc->state->adjusted_mode;
 | |
| +	struct vc4_fkms_encoder *vc4_encoder =
 | |
| +					to_vc4_fkms_encoder(vc4_crtc->encoder);
 | |
| +	struct mailbox_set_mode mb = {
 | |
| +		.tag1 = { RPI_FIRMWARE_SET_TIMING,
 | |
| +			  sizeof(struct set_timings), 0},
 | |
| +	};
 | |
| +	union hdmi_infoframe frame;
 | |
| +	int ret;
 | |
| +
 | |
| +	ret = drm_hdmi_avi_infoframe_from_display_mode(&frame.avi, vc4_crtc->connector, mode);
 | |
| +	if (ret < 0) {
 | |
| +		DRM_ERROR("couldn't fill AVI infoframe\n");
 | |
| +		return;
 | |
| +	}
 | |
| +
 | |
| +	DRM_DEBUG_KMS("Setting mode for display num %u mode name %s, clk %d, h(disp %d, start %d, end %d, total %d, skew %d) v(disp %d, start %d, end %d, total %d, scan %d), vrefresh %d, par %u, flags 0x%04x\n",
 | |
| +		      vc4_crtc->display_number, mode->name, mode->clock,
 | |
| +		      mode->hdisplay, mode->hsync_start, mode->hsync_end,
 | |
| +		      mode->htotal, mode->hskew, mode->vdisplay,
 | |
| +		      mode->vsync_start, mode->vsync_end, mode->vtotal,
 | |
| +		      mode->vscan, drm_mode_vrefresh(mode),
 | |
| +		      mode->picture_aspect_ratio, mode->flags);
 | |
| +	mb.timings.display = vc4_crtc->display_number;
 | |
| +
 | |
| +	mb.timings.clock = mode->clock;
 | |
| +	mb.timings.hdisplay = mode->hdisplay;
 | |
| +	mb.timings.hsync_start = mode->hsync_start;
 | |
| +	mb.timings.hsync_end = mode->hsync_end;
 | |
| +	mb.timings.htotal = mode->htotal;
 | |
| +	mb.timings.hskew = mode->hskew;
 | |
| +	mb.timings.vdisplay = mode->vdisplay;
 | |
| +	mb.timings.vsync_start = mode->vsync_start;
 | |
| +	mb.timings.vsync_end = mode->vsync_end;
 | |
| +	mb.timings.vtotal = mode->vtotal;
 | |
| +	mb.timings.vscan = mode->vscan;
 | |
| +	mb.timings.vrefresh = drm_mode_vrefresh(mode);
 | |
| +	mb.timings.flags = 0;
 | |
| +	if (mode->flags & DRM_MODE_FLAG_PHSYNC)
 | |
| +		mb.timings.flags |= TIMINGS_FLAGS_H_SYNC_POS;
 | |
| +	if (mode->flags & DRM_MODE_FLAG_PVSYNC)
 | |
| +		mb.timings.flags |= TIMINGS_FLAGS_V_SYNC_POS;
 | |
| +
 | |
| +	switch (frame.avi.picture_aspect) {
 | |
| +	default:
 | |
| +	case HDMI_PICTURE_ASPECT_NONE:
 | |
| +		mb.timings.flags |= TIMINGS_FLAGS_ASPECT_NONE;
 | |
| +		break;
 | |
| +	case HDMI_PICTURE_ASPECT_4_3:
 | |
| +		mb.timings.flags |= TIMINGS_FLAGS_ASPECT_4_3;
 | |
| +		break;
 | |
| +	case HDMI_PICTURE_ASPECT_16_9:
 | |
| +		mb.timings.flags |= TIMINGS_FLAGS_ASPECT_16_9;
 | |
| +		break;
 | |
| +	case HDMI_PICTURE_ASPECT_64_27:
 | |
| +		mb.timings.flags |= TIMINGS_FLAGS_ASPECT_64_27;
 | |
| +		break;
 | |
| +	case HDMI_PICTURE_ASPECT_256_135:
 | |
| +		mb.timings.flags |= TIMINGS_FLAGS_ASPECT_256_135;
 | |
| +		break;
 | |
| +	}
 | |
| +
 | |
| +	if (mode->flags & DRM_MODE_FLAG_INTERLACE)
 | |
| +		mb.timings.flags |= TIMINGS_FLAGS_INTERLACE;
 | |
| +	if (mode->flags & DRM_MODE_FLAG_DBLCLK)
 | |
| +		mb.timings.flags |= TIMINGS_FLAGS_DBL_CLK;
 | |
| +
 | |
| +	mb.timings.video_id_code = frame.avi.video_code;
 | |
| +
 | |
| +	if (!vc4_encoder->hdmi_monitor) {
 | |
| +		mb.timings.flags |= TIMINGS_FLAGS_DVI;
 | |
| +	} else {
 | |
| +		struct vc4_fkms_connector_state *conn_state =
 | |
| +			to_vc4_fkms_connector_state(vc4_crtc->connector->state);
 | |
| +
 | |
| +		if (conn_state->broadcast_rgb == VC4_BROADCAST_RGB_AUTO) {
 | |
| +			/* See CEA-861-E - 5.1 Default Encoding Parameters */
 | |
| +			if (drm_default_rgb_quant_range(mode) ==
 | |
| +					HDMI_QUANTIZATION_RANGE_LIMITED)
 | |
| +				mb.timings.flags |= TIMINGS_FLAGS_RGB_LIMITED;
 | |
| +		} else {
 | |
| +			if (conn_state->broadcast_rgb ==
 | |
| +						VC4_BROADCAST_RGB_LIMITED)
 | |
| +				mb.timings.flags |= TIMINGS_FLAGS_RGB_LIMITED;
 | |
| +
 | |
| +			/* If not using the default range, then do not provide
 | |
| +			 * a VIC as the HDMI spec requires that we do not
 | |
| +			 * signal the opposite of the defined range in the AVI
 | |
| +			 * infoframe.
 | |
| +			 */
 | |
| +			if (!!(mb.timings.flags & TIMINGS_FLAGS_RGB_LIMITED) !=
 | |
| +			    (drm_default_rgb_quant_range(mode) ==
 | |
| +					HDMI_QUANTIZATION_RANGE_LIMITED))
 | |
| +				mb.timings.video_id_code = 0;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	/*
 | |
| +	 * FIXME: To implement
 | |
| +	 * switch(mode->flag & DRM_MODE_FLAG_3D_MASK) {
 | |
| +	 * case DRM_MODE_FLAG_3D_NONE:
 | |
| +	 * case DRM_MODE_FLAG_3D_FRAME_PACKING:
 | |
| +	 * case DRM_MODE_FLAG_3D_FIELD_ALTERNATIVE:
 | |
| +	 * case DRM_MODE_FLAG_3D_LINE_ALTERNATIVE:
 | |
| +	 * case DRM_MODE_FLAG_3D_SIDE_BY_SIDE_FULL:
 | |
| +	 * case DRM_MODE_FLAG_3D_L_DEPTH:
 | |
| +	 * case DRM_MODE_FLAG_3D_L_DEPTH_GFX_GFX_DEPTH:
 | |
| +	 * case DRM_MODE_FLAG_3D_TOP_AND_BOTTOM:
 | |
| +	 * case DRM_MODE_FLAG_3D_SIDE_BY_SIDE_HALF:
 | |
| +	 * }
 | |
| +	 */
 | |
| +
 | |
| +	ret = rpi_firmware_property_list(vc4->firmware, &mb, sizeof(mb));
 | |
| +}
 | |
| +
 | |
| +static void vc4_crtc_disable(struct drm_crtc *crtc,
 | |
| +			     struct drm_atomic_state *state)
 | |
| +{
 | |
| +	struct drm_device *dev = crtc->dev;
 | |
| +	struct drm_plane *plane;
 | |
| +
 | |
| +	DRM_DEBUG_KMS("[CRTC:%d] vblanks off.\n",
 | |
| +		      crtc->base.id);
 | |
| +	drm_crtc_vblank_off(crtc);
 | |
| +
 | |
| +	/* Always turn the planes off on CRTC disable. In DRM, planes
 | |
| +	 * are enabled/disabled through the update/disable hooks
 | |
| +	 * above, and the CRTC enable/disable independently controls
 | |
| +	 * whether anything scans out at all, but the firmware doesn't
 | |
| +	 * give us a CRTC-level control for that.
 | |
| +	 */
 | |
| +
 | |
| +	drm_atomic_crtc_for_each_plane(plane, crtc)
 | |
| +		vc4_plane_atomic_disable(plane, state);
 | |
| +
 | |
| +	/*
 | |
| +	 * Make sure we issue a vblank event after disabling the CRTC if
 | |
| +	 * someone was waiting it.
 | |
| +	 */
 | |
| +	if (crtc->state->event) {
 | |
| +		unsigned long flags;
 | |
| +
 | |
| +		spin_lock_irqsave(&dev->event_lock, flags);
 | |
| +		drm_crtc_send_vblank_event(crtc, crtc->state->event);
 | |
| +		crtc->state->event = NULL;
 | |
| +		spin_unlock_irqrestore(&dev->event_lock, flags);
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +static void vc4_crtc_consume_event(struct drm_crtc *crtc)
 | |
| +{
 | |
| +	struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
 | |
| +	struct drm_device *dev = crtc->dev;
 | |
| +	unsigned long flags;
 | |
| +
 | |
| +	if (!crtc->state->event)
 | |
| +		return;
 | |
| +
 | |
| +	crtc->state->event->pipe = drm_crtc_index(crtc);
 | |
| +
 | |
| +	WARN_ON(drm_crtc_vblank_get(crtc) != 0);
 | |
| +
 | |
| +	spin_lock_irqsave(&dev->event_lock, flags);
 | |
| +	vc4_crtc->event = crtc->state->event;
 | |
| +	crtc->state->event = NULL;
 | |
| +	spin_unlock_irqrestore(&dev->event_lock, flags);
 | |
| +}
 | |
| +
 | |
| +static void vc4_crtc_enable(struct drm_crtc *crtc,
 | |
| +			    struct drm_atomic_state *state)
 | |
| +{
 | |
| +	struct drm_plane *plane;
 | |
| +
 | |
| +	DRM_DEBUG_KMS("[CRTC:%d] vblanks on.\n",
 | |
| +		      crtc->base.id);
 | |
| +	drm_crtc_vblank_on(crtc);
 | |
| +	vc4_crtc_consume_event(crtc);
 | |
| +
 | |
| +	/* Unblank the planes (if they're supposed to be displayed). */
 | |
| +	drm_atomic_crtc_for_each_plane(plane, crtc)
 | |
| +		if (plane->state->fb)
 | |
| +			vc4_plane_set_blank(plane, plane->state->visible);
 | |
| +}
 | |
| +
 | |
| +static enum drm_mode_status
 | |
| +vc4_crtc_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode *mode)
 | |
| +{
 | |
| +	struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
 | |
| +	struct drm_device *dev = crtc->dev;
 | |
| +	struct vc4_dev *vc4 = to_vc4_dev(dev);
 | |
| +	struct vc4_fkms *fkms = vc4->fkms;
 | |
| +
 | |
| +	/* Do not allow doublescan modes from user space */
 | |
| +	if (mode->flags & DRM_MODE_FLAG_DBLSCAN) {
 | |
| +		DRM_DEBUG_KMS("[CRTC:%d] Doublescan mode rejected.\n",
 | |
| +			      crtc->base.id);
 | |
| +		return MODE_NO_DBLESCAN;
 | |
| +	}
 | |
| +
 | |
| +	/* Disable refresh rates > defined threshold (default 85Hz) as limited
 | |
| +	 * gain from them
 | |
| +	 */
 | |
| +	if (drm_mode_vrefresh(mode) > fkms_max_refresh_rate)
 | |
| +		return MODE_BAD_VVALUE;
 | |
| +
 | |
| +	/* Limit the pixel clock based on the HDMI clock limits from the
 | |
| +	 * firmware
 | |
| +	 */
 | |
| +	switch (vc4_crtc->display_number) {
 | |
| +	case 2:	/* HDMI0 */
 | |
| +		if (fkms->cfg.max_pixel_clock[0] &&
 | |
| +		    mode->clock > fkms->cfg.max_pixel_clock[0])
 | |
| +			return MODE_CLOCK_HIGH;
 | |
| +		break;
 | |
| +	case 7:	/* HDMI1 */
 | |
| +		if (fkms->cfg.max_pixel_clock[1] &&
 | |
| +		    mode->clock > fkms->cfg.max_pixel_clock[1])
 | |
| +			return MODE_CLOCK_HIGH;
 | |
| +		break;
 | |
| +	}
 | |
| +
 | |
| +	/* Pi4 can't generate odd horizontal timings on HDMI, so reject modes
 | |
| +	 * that would set them.
 | |
| +	 */
 | |
| +	if (fkms->bcm2711 &&
 | |
| +	    (vc4_crtc->display_number == 2 || vc4_crtc->display_number == 7) &&
 | |
| +	    !(mode->flags & DRM_MODE_FLAG_DBLCLK) &&
 | |
| +	    ((mode->hdisplay |				/* active */
 | |
| +	      (mode->hsync_start - mode->hdisplay) |	/* front porch */
 | |
| +	      (mode->hsync_end - mode->hsync_start) |	/* sync pulse */
 | |
| +	      (mode->htotal - mode->hsync_end)) & 1))	/* back porch */ {
 | |
| +		DRM_DEBUG_KMS("[CRTC:%d] Odd timing rejected %u %u %u %u.\n",
 | |
| +			      crtc->base.id, mode->hdisplay, mode->hsync_start,
 | |
| +			      mode->hsync_end, mode->htotal);
 | |
| +		return MODE_H_ILLEGAL;
 | |
| +	}
 | |
| +
 | |
| +	return MODE_OK;
 | |
| +}
 | |
| +
 | |
| +static int vc4_crtc_atomic_check(struct drm_crtc *crtc,
 | |
| +				 struct drm_atomic_state *state)
 | |
| +{
 | |
| +	struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state,
 | |
| +									  crtc);
 | |
| +	struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc_state);
 | |
| +	struct drm_connector *conn;
 | |
| +	struct drm_connector_state *conn_state;
 | |
| +	int i;
 | |
| +
 | |
| +	DRM_DEBUG_KMS("[CRTC:%d] crtc_atomic_check.\n", crtc->base.id);
 | |
| +
 | |
| +	for_each_new_connector_in_state(crtc_state->state, conn, conn_state, i) {
 | |
| +		if (conn_state->crtc != crtc)
 | |
| +			continue;
 | |
| +
 | |
| +		vc4_state->margins.left = conn_state->tv.margins.left;
 | |
| +		vc4_state->margins.right = conn_state->tv.margins.right;
 | |
| +		vc4_state->margins.top = conn_state->tv.margins.top;
 | |
| +		vc4_state->margins.bottom = conn_state->tv.margins.bottom;
 | |
| +		break;
 | |
| +	}
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +static void vc4_crtc_atomic_flush(struct drm_crtc *crtc,
 | |
| +				  struct drm_atomic_state *state)
 | |
| +{
 | |
| +	struct drm_crtc_state *old_state = drm_atomic_get_old_crtc_state(state,
 | |
| +									 crtc);
 | |
| +
 | |
| +	DRM_DEBUG_KMS("[CRTC:%d] crtc_atomic_flush.\n",
 | |
| +		      crtc->base.id);
 | |
| +	if (crtc->state->active && old_state->active && crtc->state->event)
 | |
| +		vc4_crtc_consume_event(crtc);
 | |
| +}
 | |
| +
 | |
| +static void vc4_crtc_handle_page_flip(struct vc4_crtc *vc4_crtc)
 | |
| +{
 | |
| +	struct drm_crtc *crtc = &vc4_crtc->base;
 | |
| +	struct drm_device *dev = crtc->dev;
 | |
| +	unsigned long flags;
 | |
| +
 | |
| +	spin_lock_irqsave(&dev->event_lock, flags);
 | |
| +	if (vc4_crtc->event) {
 | |
| +		drm_crtc_send_vblank_event(crtc, vc4_crtc->event);
 | |
| +		vc4_crtc->event = NULL;
 | |
| +		drm_crtc_vblank_put(crtc);
 | |
| +	}
 | |
| +	spin_unlock_irqrestore(&dev->event_lock, flags);
 | |
| +}
 | |
| +
 | |
| +static irqreturn_t vc4_crtc_irq_handler(int irq, void *data)
 | |
| +{
 | |
| +	struct vc4_crtc **crtc_list = data;
 | |
| +	int i;
 | |
| +	u32 stat = readl(crtc_list[0]->regs + SMICS);
 | |
| +	irqreturn_t ret = IRQ_NONE;
 | |
| +	u32 chan;
 | |
| +
 | |
| +	if (stat & SMICS_INTERRUPTS) {
 | |
| +		writel(0, crtc_list[0]->regs + SMICS);
 | |
| +
 | |
| +		chan = readl(crtc_list[0]->regs + SMIDSW0);
 | |
| +
 | |
| +		if ((chan & 0xFFFF0000) != SMI_NEW) {
 | |
| +			/* Older firmware. Treat the one interrupt as vblank/
 | |
| +			 * complete for all crtcs.
 | |
| +			 */
 | |
| +			for (i = 0; crtc_list[i]; i++) {
 | |
| +				if (crtc_list[i]->vblank_enabled)
 | |
| +					drm_crtc_handle_vblank(&crtc_list[i]->base);
 | |
| +				vc4_crtc_handle_page_flip(crtc_list[i]);
 | |
| +			}
 | |
| +		} else {
 | |
| +			if (chan & 1) {
 | |
| +				writel(SMI_NEW, crtc_list[0]->regs + SMIDSW0);
 | |
| +				if (crtc_list[0]->vblank_enabled)
 | |
| +					drm_crtc_handle_vblank(&crtc_list[0]->base);
 | |
| +				vc4_crtc_handle_page_flip(crtc_list[0]);
 | |
| +			}
 | |
| +
 | |
| +			if (crtc_list[1]) {
 | |
| +				/* Check for the secondary display too */
 | |
| +				chan = readl(crtc_list[0]->regs + SMIDSW1);
 | |
| +
 | |
| +				if (chan & 1) {
 | |
| +					writel(SMI_NEW, crtc_list[0]->regs + SMIDSW1);
 | |
| +
 | |
| +					if (crtc_list[1]->vblank_enabled)
 | |
| +						drm_crtc_handle_vblank(&crtc_list[1]->base);
 | |
| +					vc4_crtc_handle_page_flip(crtc_list[1]);
 | |
| +				}
 | |
| +			}
 | |
| +		}
 | |
| +
 | |
| +		ret = IRQ_HANDLED;
 | |
| +	}
 | |
| +
 | |
| +	return ret;
 | |
| +}
 | |
| +
 | |
| +static int vc4_fkms_page_flip(struct drm_crtc *crtc,
 | |
| +			      struct drm_framebuffer *fb,
 | |
| +			      struct drm_pending_vblank_event *event,
 | |
| +			      uint32_t flags,
 | |
| +			      struct drm_modeset_acquire_ctx *ctx)
 | |
| +{
 | |
| +	if (flags & DRM_MODE_PAGE_FLIP_ASYNC) {
 | |
| +		DRM_ERROR("Async flips aren't allowed\n");
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
| +
 | |
| +	return drm_atomic_helper_page_flip(crtc, fb, event, flags, ctx);
 | |
| +}
 | |
| +
 | |
| +static struct drm_crtc_state *
 | |
| +vc4_fkms_crtc_duplicate_state(struct drm_crtc *crtc)
 | |
| +{
 | |
| +	struct vc4_crtc_state *vc4_state, *old_vc4_state;
 | |
| +
 | |
| +	vc4_state = kzalloc(sizeof(*vc4_state), GFP_KERNEL);
 | |
| +	if (!vc4_state)
 | |
| +		return NULL;
 | |
| +
 | |
| +	old_vc4_state = to_vc4_crtc_state(crtc->state);
 | |
| +	vc4_state->margins = old_vc4_state->margins;
 | |
| +
 | |
| +	__drm_atomic_helper_crtc_duplicate_state(crtc, &vc4_state->base);
 | |
| +	return &vc4_state->base;
 | |
| +}
 | |
| +
 | |
| +static void
 | |
| +vc4_fkms_crtc_reset(struct drm_crtc *crtc)
 | |
| +{
 | |
| +	if (crtc->state)
 | |
| +		__drm_atomic_helper_crtc_destroy_state(crtc->state);
 | |
| +
 | |
| +	crtc->state = kzalloc(sizeof(*crtc->state), GFP_KERNEL);
 | |
| +	if (crtc->state)
 | |
| +		crtc->state->crtc = crtc;
 | |
| +}
 | |
| +
 | |
| +static int vc4_fkms_enable_vblank(struct drm_crtc *crtc)
 | |
| +{
 | |
| +	struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
 | |
| +
 | |
| +	DRM_DEBUG_KMS("[CRTC:%d] enable_vblank.\n",
 | |
| +		      crtc->base.id);
 | |
| +	vc4_crtc->vblank_enabled = true;
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +static void vc4_fkms_disable_vblank(struct drm_crtc *crtc)
 | |
| +{
 | |
| +	struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
 | |
| +
 | |
| +	DRM_DEBUG_KMS("[CRTC:%d] disable_vblank.\n",
 | |
| +		      crtc->base.id);
 | |
| +	vc4_crtc->vblank_enabled = false;
 | |
| +}
 | |
| +
 | |
| +static const struct drm_crtc_funcs vc4_crtc_funcs = {
 | |
| +	.set_config = drm_atomic_helper_set_config,
 | |
| +	.destroy = drm_crtc_cleanup,
 | |
| +	.page_flip = vc4_fkms_page_flip,
 | |
| +	.set_property = NULL,
 | |
| +	.cursor_set = NULL, /* handled by drm_mode_cursor_universal */
 | |
| +	.cursor_move = NULL, /* handled by drm_mode_cursor_universal */
 | |
| +	.reset = vc4_fkms_crtc_reset,
 | |
| +	.atomic_duplicate_state = vc4_fkms_crtc_duplicate_state,
 | |
| +	.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
 | |
| +	.enable_vblank = vc4_fkms_enable_vblank,
 | |
| +	.disable_vblank = vc4_fkms_disable_vblank,
 | |
| +};
 | |
| +
 | |
| +static const struct drm_crtc_helper_funcs vc4_crtc_helper_funcs = {
 | |
| +	.mode_set_nofb = vc4_crtc_mode_set_nofb,
 | |
| +	.mode_valid = vc4_crtc_mode_valid,
 | |
| +	.atomic_check = vc4_crtc_atomic_check,
 | |
| +	.atomic_flush = vc4_crtc_atomic_flush,
 | |
| +	.atomic_enable = vc4_crtc_enable,
 | |
| +	.atomic_disable = vc4_crtc_disable,
 | |
| +};
 | |
| +
 | |
| +static const struct of_device_id vc4_firmware_kms_dt_match[] = {
 | |
| +	{ .compatible = "raspberrypi,rpi-firmware-kms" },
 | |
| +	{ .compatible = "raspberrypi,rpi-firmware-kms-2711",
 | |
| +	  .data = (void *)1 },
 | |
| +	{}
 | |
| +};
 | |
| +
 | |
| +static enum drm_connector_status
 | |
| +vc4_fkms_connector_detect(struct drm_connector *connector, bool force)
 | |
| +{
 | |
| +	DRM_DEBUG_KMS("connector detect.\n");
 | |
| +	return connector_status_connected;
 | |
| +}
 | |
| +
 | |
| +/* Queries the firmware to populate a drm_mode structure for this display */
 | |
| +static int vc4_fkms_get_fw_mode(struct vc4_fkms_connector *fkms_connector,
 | |
| +				struct drm_display_mode *mode)
 | |
| +{
 | |
| +	struct vc4_dev *vc4 = fkms_connector->vc4_dev;
 | |
| +	struct set_timings timings = { 0 };
 | |
| +	int ret;
 | |
| +
 | |
| +	timings.display = fkms_connector->display_number;
 | |
| +
 | |
| +	ret = rpi_firmware_property(vc4->firmware,
 | |
| +				    RPI_FIRMWARE_GET_DISPLAY_TIMING, &timings,
 | |
| +				    sizeof(timings));
 | |
| +	if (ret || !timings.clock)
 | |
| +		/* No mode returned - abort */
 | |
| +		return -1;
 | |
| +
 | |
| +	/* Equivalent to DRM_MODE macro. */
 | |
| +	memset(mode, 0, sizeof(*mode));
 | |
| +	strncpy(mode->name, "FIXED_MODE", sizeof(mode->name));
 | |
| +	mode->status = 0;
 | |
| +	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
 | |
| +	mode->clock = timings.clock;
 | |
| +	mode->hdisplay = timings.hdisplay;
 | |
| +	mode->hsync_start = timings.hsync_start;
 | |
| +	mode->hsync_end = timings.hsync_end;
 | |
| +	mode->htotal = timings.htotal;
 | |
| +	mode->hskew = 0;
 | |
| +	mode->vdisplay = timings.vdisplay;
 | |
| +	mode->vsync_start = timings.vsync_start;
 | |
| +	mode->vsync_end = timings.vsync_end;
 | |
| +	mode->vtotal = timings.vtotal;
 | |
| +	mode->vscan = timings.vscan;
 | |
| +
 | |
| +	if (timings.flags & TIMINGS_FLAGS_H_SYNC_POS)
 | |
| +		mode->flags |= DRM_MODE_FLAG_PHSYNC;
 | |
| +	else
 | |
| +		mode->flags |= DRM_MODE_FLAG_NHSYNC;
 | |
| +
 | |
| +	if (timings.flags & TIMINGS_FLAGS_V_SYNC_POS)
 | |
| +		mode->flags |= DRM_MODE_FLAG_PVSYNC;
 | |
| +	else
 | |
| +		mode->flags |= DRM_MODE_FLAG_NVSYNC;
 | |
| +
 | |
| +	if (timings.flags & TIMINGS_FLAGS_INTERLACE)
 | |
| +		mode->flags |= DRM_MODE_FLAG_INTERLACE;
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +static int vc4_fkms_get_edid_block(void *data, u8 *buf, unsigned int block,
 | |
| +				   size_t len)
 | |
| +{
 | |
| +	struct vc4_fkms_connector *fkms_connector =
 | |
| +					(struct vc4_fkms_connector *)data;
 | |
| +	struct vc4_dev *vc4 = fkms_connector->vc4_dev;
 | |
| +	struct mailbox_get_edid mb = {
 | |
| +		.tag1 = { RPI_FIRMWARE_GET_EDID_BLOCK_DISPLAY,
 | |
| +			  128 + 8, 0 },
 | |
| +		.block = block,
 | |
| +		.display_number = fkms_connector->display_number,
 | |
| +	};
 | |
| +	int ret = 0;
 | |
| +
 | |
| +	ret = rpi_firmware_property_list(vc4->firmware, &mb, sizeof(mb));
 | |
| +
 | |
| +	if (!ret)
 | |
| +		memcpy(buf, mb.edid, len);
 | |
| +
 | |
| +	return ret;
 | |
| +}
 | |
| +
 | |
| +static int vc4_fkms_connector_get_modes(struct drm_connector *connector)
 | |
| +{
 | |
| +	struct vc4_fkms_connector *fkms_connector =
 | |
| +					to_vc4_fkms_connector(connector);
 | |
| +	struct drm_encoder *encoder = fkms_connector->encoder;
 | |
| +	struct vc4_fkms_encoder *vc4_encoder = to_vc4_fkms_encoder(encoder);
 | |
| +	struct drm_display_mode fw_mode;
 | |
| +	struct drm_display_mode *mode;
 | |
| +	struct edid *edid;
 | |
| +	int num_modes;
 | |
| +
 | |
| +	if (!vc4_fkms_get_fw_mode(fkms_connector, &fw_mode)) {
 | |
| +		drm_mode_debug_printmodeline(&fw_mode);
 | |
| +		mode = drm_mode_duplicate(connector->dev,
 | |
| +					  &fw_mode);
 | |
| +		drm_mode_probed_add(connector, mode);
 | |
| +		num_modes = 1;	/* 1 mode */
 | |
| +	} else {
 | |
| +		edid = drm_do_get_edid(connector, vc4_fkms_get_edid_block,
 | |
| +				       fkms_connector);
 | |
| +
 | |
| +		/* FIXME: Can we do CEC?
 | |
| +		 * cec_s_phys_addr_from_edid(vc4->hdmi->cec_adap, edid);
 | |
| +		 * if (!edid)
 | |
| +		 *	return -ENODEV;
 | |
| +		 */
 | |
| +
 | |
| +		vc4_encoder->hdmi_monitor = drm_detect_hdmi_monitor(edid);
 | |
| +
 | |
| +		drm_connector_update_edid_property(connector, edid);
 | |
| +		num_modes = drm_add_edid_modes(connector, edid);
 | |
| +		kfree(edid);
 | |
| +	}
 | |
| +
 | |
| +	return num_modes;
 | |
| +}
 | |
| +
 | |
| +/* This is the DSI panel resolution. Use this as a default should the firmware
 | |
| + * not respond to our request for the timings.
 | |
| + */
 | |
| +static const struct drm_display_mode lcd_mode = {
 | |
| +	DRM_MODE("800x480", DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
 | |
| +		 25979400 / 1000,
 | |
| +		 800, 800 + 1, 800 + 1 + 2, 800 + 1 + 2 + 46, 0,
 | |
| +		 480, 480 + 7, 480 + 7 + 2, 480 + 7 + 2 + 21, 0,
 | |
| +		 0)
 | |
| +};
 | |
| +
 | |
| +static int vc4_fkms_lcd_connector_get_modes(struct drm_connector *connector)
 | |
| +{
 | |
| +	struct vc4_fkms_connector *fkms_connector =
 | |
| +					to_vc4_fkms_connector(connector);
 | |
| +	struct drm_display_mode *mode;
 | |
| +	struct drm_display_mode fw_mode;
 | |
| +
 | |
| +	if (!vc4_fkms_get_fw_mode(fkms_connector, &fw_mode) && fw_mode.clock)
 | |
| +		mode = drm_mode_duplicate(connector->dev,
 | |
| +					  &fw_mode);
 | |
| +	else
 | |
| +		mode = drm_mode_duplicate(connector->dev,
 | |
| +					  &lcd_mode);
 | |
| +
 | |
| +	if (!mode) {
 | |
| +		DRM_ERROR("Failed to create a new display mode\n");
 | |
| +		return -ENOMEM;
 | |
| +	}
 | |
| +
 | |
| +	drm_mode_probed_add(connector, mode);
 | |
| +
 | |
| +	/* We have one mode */
 | |
| +	return 1;
 | |
| +}
 | |
| +
 | |
| +static struct drm_encoder *
 | |
| +vc4_fkms_connector_best_encoder(struct drm_connector *connector)
 | |
| +{
 | |
| +	struct vc4_fkms_connector *fkms_connector =
 | |
| +		to_vc4_fkms_connector(connector);
 | |
| +	DRM_DEBUG_KMS("best_connector.\n");
 | |
| +	return fkms_connector->encoder;
 | |
| +}
 | |
| +
 | |
| +static void vc4_fkms_connector_destroy(struct drm_connector *connector)
 | |
| +{
 | |
| +	DRM_DEBUG_KMS("[CONNECTOR:%d] destroy.\n",
 | |
| +		      connector->base.id);
 | |
| +	drm_connector_unregister(connector);
 | |
| +	drm_connector_cleanup(connector);
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * vc4_connector_duplicate_state - duplicate connector state
 | |
| + * @connector: digital connector
 | |
| + *
 | |
| + * Allocates and returns a copy of the connector state (both common and
 | |
| + * digital connector specific) for the specified connector.
 | |
| + *
 | |
| + * Returns: The newly allocated connector state, or NULL on failure.
 | |
| + */
 | |
| +struct drm_connector_state *
 | |
| +vc4_connector_duplicate_state(struct drm_connector *connector)
 | |
| +{
 | |
| +	struct vc4_fkms_connector_state *state;
 | |
| +
 | |
| +	state = kmemdup(connector->state, sizeof(*state), GFP_KERNEL);
 | |
| +	if (!state)
 | |
| +		return NULL;
 | |
| +
 | |
| +	__drm_atomic_helper_connector_duplicate_state(connector, &state->base);
 | |
| +	return &state->base;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * vc4_connector_atomic_get_property - hook for connector->atomic_get_property.
 | |
| + * @connector: Connector to get the property for.
 | |
| + * @state: Connector state to retrieve the property from.
 | |
| + * @property: Property to retrieve.
 | |
| + * @val: Return value for the property.
 | |
| + *
 | |
| + * Returns the atomic property value for a digital connector.
 | |
| + */
 | |
| +int vc4_connector_atomic_get_property(struct drm_connector *connector,
 | |
| +				      const struct drm_connector_state *state,
 | |
| +				      struct drm_property *property,
 | |
| +				      uint64_t *val)
 | |
| +{
 | |
| +	struct vc4_fkms_connector *fkms_connector =
 | |
| +					to_vc4_fkms_connector(connector);
 | |
| +	struct vc4_fkms_connector_state *vc4_conn_state =
 | |
| +					to_vc4_fkms_connector_state(state);
 | |
| +
 | |
| +	if (property == fkms_connector->broadcast_rgb_property) {
 | |
| +		*val = vc4_conn_state->broadcast_rgb;
 | |
| +	} else {
 | |
| +		DRM_DEBUG_ATOMIC("Unknown property [PROP:%d:%s]\n",
 | |
| +				 property->base.id, property->name);
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * vc4_connector_atomic_set_property - hook for connector->atomic_set_property.
 | |
| + * @connector: Connector to set the property for.
 | |
| + * @state: Connector state to set the property on.
 | |
| + * @property: Property to set.
 | |
| + * @val: New value for the property.
 | |
| + *
 | |
| + * Sets the atomic property value for a digital connector.
 | |
| + */
 | |
| +int vc4_connector_atomic_set_property(struct drm_connector *connector,
 | |
| +				      struct drm_connector_state *state,
 | |
| +				      struct drm_property *property,
 | |
| +				      uint64_t val)
 | |
| +{
 | |
| +	struct vc4_fkms_connector *fkms_connector =
 | |
| +					to_vc4_fkms_connector(connector);
 | |
| +	struct vc4_fkms_connector_state *vc4_conn_state =
 | |
| +					to_vc4_fkms_connector_state(state);
 | |
| +
 | |
| +	if (property == fkms_connector->broadcast_rgb_property) {
 | |
| +		vc4_conn_state->broadcast_rgb = val;
 | |
| +		return 0;
 | |
| +	}
 | |
| +
 | |
| +	DRM_DEBUG_ATOMIC("Unknown property [PROP:%d:%s]\n",
 | |
| +			 property->base.id, property->name);
 | |
| +	return -EINVAL;
 | |
| +}
 | |
| +
 | |
| +int vc4_connector_atomic_check(struct drm_connector *connector,
 | |
| +			       struct drm_atomic_state *state)
 | |
| +{
 | |
| +	struct drm_connector_state *old_state =
 | |
| +		drm_atomic_get_old_connector_state(state, connector);
 | |
| +	struct vc4_fkms_connector_state *vc4_old_state =
 | |
| +					to_vc4_fkms_connector_state(old_state);
 | |
| +	struct drm_connector_state *new_state =
 | |
| +		drm_atomic_get_new_connector_state(state, connector);
 | |
| +	struct vc4_fkms_connector_state *vc4_new_state =
 | |
| +					to_vc4_fkms_connector_state(new_state);
 | |
| +	struct drm_crtc *crtc = new_state->crtc;
 | |
| +
 | |
| +	if (!crtc)
 | |
| +		return 0;
 | |
| +
 | |
| +	if (vc4_old_state->broadcast_rgb != vc4_new_state->broadcast_rgb) {
 | |
| +		struct drm_crtc_state *crtc_state;
 | |
| +
 | |
| +		crtc_state = drm_atomic_get_crtc_state(state, crtc);
 | |
| +		if (IS_ERR(crtc_state))
 | |
| +			return PTR_ERR(crtc_state);
 | |
| +
 | |
| +		crtc_state->mode_changed = true;
 | |
| +	}
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +static void vc4_hdmi_connector_reset(struct drm_connector *connector)
 | |
| +{
 | |
| +	drm_atomic_helper_connector_reset(connector);
 | |
| +	drm_atomic_helper_connector_tv_reset(connector);
 | |
| +}
 | |
| +
 | |
| +static const struct drm_connector_funcs vc4_fkms_connector_funcs = {
 | |
| +	.detect = vc4_fkms_connector_detect,
 | |
| +	.fill_modes = drm_helper_probe_single_connector_modes,
 | |
| +	.destroy = vc4_fkms_connector_destroy,
 | |
| +	.reset = vc4_hdmi_connector_reset,
 | |
| +	.atomic_duplicate_state = vc4_connector_duplicate_state,
 | |
| +	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
 | |
| +	.atomic_get_property = vc4_connector_atomic_get_property,
 | |
| +	.atomic_set_property = vc4_connector_atomic_set_property,
 | |
| +};
 | |
| +
 | |
| +static const struct drm_connector_helper_funcs vc4_fkms_connector_helper_funcs = {
 | |
| +	.get_modes = vc4_fkms_connector_get_modes,
 | |
| +	.best_encoder = vc4_fkms_connector_best_encoder,
 | |
| +	.atomic_check = vc4_connector_atomic_check,
 | |
| +};
 | |
| +
 | |
| +static const struct drm_connector_helper_funcs vc4_fkms_lcd_conn_helper_funcs = {
 | |
| +	.get_modes = vc4_fkms_lcd_connector_get_modes,
 | |
| +	.best_encoder = vc4_fkms_connector_best_encoder,
 | |
| +};
 | |
| +
 | |
| +static const struct drm_prop_enum_list broadcast_rgb_names[] = {
 | |
| +	{ VC4_BROADCAST_RGB_AUTO, "Automatic" },
 | |
| +	{ VC4_BROADCAST_RGB_FULL, "Full" },
 | |
| +	{ VC4_BROADCAST_RGB_LIMITED, "Limited 16:235" },
 | |
| +};
 | |
| +
 | |
| +static void
 | |
| +vc4_attach_broadcast_rgb_property(struct vc4_fkms_connector *fkms_connector)
 | |
| +{
 | |
| +	struct drm_device *dev = fkms_connector->base.dev;
 | |
| +	struct drm_property *prop;
 | |
| +
 | |
| +	prop = fkms_connector->broadcast_rgb_property;
 | |
| +	if (!prop) {
 | |
| +		prop = drm_property_create_enum(dev, DRM_MODE_PROP_ENUM,
 | |
| +						"Broadcast RGB",
 | |
| +						broadcast_rgb_names,
 | |
| +						ARRAY_SIZE(broadcast_rgb_names));
 | |
| +		if (!prop)
 | |
| +			return;
 | |
| +
 | |
| +		fkms_connector->broadcast_rgb_property = prop;
 | |
| +	}
 | |
| +
 | |
| +	drm_object_attach_property(&fkms_connector->base.base, prop, 0);
 | |
| +}
 | |
| +
 | |
| +static struct drm_connector *
 | |
| +vc4_fkms_connector_init(struct drm_device *dev, struct drm_encoder *encoder,
 | |
| +			u32 display_num)
 | |
| +{
 | |
| +	struct drm_connector *connector = NULL;
 | |
| +	struct vc4_fkms_connector *fkms_connector;
 | |
| +	struct vc4_fkms_connector_state *conn_state = NULL;
 | |
| +	struct vc4_dev *vc4_dev = to_vc4_dev(dev);
 | |
| +	int ret = 0;
 | |
| +
 | |
| +	DRM_DEBUG_KMS("connector_init, display_num %u\n", display_num);
 | |
| +
 | |
| +	fkms_connector = devm_kzalloc(dev->dev, sizeof(*fkms_connector),
 | |
| +				      GFP_KERNEL);
 | |
| +	if (!fkms_connector)
 | |
| +		return ERR_PTR(-ENOMEM);
 | |
| +
 | |
| +	/*
 | |
| +	 * Allocate enough memory to hold vc4_fkms_connector_state,
 | |
| +	 */
 | |
| +	conn_state = kzalloc(sizeof(*conn_state), GFP_KERNEL);
 | |
| +	if (!conn_state) {
 | |
| +		kfree(fkms_connector);
 | |
| +		return ERR_PTR(-ENOMEM);
 | |
| +	}
 | |
| +
 | |
| +	connector = &fkms_connector->base;
 | |
| +
 | |
| +	fkms_connector->encoder = encoder;
 | |
| +	fkms_connector->display_number = display_num;
 | |
| +	fkms_connector->display_type = vc4_get_display_type(display_num);
 | |
| +	fkms_connector->vc4_dev = vc4_dev;
 | |
| +
 | |
| +	__drm_atomic_helper_connector_reset(connector,
 | |
| +					    &conn_state->base);
 | |
| +
 | |
| +	if (fkms_connector->display_type == DRM_MODE_ENCODER_DSI) {
 | |
| +		drm_connector_init(dev, connector, &vc4_fkms_connector_funcs,
 | |
| +				   DRM_MODE_CONNECTOR_DSI);
 | |
| +		drm_connector_helper_add(connector,
 | |
| +					 &vc4_fkms_lcd_conn_helper_funcs);
 | |
| +		connector->interlace_allowed = 0;
 | |
| +	} else if (fkms_connector->display_type == DRM_MODE_ENCODER_TVDAC) {
 | |
| +		drm_connector_init(dev, connector, &vc4_fkms_connector_funcs,
 | |
| +				   DRM_MODE_CONNECTOR_Composite);
 | |
| +		drm_connector_helper_add(connector,
 | |
| +					 &vc4_fkms_lcd_conn_helper_funcs);
 | |
| +		connector->interlace_allowed = 1;
 | |
| +	} else {
 | |
| +		drm_connector_init(dev, connector, &vc4_fkms_connector_funcs,
 | |
| +				   DRM_MODE_CONNECTOR_HDMIA);
 | |
| +		drm_connector_helper_add(connector,
 | |
| +					 &vc4_fkms_connector_helper_funcs);
 | |
| +		connector->interlace_allowed = 1;
 | |
| +	}
 | |
| +
 | |
| +	ret = drm_mode_create_tv_margin_properties(dev);
 | |
| +	if (ret)
 | |
| +		goto fail;
 | |
| +
 | |
| +	drm_connector_attach_tv_margin_properties(connector);
 | |
| +
 | |
| +	connector->polled = (DRM_CONNECTOR_POLL_CONNECT |
 | |
| +			     DRM_CONNECTOR_POLL_DISCONNECT);
 | |
| +
 | |
| +	connector->doublescan_allowed = 0;
 | |
| +
 | |
| +	vc4_attach_broadcast_rgb_property(fkms_connector);
 | |
| +
 | |
| +	drm_connector_attach_encoder(connector, encoder);
 | |
| +
 | |
| +	return connector;
 | |
| +
 | |
| + fail:
 | |
| +	if (connector)
 | |
| +		vc4_fkms_connector_destroy(connector);
 | |
| +
 | |
| +	return ERR_PTR(ret);
 | |
| +}
 | |
| +
 | |
| +static void vc4_fkms_encoder_destroy(struct drm_encoder *encoder)
 | |
| +{
 | |
| +	DRM_DEBUG_KMS("Encoder_destroy\n");
 | |
| +	drm_encoder_cleanup(encoder);
 | |
| +}
 | |
| +
 | |
| +static const struct drm_encoder_funcs vc4_fkms_encoder_funcs = {
 | |
| +	.destroy = vc4_fkms_encoder_destroy,
 | |
| +};
 | |
| +
 | |
| +static void vc4_fkms_display_power(struct drm_encoder *encoder, bool power)
 | |
| +{
 | |
| +	struct vc4_fkms_encoder *vc4_encoder = to_vc4_fkms_encoder(encoder);
 | |
| +	struct vc4_dev *vc4 = to_vc4_dev(encoder->dev);
 | |
| +
 | |
| +	struct mailbox_display_pwr pwr = {
 | |
| +		.tag1 = {RPI_FIRMWARE_SET_DISPLAY_POWER, 8, 0, },
 | |
| +		.display = vc4_encoder->display_num,
 | |
| +		.state = power ? 1 : 0,
 | |
| +	};
 | |
| +
 | |
| +	rpi_firmware_property_list(vc4->firmware, &pwr, sizeof(pwr));
 | |
| +}
 | |
| +
 | |
| +static void vc4_fkms_encoder_enable(struct drm_encoder *encoder)
 | |
| +{
 | |
| +	vc4_fkms_display_power(encoder, true);
 | |
| +	DRM_DEBUG_KMS("Encoder_enable\n");
 | |
| +}
 | |
| +
 | |
| +static void vc4_fkms_encoder_disable(struct drm_encoder *encoder)
 | |
| +{
 | |
| +	vc4_fkms_display_power(encoder, false);
 | |
| +	DRM_DEBUG_KMS("Encoder_disable\n");
 | |
| +}
 | |
| +
 | |
| +static const struct drm_encoder_helper_funcs vc4_fkms_encoder_helper_funcs = {
 | |
| +	.enable = vc4_fkms_encoder_enable,
 | |
| +	.disable = vc4_fkms_encoder_disable,
 | |
| +};
 | |
| +
 | |
| +static int vc4_fkms_create_screen(struct device *dev, struct drm_device *drm,
 | |
| +				  int display_idx, int display_ref,
 | |
| +				  struct vc4_crtc **ret_crtc)
 | |
| +{
 | |
| +	struct vc4_dev *vc4 = to_vc4_dev(drm);
 | |
| +	struct vc4_crtc *vc4_crtc;
 | |
| +	struct vc4_fkms_encoder *vc4_encoder;
 | |
| +	struct drm_crtc *crtc;
 | |
| +	struct drm_plane *destroy_plane, *temp;
 | |
| +	struct mailbox_blank_display blank = {
 | |
| +		.tag1 = {RPI_FIRMWARE_FRAMEBUFFER_SET_DISPLAY_NUM, 4, 0, },
 | |
| +		.display = display_idx,
 | |
| +		.tag2 = { RPI_FIRMWARE_FRAMEBUFFER_BLANK, 4, 0, },
 | |
| +		.blank = 1,
 | |
| +	};
 | |
| +	struct drm_plane *planes[PLANES_PER_CRTC];
 | |
| +	int ret, i;
 | |
| +
 | |
| +	vc4_crtc = devm_kzalloc(dev, sizeof(*vc4_crtc), GFP_KERNEL);
 | |
| +	if (!vc4_crtc)
 | |
| +		return -ENOMEM;
 | |
| +	crtc = &vc4_crtc->base;
 | |
| +
 | |
| +	vc4_crtc->display_number = display_ref;
 | |
| +	vc4_crtc->display_type = vc4_get_display_type(display_ref);
 | |
| +
 | |
| +	/* Blank the firmware provided framebuffer */
 | |
| +	rpi_firmware_property_list(vc4->firmware, &blank, sizeof(blank));
 | |
| +
 | |
| +	for (i = 0; i < PLANES_PER_CRTC; i++) {
 | |
| +		planes[i] = vc4_fkms_plane_init(drm,
 | |
| +						(i == 0) ?
 | |
| +						  DRM_PLANE_TYPE_PRIMARY :
 | |
| +						  (i == PLANES_PER_CRTC - 1) ?
 | |
| +							DRM_PLANE_TYPE_CURSOR :
 | |
| +							DRM_PLANE_TYPE_OVERLAY,
 | |
| +						display_ref,
 | |
| +						i + (display_idx * PLANES_PER_CRTC)
 | |
| +					       );
 | |
| +		if (IS_ERR(planes[i])) {
 | |
| +			dev_err(dev, "failed to construct plane %u\n", i);
 | |
| +			ret = PTR_ERR(planes[i]);
 | |
| +			goto err;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	drm_crtc_init_with_planes(drm, crtc, planes[0],
 | |
| +				  planes[PLANES_PER_CRTC - 1], &vc4_crtc_funcs,
 | |
| +				  NULL);
 | |
| +	drm_crtc_helper_add(crtc, &vc4_crtc_helper_funcs);
 | |
| +
 | |
| +	/* Update the possible_crtcs mask for the overlay plane(s) */
 | |
| +	for (i = 1; i < (PLANES_PER_CRTC - 1); i++)
 | |
| +		planes[i]->possible_crtcs = drm_crtc_mask(crtc);
 | |
| +
 | |
| +	vc4_encoder = devm_kzalloc(dev, sizeof(*vc4_encoder), GFP_KERNEL);
 | |
| +	if (!vc4_encoder)
 | |
| +		return -ENOMEM;
 | |
| +	vc4_crtc->encoder = &vc4_encoder->base;
 | |
| +
 | |
| +	vc4_encoder->display_num = display_ref;
 | |
| +	vc4_encoder->base.possible_crtcs |= drm_crtc_mask(crtc);
 | |
| +
 | |
| +	drm_encoder_init(drm, &vc4_encoder->base, &vc4_fkms_encoder_funcs,
 | |
| +			 vc4_crtc->display_type, NULL);
 | |
| +	drm_encoder_helper_add(&vc4_encoder->base,
 | |
| +			       &vc4_fkms_encoder_helper_funcs);
 | |
| +
 | |
| +	vc4_crtc->connector = vc4_fkms_connector_init(drm, &vc4_encoder->base,
 | |
| +						      display_ref);
 | |
| +	if (IS_ERR(vc4_crtc->connector)) {
 | |
| +		ret = PTR_ERR(vc4_crtc->connector);
 | |
| +		goto err_destroy_encoder;
 | |
| +	}
 | |
| +
 | |
| +	*ret_crtc = vc4_crtc;
 | |
| +
 | |
| +	return 0;
 | |
| +
 | |
| +err_destroy_encoder:
 | |
| +	vc4_fkms_encoder_destroy(vc4_crtc->encoder);
 | |
| +	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;
 | |
| +}
 | |
| +
 | |
| +static int vc4_fkms_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 = to_vc4_dev(drm);
 | |
| +	struct device_node *firmware_node;
 | |
| +	const struct of_device_id *match;
 | |
| +	struct vc4_crtc **crtc_list;
 | |
| +	u32 num_displays, display_num;
 | |
| +	struct vc4_fkms *fkms;
 | |
| +	int ret;
 | |
| +	u32 display_id;
 | |
| +
 | |
| +	vc4->firmware_kms = true;
 | |
| +
 | |
| +	fkms = devm_kzalloc(dev, sizeof(*fkms), GFP_KERNEL);
 | |
| +	if (!fkms)
 | |
| +		return -ENOMEM;
 | |
| +
 | |
| +	match = of_match_device(vc4_firmware_kms_dt_match, dev);
 | |
| +	if (!match)
 | |
| +		return -ENODEV;
 | |
| +	if (match->data)
 | |
| +		fkms->bcm2711 = true;
 | |
| +
 | |
| +	firmware_node = of_parse_phandle(dev->of_node, "brcm,firmware", 0);
 | |
| +	vc4->firmware = devm_rpi_firmware_get(&pdev->dev, firmware_node);
 | |
| +	if (!vc4->firmware) {
 | |
| +		DRM_DEBUG("Failed to get Raspberry Pi firmware reference.\n");
 | |
| +		return -EPROBE_DEFER;
 | |
| +	}
 | |
| +	of_node_put(firmware_node);
 | |
| +
 | |
| +	ret = rpi_firmware_property(vc4->firmware,
 | |
| +				    RPI_FIRMWARE_FRAMEBUFFER_GET_NUM_DISPLAYS,
 | |
| +				    &num_displays, sizeof(u32));
 | |
| +
 | |
| +	/* If we fail to get the number of displays, then
 | |
| +	 * assume old firmware that doesn't have the mailbox call, so just
 | |
| +	 * set one display
 | |
| +	 */
 | |
| +	if (ret) {
 | |
| +		num_displays = 1;
 | |
| +		DRM_WARN("Unable to determine number of displays - assuming 1\n");
 | |
| +		ret = 0;
 | |
| +	}
 | |
| +
 | |
| +	ret = rpi_firmware_property(vc4->firmware,
 | |
| +				    RPI_FIRMWARE_GET_DISPLAY_CFG,
 | |
| +				    &fkms->cfg, sizeof(fkms->cfg));
 | |
| +
 | |
| +	if (ret)
 | |
| +		return -EINVAL;
 | |
| +	/* The firmware works in Hz. This will be compared against kHz, so div
 | |
| +	 * 1000 now rather than multiple times later.
 | |
| +	 */
 | |
| +	fkms->cfg.max_pixel_clock[0] /= 1000;
 | |
| +	fkms->cfg.max_pixel_clock[1] /= 1000;
 | |
| +
 | |
| +	/* Allocate a list, with space for a NULL on the end */
 | |
| +	crtc_list = devm_kzalloc(dev, sizeof(crtc_list) * (num_displays + 1),
 | |
| +				 GFP_KERNEL);
 | |
| +	if (!crtc_list)
 | |
| +		return -ENOMEM;
 | |
| +
 | |
| +	for (display_num = 0; display_num < num_displays; display_num++) {
 | |
| +		display_id = display_num;
 | |
| +		ret = rpi_firmware_property(vc4->firmware,
 | |
| +					    RPI_FIRMWARE_FRAMEBUFFER_GET_DISPLAY_ID,
 | |
| +					    &display_id, sizeof(display_id));
 | |
| +		/* FIXME: Determine the correct error handling here.
 | |
| +		 * Should we fail to create the one "screen" but keep the
 | |
| +		 * others, or fail the whole thing?
 | |
| +		 */
 | |
| +		if (ret)
 | |
| +			DRM_ERROR("Failed to get display id %u\n", display_num);
 | |
| +
 | |
| +		ret = vc4_fkms_create_screen(dev, drm, display_num, display_id,
 | |
| +					     &crtc_list[display_num]);
 | |
| +		if (ret)
 | |
| +			DRM_ERROR("Oh dear, failed to create display %u\n",
 | |
| +				  display_num);
 | |
| +	}
 | |
| +
 | |
| +	if (num_displays > 0) {
 | |
| +		/* Map the SMI interrupt reg */
 | |
| +		crtc_list[0]->regs = vc4_ioremap_regs(pdev, 0);
 | |
| +		if (IS_ERR(crtc_list[0]->regs))
 | |
| +			DRM_ERROR("Oh dear, failed to map registers\n");
 | |
| +
 | |
| +		writel(0, crtc_list[0]->regs + SMICS);
 | |
| +		ret = devm_request_irq(dev, platform_get_irq(pdev, 0),
 | |
| +				       vc4_crtc_irq_handler, 0,
 | |
| +				       "vc4 firmware kms", crtc_list);
 | |
| +		if (ret)
 | |
| +			DRM_ERROR("Oh dear, failed to register IRQ\n");
 | |
| +	} else {
 | |
| +		DRM_WARN("No displays found. Consider forcing hotplug if HDMI is attached\n");
 | |
| +	}
 | |
| +
 | |
| +	vc4->fkms = fkms;
 | |
| +
 | |
| +	platform_set_drvdata(pdev, crtc_list);
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +static void vc4_fkms_unbind(struct device *dev, struct device *master,
 | |
| +			    void *data)
 | |
| +{
 | |
| +	struct platform_device *pdev = to_platform_device(dev);
 | |
| +	struct vc4_crtc **crtc_list = dev_get_drvdata(dev);
 | |
| +	int i;
 | |
| +
 | |
| +	for (i = 0; crtc_list[i]; i++) {
 | |
| +		vc4_fkms_connector_destroy(crtc_list[i]->connector);
 | |
| +		vc4_fkms_encoder_destroy(crtc_list[i]->encoder);
 | |
| +		drm_crtc_cleanup(&crtc_list[i]->base);
 | |
| +	}
 | |
| +
 | |
| +	platform_set_drvdata(pdev, NULL);
 | |
| +}
 | |
| +
 | |
| +static const struct component_ops vc4_fkms_ops = {
 | |
| +	.bind   = vc4_fkms_bind,
 | |
| +	.unbind = vc4_fkms_unbind,
 | |
| +};
 | |
| +
 | |
| +static int vc4_fkms_probe(struct platform_device *pdev)
 | |
| +{
 | |
| +	return component_add(&pdev->dev, &vc4_fkms_ops);
 | |
| +}
 | |
| +
 | |
| +static int vc4_fkms_remove(struct platform_device *pdev)
 | |
| +{
 | |
| +	component_del(&pdev->dev, &vc4_fkms_ops);
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +struct platform_driver vc4_firmware_kms_driver = {
 | |
| +	.probe = vc4_fkms_probe,
 | |
| +	.remove = vc4_fkms_remove,
 | |
| +	.driver = {
 | |
| +		.name = "vc4_firmware_kms",
 | |
| +		.of_match_table = vc4_firmware_kms_dt_match,
 | |
| +	},
 | |
| +};
 | |
| --- a/drivers/gpu/drm/vc4/vc4_kms.c
 | |
| +++ b/drivers/gpu/drm/vc4/vc4_kms.c
 | |
| @@ -162,6 +162,9 @@ vc4_ctm_commit(struct vc4_dev *vc4, stru
 | |
|  	struct vc4_ctm_state *ctm_state = to_vc4_ctm_state(vc4->ctm_manager.state);
 | |
|  	struct drm_color_ctm *ctm = ctm_state->ctm;
 | |
|  
 | |
| +	if (vc4->firmware_kms)
 | |
| +		return;
 | |
| +
 | |
|  	if (ctm_state->fifo) {
 | |
|  		HVS_WRITE(SCALER_OLEDCOEF2,
 | |
|  			  VC4_SET_FIELD(vc4_ctm_s31_32_to_s0_9(ctm->matrix[0]),
 | |
| @@ -367,7 +370,7 @@ static void vc4_atomic_commit_tail(struc
 | |
|  	for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) {
 | |
|  		struct vc4_crtc_state *vc4_crtc_state;
 | |
|  
 | |
| -		if (!new_crtc_state->commit)
 | |
| +		if (!new_crtc_state->commit || vc4->firmware_kms)
 | |
|  			continue;
 | |
|  
 | |
|  		vc4_crtc_state = to_vc4_crtc_state(new_crtc_state);
 | |
| @@ -393,7 +396,7 @@ static void vc4_atomic_commit_tail(struc
 | |
|  		old_hvs_state->fifo_state[channel].pending_commit = NULL;
 | |
|  	}
 | |
|  
 | |
| -	if (vc4->is_vc5) {
 | |
| +	if (vc4->is_vc5 && !vc4->firmware_kms) {
 | |
|  		unsigned long state_rate = max(old_hvs_state->core_clock_rate,
 | |
|  					       new_hvs_state->core_clock_rate);
 | |
|  		unsigned long core_rate = max_t(unsigned long,
 | |
| @@ -412,10 +415,12 @@ static void vc4_atomic_commit_tail(struc
 | |
|  
 | |
|  	vc4_ctm_commit(vc4, state);
 | |
|  
 | |
| -	if (vc4->is_vc5)
 | |
| -		vc5_hvs_pv_muxing_commit(vc4, state);
 | |
| -	else
 | |
| -		vc4_hvs_pv_muxing_commit(vc4, state);
 | |
| +	if (!vc4->firmware_kms) {
 | |
| +		if (vc4->is_vc5)
 | |
| +			vc5_hvs_pv_muxing_commit(vc4, state);
 | |
| +		else
 | |
| +			vc4_hvs_pv_muxing_commit(vc4, state);
 | |
| +	}
 | |
|  
 | |
|  	drm_atomic_helper_commit_planes(dev, state,
 | |
|  					DRM_PLANE_COMMIT_ACTIVE_ONLY);
 | |
| @@ -430,7 +435,7 @@ static void vc4_atomic_commit_tail(struc
 | |
|  
 | |
|  	drm_atomic_helper_cleanup_planes(dev, state);
 | |
|  
 | |
| -	if (vc4->is_vc5) {
 | |
| +	if (vc4->is_vc5 && !vc4->firmware_kms) {
 | |
|  		drm_dbg(dev, "Running the core clock at %lu Hz\n",
 | |
|  			new_hvs_state->core_clock_rate);
 | |
|  
 | |
| @@ -447,11 +452,21 @@ static void vc4_atomic_commit_tail(struc
 | |
|  
 | |
|  static int vc4_atomic_commit_setup(struct drm_atomic_state *state)
 | |
|  {
 | |
| +	struct drm_device *dev = state->dev;
 | |
| +	struct vc4_dev *vc4 = to_vc4_dev(dev);
 | |
|  	struct drm_crtc_state *crtc_state;
 | |
|  	struct vc4_hvs_state *hvs_state;
 | |
|  	struct drm_crtc *crtc;
 | |
|  	unsigned int i;
 | |
|  
 | |
| +	/* We know for sure we don't want an async update here. Set
 | |
| +	 * state->legacy_cursor_update to false to prevent
 | |
| +	 * drm_atomic_helper_setup_commit() from auto-completing
 | |
| +	 * commit->flip_done.
 | |
| +	 */
 | |
| +	if (!vc4->firmware_kms)
 | |
| +		state->legacy_cursor_update = false;
 | |
| +
 | |
|  	hvs_state = vc4_hvs_get_new_global_state(state);
 | |
|  	if (WARN_ON(IS_ERR(hvs_state)))
 | |
|  		return PTR_ERR(hvs_state);
 | |
| @@ -806,6 +821,7 @@ static int vc4_hvs_channels_obj_init(str
 | |
|  static int vc4_pv_muxing_atomic_check(struct drm_device *dev,
 | |
|  				      struct drm_atomic_state *state)
 | |
|  {
 | |
| +	struct vc4_dev *vc4 = to_vc4_dev(state->dev);
 | |
|  	struct vc4_hvs_state *hvs_new_state;
 | |
|  	struct drm_crtc_state *old_crtc_state, *new_crtc_state;
 | |
|  	struct drm_crtc *crtc;
 | |
| @@ -829,6 +845,9 @@ static int vc4_pv_muxing_atomic_check(st
 | |
|  		unsigned int matching_channels;
 | |
|  		unsigned int channel;
 | |
|  
 | |
| +		if (vc4->firmware_kms)
 | |
| +			continue;
 | |
| +
 | |
|  		drm_dbg(dev, "%s: Trying to find a channel.\n", crtc->name);
 | |
|  
 | |
|  		/* Nothing to do here, let's skip it */
 | |
| @@ -1047,6 +1066,8 @@ int vc4_kms_load(struct drm_device *dev)
 | |
|  	dev->mode_config.helper_private = &vc4_mode_config_helpers;
 | |
|  	dev->mode_config.preferred_depth = 24;
 | |
|  	dev->mode_config.async_page_flip = true;
 | |
| +	if (vc4->firmware_kms)
 | |
| +		dev->mode_config.normalize_zpos = true;
 | |
|  
 | |
|  	ret = vc4_ctm_obj_init(vc4);
 | |
|  	if (ret)
 | |
| --- /dev/null
 | |
| +++ b/drivers/gpu/drm/vc4/vc_image_types.h
 | |
| @@ -0,0 +1,175 @@
 | |
| +
 | |
| +/*
 | |
| + * Copyright (c) 2012, Broadcom Europe Ltd
 | |
| + *
 | |
| + * Values taken from vc_image_types.h released by Broadcom at
 | |
| + * https://github.com/raspberrypi/userland/blob/master/interface/vctypes/vc_image_types.h
 | |
| + * and vc_image_structs.h at
 | |
| + * https://github.com/raspberrypi/userland/blob/master/interface/vctypes/vc_image_structs.h
 | |
| + *
 | |
| + * 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.
 | |
| + */
 | |
| +
 | |
| +enum {
 | |
| +	VC_IMAGE_MIN = 0, //bounds for error checking
 | |
| +
 | |
| +	VC_IMAGE_RGB565 = 1,
 | |
| +	VC_IMAGE_1BPP,
 | |
| +	VC_IMAGE_YUV420,
 | |
| +	VC_IMAGE_48BPP,
 | |
| +	VC_IMAGE_RGB888,
 | |
| +	VC_IMAGE_8BPP,
 | |
| +	/* 4bpp palettised image */
 | |
| +	VC_IMAGE_4BPP,
 | |
| +	/* A separated format of 16 colour/light shorts followed by 16 z
 | |
| +	 * values
 | |
| +	 */
 | |
| +	VC_IMAGE_3D32,
 | |
| +	/* 16 colours followed by 16 z values */
 | |
| +	VC_IMAGE_3D32B,
 | |
| +	/* A separated format of 16 material/colour/light shorts followed by
 | |
| +	 * 16 z values
 | |
| +	 */
 | |
| +	VC_IMAGE_3D32MAT,
 | |
| +	/* 32 bit format containing 18 bits of 6.6.6 RGB, 9 bits per short */
 | |
| +	VC_IMAGE_RGB2X9,
 | |
| +	/* 32-bit format holding 18 bits of 6.6.6 RGB */
 | |
| +	VC_IMAGE_RGB666,
 | |
| +	/* 4bpp palettised image with embedded palette */
 | |
| +	VC_IMAGE_PAL4_OBSOLETE,
 | |
| +	/* 8bpp palettised image with embedded palette */
 | |
| +	VC_IMAGE_PAL8_OBSOLETE,
 | |
| +	/* RGB888 with an alpha byte after each pixel */
 | |
| +	VC_IMAGE_RGBA32,
 | |
| +	/* a line of Y (32-byte padded), a line of U (16-byte padded), and a
 | |
| +	 * line of V (16-byte padded)
 | |
| +	 */
 | |
| +	VC_IMAGE_YUV422,
 | |
| +	/* RGB565 with a transparent patch */
 | |
| +	VC_IMAGE_RGBA565,
 | |
| +	/* Compressed (4444) version of RGBA32 */
 | |
| +	VC_IMAGE_RGBA16,
 | |
| +	/* VCIII codec format */
 | |
| +	VC_IMAGE_YUV_UV,
 | |
| +	/* VCIII T-format RGBA8888 */
 | |
| +	VC_IMAGE_TF_RGBA32,
 | |
| +	/* VCIII T-format RGBx8888 */
 | |
| +	VC_IMAGE_TF_RGBX32,
 | |
| +	/* VCIII T-format float */
 | |
| +	VC_IMAGE_TF_FLOAT,
 | |
| +	/* VCIII T-format RGBA4444 */
 | |
| +	VC_IMAGE_TF_RGBA16,
 | |
| +	/* VCIII T-format RGB5551 */
 | |
| +	VC_IMAGE_TF_RGBA5551,
 | |
| +	/* VCIII T-format RGB565 */
 | |
| +	VC_IMAGE_TF_RGB565,
 | |
| +	/* VCIII T-format 8-bit luma and 8-bit alpha */
 | |
| +	VC_IMAGE_TF_YA88,
 | |
| +	/* VCIII T-format 8 bit generic sample */
 | |
| +	VC_IMAGE_TF_BYTE,
 | |
| +	/* VCIII T-format 8-bit palette */
 | |
| +	VC_IMAGE_TF_PAL8,
 | |
| +	/* VCIII T-format 4-bit palette */
 | |
| +	VC_IMAGE_TF_PAL4,
 | |
| +	/* VCIII T-format Ericsson Texture Compressed */
 | |
| +	VC_IMAGE_TF_ETC1,
 | |
| +	/* RGB888 with R & B swapped */
 | |
| +	VC_IMAGE_BGR888,
 | |
| +	/* RGB888 with R & B swapped, but with no pitch, i.e. no padding after
 | |
| +	 * each row of pixels
 | |
| +	 */
 | |
| +	VC_IMAGE_BGR888_NP,
 | |
| +	/* Bayer image, extra defines which variant is being used */
 | |
| +	VC_IMAGE_BAYER,
 | |
| +	/* General wrapper for codec images e.g. JPEG from camera */
 | |
| +	VC_IMAGE_CODEC,
 | |
| +	/* VCIII codec format */
 | |
| +	VC_IMAGE_YUV_UV32,
 | |
| +	/* VCIII T-format 8-bit luma */
 | |
| +	VC_IMAGE_TF_Y8,
 | |
| +	/* VCIII T-format 8-bit alpha */
 | |
| +	VC_IMAGE_TF_A8,
 | |
| +	/* VCIII T-format 16-bit generic sample */
 | |
| +	VC_IMAGE_TF_SHORT,
 | |
| +	/* VCIII T-format 1bpp black/white */
 | |
| +	VC_IMAGE_TF_1BPP,
 | |
| +	VC_IMAGE_OPENGL,
 | |
| +	/* VCIII-B0 HVS YUV 4:4:4 interleaved samples */
 | |
| +	VC_IMAGE_YUV444I,
 | |
| +	/* Y, U, & V planes separately (VC_IMAGE_YUV422 has them interleaved on
 | |
| +	 * a per line basis)
 | |
| +	 */
 | |
| +	VC_IMAGE_YUV422PLANAR,
 | |
| +	/* 32bpp with 8bit alpha at MS byte, with R, G, B (LS byte) */
 | |
| +	VC_IMAGE_ARGB8888,
 | |
| +	/* 32bpp with 8bit unused at MS byte, with R, G, B (LS byte) */
 | |
| +	VC_IMAGE_XRGB8888,
 | |
| +
 | |
| +	/* interleaved 8 bit samples of Y, U, Y, V (4 flavours) */
 | |
| +	VC_IMAGE_YUV422YUYV,
 | |
| +	VC_IMAGE_YUV422YVYU,
 | |
| +	VC_IMAGE_YUV422UYVY,
 | |
| +	VC_IMAGE_YUV422VYUY,
 | |
| +
 | |
| +	/* 32bpp like RGBA32 but with unused alpha */
 | |
| +	VC_IMAGE_RGBX32,
 | |
| +	/* 32bpp, corresponding to RGBA with unused alpha */
 | |
| +	VC_IMAGE_RGBX8888,
 | |
| +	/* 32bpp, corresponding to BGRA with unused alpha */
 | |
| +	VC_IMAGE_BGRX8888,
 | |
| +
 | |
| +	/* Y as a plane, then UV byte interleaved in plane with same pitch,
 | |
| +	 * half height
 | |
| +	 */
 | |
| +	VC_IMAGE_YUV420SP,
 | |
| +
 | |
| +	/* Y, U, & V planes separately 4:4:4 */
 | |
| +	VC_IMAGE_YUV444PLANAR,
 | |
| +
 | |
| +	/* T-format 8-bit U - same as TF_Y8 buf from U plane */
 | |
| +	VC_IMAGE_TF_U8,
 | |
| +	/* T-format 8-bit U - same as TF_Y8 buf from V plane */
 | |
| +	VC_IMAGE_TF_V8,
 | |
| +
 | |
| +	/* YUV4:2:0 planar, 16bit values */
 | |
| +	VC_IMAGE_YUV420_16,
 | |
| +	/* YUV4:2:0 codec format, 16bit values */
 | |
| +	VC_IMAGE_YUV_UV_16,
 | |
| +	/* YUV4:2:0 with U,V in side-by-side format */
 | |
| +	VC_IMAGE_YUV420_S,
 | |
| +	/* 10-bit YUV 420 column image format */
 | |
| +	VC_IMAGE_YUV10COL,
 | |
| +	/* 32-bpp, 10-bit R/G/B, 2-bit Alpha */
 | |
| +	VC_IMAGE_RGBA1010102,
 | |
| +
 | |
| +	VC_IMAGE_MAX,     /* bounds for error checking */
 | |
| +	VC_IMAGE_FORCE_ENUM_16BIT = 0xffff,
 | |
| +};
 | |
| +
 | |
| +enum {
 | |
| +	/* Unknown or unset - defaults to BT601 interstitial */
 | |
| +	VC_IMAGE_YUVINFO_UNSPECIFIED    = 0,
 | |
| +
 | |
| +	/* colour-space conversions data [4 bits] */
 | |
| +
 | |
| +	/* ITU-R BT.601-5 [SDTV] (compatible with VideoCore-II) */
 | |
| +	VC_IMAGE_YUVINFO_CSC_ITUR_BT601      = 1,
 | |
| +	/* ITU-R BT.709-3 [HDTV] */
 | |
| +	VC_IMAGE_YUVINFO_CSC_ITUR_BT709      = 2,
 | |
| +	/* JPEG JFIF */
 | |
| +	VC_IMAGE_YUVINFO_CSC_JPEG_JFIF       = 3,
 | |
| +	/* Title 47 Code of Federal Regulations (2003) 73.682 (a) (20) */
 | |
| +	VC_IMAGE_YUVINFO_CSC_FCC             = 4,
 | |
| +	/* Society of Motion Picture and Television Engineers 240M (1999) */
 | |
| +	VC_IMAGE_YUVINFO_CSC_SMPTE_240M      = 5,
 | |
| +	/* ITU-R BT.470-2 System M */
 | |
| +	VC_IMAGE_YUVINFO_CSC_ITUR_BT470_2_M  = 6,
 | |
| +	/* ITU-R BT.470-2 System B,G */
 | |
| +	VC_IMAGE_YUVINFO_CSC_ITUR_BT470_2_BG = 7,
 | |
| +	/* JPEG JFIF, but with 16..255 luma */
 | |
| +	VC_IMAGE_YUVINFO_CSC_JPEG_JFIF_Y16_255 = 8,
 | |
| +	/* Rec 2020 */
 | |
| +	VC_IMAGE_YUVINFO_CSC_REC_2020        = 9,
 | |
| +};
 |