diff options
Diffstat (limited to 'drivers/gpu/drm/vc4')
| -rw-r--r-- | drivers/gpu/drm/vc4/vc4_bo.c | 9 | ||||
| -rw-r--r-- | drivers/gpu/drm/vc4/vc4_drv.c | 40 | ||||
| -rw-r--r-- | drivers/gpu/drm/vc4/vc4_drv.h | 25 | ||||
| -rw-r--r-- | drivers/gpu/drm/vc4/vc4_gem.c | 19 | ||||
| -rw-r--r-- | drivers/gpu/drm/vc4/vc4_hdmi.c | 54 | ||||
| -rw-r--r-- | drivers/gpu/drm/vc4/vc4_hdmi.h | 11 | ||||
| -rw-r--r-- | drivers/gpu/drm/vc4/vc4_hvs.c | 4 | ||||
| -rw-r--r-- | drivers/gpu/drm/vc4/vc4_kms.c | 318 | ||||
| -rw-r--r-- | drivers/gpu/drm/vc4/vc4_v3d.c | 12 | 
9 files changed, 350 insertions, 142 deletions
| diff --git a/drivers/gpu/drm/vc4/vc4_bo.c b/drivers/gpu/drm/vc4/vc4_bo.c index 74ceebd62fbc..cc74a3f3a07a 100644 --- a/drivers/gpu/drm/vc4/vc4_bo.c +++ b/drivers/gpu/drm/vc4/vc4_bo.c @@ -449,7 +449,7 @@ struct vc4_bo *vc4_bo_create(struct drm_device *dev, size_t unaligned_size,  	}  	if (IS_ERR(cma_obj)) { -		struct drm_printer p = drm_info_printer(vc4->dev->dev); +		struct drm_printer p = drm_info_printer(vc4->base.dev);  		DRM_ERROR("Failed to allocate from CMA:\n");  		vc4_bo_stats_print(&p, vc4);  		return ERR_PTR(-ENOMEM); @@ -590,7 +590,7 @@ static void vc4_bo_cache_time_work(struct work_struct *work)  {  	struct vc4_dev *vc4 =  		container_of(work, struct vc4_dev, bo_cache.time_work); -	struct drm_device *dev = vc4->dev; +	struct drm_device *dev = &vc4->base;  	mutex_lock(&vc4->bo_lock);  	vc4_bo_cache_free_old(dev); @@ -1005,6 +1005,7 @@ int vc4_get_tiling_ioctl(struct drm_device *dev, void *data,  	return 0;  } +static void vc4_bo_cache_destroy(struct drm_device *dev, void *unused);  int vc4_bo_cache_init(struct drm_device *dev)  {  	struct vc4_dev *vc4 = to_vc4_dev(dev); @@ -1033,10 +1034,10 @@ int vc4_bo_cache_init(struct drm_device *dev)  	INIT_WORK(&vc4->bo_cache.time_work, vc4_bo_cache_time_work);  	timer_setup(&vc4->bo_cache.time_timer, vc4_bo_cache_time_timer, 0); -	return 0; +	return drmm_add_action_or_reset(dev, vc4_bo_cache_destroy, NULL);  } -void vc4_bo_cache_destroy(struct drm_device *dev) +static void vc4_bo_cache_destroy(struct drm_device *dev, void *unused)  {  	struct vc4_dev *vc4 = to_vc4_dev(dev);  	int i; diff --git a/drivers/gpu/drm/vc4/vc4_drv.c b/drivers/gpu/drm/vc4/vc4_drv.c index f1a5fd5dab6f..839610f8092a 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.c +++ b/drivers/gpu/drm/vc4/vc4_drv.c @@ -257,37 +257,37 @@ static int vc4_drm_bind(struct device *dev)  	dev->coherent_dma_mask = DMA_BIT_MASK(32); -	vc4 = devm_kzalloc(dev, sizeof(*vc4), GFP_KERNEL); -	if (!vc4) -		return -ENOMEM; -  	/* If VC4 V3D is missing, don't advertise render nodes. */  	node = of_find_matching_node_and_match(NULL, vc4_v3d_dt_match, NULL);  	if (!node || !of_device_is_available(node))  		vc4_drm_driver.driver_features &= ~DRIVER_RENDER;  	of_node_put(node); -	drm = drm_dev_alloc(&vc4_drm_driver, dev); -	if (IS_ERR(drm)) -		return PTR_ERR(drm); +	vc4 = devm_drm_dev_alloc(dev, &vc4_drm_driver, struct vc4_dev, base); +	if (IS_ERR(vc4)) +		return PTR_ERR(vc4); + +	drm = &vc4->base;  	platform_set_drvdata(pdev, drm); -	vc4->dev = drm; -	drm->dev_private = vc4;  	INIT_LIST_HEAD(&vc4->debugfs_list);  	mutex_init(&vc4->bin_bo_lock);  	ret = vc4_bo_cache_init(drm);  	if (ret) -		goto dev_put; +		return ret; -	drm_mode_config_init(drm); +	ret = drmm_mode_config_init(drm); +	if (ret) +		return ret; -	vc4_gem_init(drm); +	ret = vc4_gem_init(drm); +	if (ret) +		return ret;  	ret = component_bind_all(dev, drm);  	if (ret) -		goto gem_destroy; +		return ret;  	ret = vc4_plane_create_additional_planes(drm);  	if (ret) @@ -312,29 +312,17 @@ static int vc4_drm_bind(struct device *dev)  unbind_all:  	component_unbind_all(dev, drm); -gem_destroy: -	vc4_gem_destroy(drm); -	vc4_bo_cache_destroy(drm); -dev_put: -	drm_dev_put(drm); +  	return ret;  }  static void vc4_drm_unbind(struct device *dev)  {  	struct drm_device *drm = dev_get_drvdata(dev); -	struct vc4_dev *vc4 = to_vc4_dev(drm);  	drm_dev_unregister(drm);  	drm_atomic_helper_shutdown(drm); - -	drm_mode_config_cleanup(drm); - -	drm_atomic_private_obj_fini(&vc4->load_tracker); -	drm_atomic_private_obj_fini(&vc4->ctm_manager); - -	drm_dev_put(drm);  }  static const struct component_master_ops vc4_drm_ops = { diff --git a/drivers/gpu/drm/vc4/vc4_drv.h b/drivers/gpu/drm/vc4/vc4_drv.h index 90b911fd2a7f..c5f2944d5bc6 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.h +++ b/drivers/gpu/drm/vc4/vc4_drv.h @@ -14,6 +14,7 @@  #include <drm/drm_device.h>  #include <drm/drm_encoder.h>  #include <drm/drm_gem_cma_helper.h> +#include <drm/drm_managed.h>  #include <drm/drm_mm.h>  #include <drm/drm_modeset_lock.h> @@ -71,7 +72,7 @@ struct vc4_perfmon {  };  struct vc4_dev { -	struct drm_device *dev; +	struct drm_device base;  	struct vc4_hvs *hvs;  	struct vc4_v3d *v3d; @@ -218,6 +219,7 @@ struct vc4_dev {  	struct drm_modeset_lock ctm_state_lock;  	struct drm_private_obj ctm_manager; +	struct drm_private_obj hvs_channels;  	struct drm_private_obj load_tracker;  	/* List of vc4_debugfs_info_entry for adding to debugfs once @@ -234,7 +236,7 @@ struct vc4_dev {  static inline struct vc4_dev *  to_vc4_dev(struct drm_device *dev)  { -	return (struct vc4_dev *)dev->dev_private; +	return container_of(dev, struct vc4_dev, base);  }  struct vc4_bo { @@ -287,7 +289,7 @@ struct vc4_bo {  static inline struct vc4_bo *  to_vc4_bo(struct drm_gem_object *bo)  { -	return (struct vc4_bo *)bo; +	return container_of(to_drm_gem_cma_obj(bo), struct vc4_bo, base);  }  struct vc4_fence { @@ -300,7 +302,7 @@ struct vc4_fence {  static inline struct vc4_fence *  to_vc4_fence(struct dma_fence *fence)  { -	return (struct vc4_fence *)fence; +	return container_of(fence, struct vc4_fence, base);  }  struct vc4_seqno_cb { @@ -347,7 +349,7 @@ struct vc4_plane {  static inline struct vc4_plane *  to_vc4_plane(struct drm_plane *plane)  { -	return (struct vc4_plane *)plane; +	return container_of(plane, struct vc4_plane, base);  }  enum vc4_scaling_mode { @@ -423,7 +425,7 @@ struct vc4_plane_state {  static inline struct vc4_plane_state *  to_vc4_plane_state(struct drm_plane_state *state)  { -	return (struct vc4_plane_state *)state; +	return container_of(state, struct vc4_plane_state, base);  }  enum vc4_encoder_type { @@ -499,7 +501,7 @@ struct vc4_crtc {  static inline struct vc4_crtc *  to_vc4_crtc(struct drm_crtc *crtc)  { -	return (struct vc4_crtc *)crtc; +	return container_of(crtc, struct vc4_crtc, base);  }  static inline const struct vc4_crtc_data * @@ -530,6 +532,9 @@ struct vc4_crtc_state {  		unsigned int top;  		unsigned int bottom;  	} margins; + +	/* Transitional state below, only valid during atomic commits */ +	bool update_muxing;  };  #define VC4_HVS_CHANNEL_DISABLED ((unsigned int)-1) @@ -537,7 +542,7 @@ struct vc4_crtc_state {  static inline struct vc4_crtc_state *  to_vc4_crtc_state(struct drm_crtc_state *crtc_state)  { -	return (struct vc4_crtc_state *)crtc_state; +	return container_of(crtc_state, struct vc4_crtc_state, base);  }  #define V3D_READ(offset) readl(vc4->v3d->regs + offset) @@ -809,7 +814,6 @@ struct drm_gem_object *vc4_prime_import_sg_table(struct drm_device *dev,  						 struct sg_table *sgt);  void *vc4_prime_vmap(struct drm_gem_object *obj);  int vc4_bo_cache_init(struct drm_device *dev); -void vc4_bo_cache_destroy(struct drm_device *dev);  int vc4_bo_inc_usecnt(struct vc4_bo *bo);  void vc4_bo_dec_usecnt(struct vc4_bo *bo);  void vc4_bo_add_to_purgeable_pool(struct vc4_bo *bo); @@ -874,8 +878,7 @@ extern struct platform_driver vc4_dsi_driver;  extern const struct dma_fence_ops vc4_fence_ops;  /* vc4_gem.c */ -void vc4_gem_init(struct drm_device *dev); -void vc4_gem_destroy(struct drm_device *dev); +int vc4_gem_init(struct drm_device *dev);  int vc4_submit_cl_ioctl(struct drm_device *dev, void *data,  			struct drm_file *file_priv);  int vc4_wait_seqno_ioctl(struct drm_device *dev, void *data, diff --git a/drivers/gpu/drm/vc4/vc4_gem.c b/drivers/gpu/drm/vc4/vc4_gem.c index 9f01ddd5b932..b641252939d8 100644 --- a/drivers/gpu/drm/vc4/vc4_gem.c +++ b/drivers/gpu/drm/vc4/vc4_gem.c @@ -314,16 +314,16 @@ vc4_reset_work(struct work_struct *work)  	struct vc4_dev *vc4 =  		container_of(work, struct vc4_dev, hangcheck.reset_work); -	vc4_save_hang_state(vc4->dev); +	vc4_save_hang_state(&vc4->base); -	vc4_reset(vc4->dev); +	vc4_reset(&vc4->base);  }  static void  vc4_hangcheck_elapsed(struct timer_list *t)  {  	struct vc4_dev *vc4 = from_timer(vc4, t, hangcheck.timer); -	struct drm_device *dev = vc4->dev; +	struct drm_device *dev = &vc4->base;  	uint32_t ct0ca, ct1ca;  	unsigned long irqflags;  	struct vc4_exec_info *bin_exec, *render_exec; @@ -1000,7 +1000,7 @@ vc4_job_handle_completed(struct vc4_dev *vc4)  		list_del(&exec->head);  		spin_unlock_irqrestore(&vc4->job_lock, irqflags); -		vc4_complete_exec(vc4->dev, exec); +		vc4_complete_exec(&vc4->base, exec);  		spin_lock_irqsave(&vc4->job_lock, irqflags);  	} @@ -1258,13 +1258,13 @@ vc4_submit_cl_ioctl(struct drm_device *dev, void *data,  	return 0;  fail: -	vc4_complete_exec(vc4->dev, exec); +	vc4_complete_exec(&vc4->base, exec);  	return ret;  } -void -vc4_gem_init(struct drm_device *dev) +static void vc4_gem_destroy(struct drm_device *dev, void *unused); +int vc4_gem_init(struct drm_device *dev)  {  	struct vc4_dev *vc4 = to_vc4_dev(dev); @@ -1285,10 +1285,11 @@ vc4_gem_init(struct drm_device *dev)  	INIT_LIST_HEAD(&vc4->purgeable.list);  	mutex_init(&vc4->purgeable.lock); + +	return drmm_add_action_or_reset(dev, vc4_gem_destroy, NULL);  } -void -vc4_gem_destroy(struct drm_device *dev) +static void vc4_gem_destroy(struct drm_device *dev, void *unused)  {  	struct vc4_dev *vc4 = to_vc4_dev(dev); diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index e8f99e290655..afc178b0d89f 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -760,12 +760,54 @@ static void vc4_hdmi_encoder_enable(struct drm_encoder *encoder)  {  } +#define WIFI_2_4GHz_CH1_MIN_FREQ	2400000000ULL +#define WIFI_2_4GHz_CH1_MAX_FREQ	2422000000ULL + +static int vc4_hdmi_encoder_atomic_check(struct drm_encoder *encoder, +					 struct drm_crtc_state *crtc_state, +					 struct drm_connector_state *conn_state) +{ +	struct drm_display_mode *mode = &crtc_state->adjusted_mode; +	struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); +	unsigned long long pixel_rate = mode->clock * 1000; +	unsigned long long tmds_rate; + +	if (vc4_hdmi->variant->unsupported_odd_h_timings && +	    ((mode->hdisplay % 2) || (mode->hsync_start % 2) || +	     (mode->hsync_end % 2) || (mode->htotal % 2))) +		return -EINVAL; + +	/* +	 * The 1440p@60 pixel rate is in the same range than the first +	 * WiFi channel (between 2.4GHz and 2.422GHz with 22MHz +	 * bandwidth). Slightly lower the frequency to bring it out of +	 * the WiFi range. +	 */ +	tmds_rate = pixel_rate * 10; +	if (vc4_hdmi->disable_wifi_frequencies && +	    (tmds_rate >= WIFI_2_4GHz_CH1_MIN_FREQ && +	     tmds_rate <= WIFI_2_4GHz_CH1_MAX_FREQ)) { +		mode->clock = 238560; +		pixel_rate = mode->clock * 1000; +	} + +	if (pixel_rate > vc4_hdmi->variant->max_pixel_clock) +		return -EINVAL; + +	return 0; +} +  static enum drm_mode_status  vc4_hdmi_encoder_mode_valid(struct drm_encoder *encoder,  			    const struct drm_display_mode *mode)  {  	struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); +	if (vc4_hdmi->variant->unsupported_odd_h_timings && +	    ((mode->hdisplay % 2) || (mode->hsync_start % 2) || +	     (mode->hsync_end % 2) || (mode->htotal % 2))) +		return MODE_H_ILLEGAL; +  	if ((mode->clock * 1000) > vc4_hdmi->variant->max_pixel_clock)  		return MODE_CLOCK_HIGH; @@ -773,6 +815,7 @@ vc4_hdmi_encoder_mode_valid(struct drm_encoder *encoder,  }  static const struct drm_encoder_helper_funcs vc4_hdmi_encoder_helper_funcs = { +	.atomic_check = vc4_hdmi_encoder_atomic_check,  	.mode_valid = vc4_hdmi_encoder_mode_valid,  	.disable = vc4_hdmi_encoder_disable,  	.enable = vc4_hdmi_encoder_enable, @@ -922,6 +965,7 @@ static int vc4_hdmi_audio_hw_params(struct snd_pcm_substream *substream,  				    struct snd_soc_dai *dai)  {  	struct vc4_hdmi *vc4_hdmi = dai_to_hdmi(dai); +	struct drm_encoder *encoder = &vc4_hdmi->encoder.base.base;  	struct device *dev = &vc4_hdmi->pdev->dev;  	u32 audio_packet_config, channel_mask;  	u32 channel_map; @@ -981,6 +1025,8 @@ static int vc4_hdmi_audio_hw_params(struct snd_pcm_substream *substream,  	HDMI_WRITE(HDMI_AUDIO_PACKET_CONFIG, audio_packet_config);  	vc4_hdmi_set_n_cts(vc4_hdmi); +	vc4_hdmi_set_audio_infoframe(encoder); +  	return 0;  } @@ -988,11 +1034,9 @@ static int vc4_hdmi_audio_trigger(struct snd_pcm_substream *substream, int cmd,  				  struct snd_soc_dai *dai)  {  	struct vc4_hdmi *vc4_hdmi = dai_to_hdmi(dai); -	struct drm_encoder *encoder = &vc4_hdmi->encoder.base.base;  	switch (cmd) {  	case SNDRV_PCM_TRIGGER_START: -		vc4_hdmi_set_audio_infoframe(encoder);  		vc4_hdmi->audio.streaming = true;  		if (vc4_hdmi->variant->phy_rng_enable) @@ -1076,6 +1120,7 @@ static const struct snd_soc_dapm_route vc4_hdmi_audio_routes[] = {  };  static const struct snd_soc_component_driver vc4_hdmi_audio_component_drv = { +	.name			= "vc4-hdmi-codec-dai-component",  	.controls		= vc4_hdmi_audio_controls,  	.num_controls		= ARRAY_SIZE(vc4_hdmi_audio_controls),  	.dapm_widgets		= vc4_hdmi_audio_widgets, @@ -1692,6 +1737,9 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data)  		vc4_hdmi->hpd_active_low = hpd_gpio_flags & OF_GPIO_ACTIVE_LOW;  	} +	vc4_hdmi->disable_wifi_frequencies = +		of_property_read_bool(dev->of_node, "wifi-2.4ghz-coexistence"); +  	pm_runtime_enable(dev);  	drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS); @@ -1815,6 +1863,7 @@ static const struct vc4_hdmi_variant bcm2711_hdmi0_variant = {  		PHY_LANE_2,  		PHY_LANE_CK,  	}, +	.unsupported_odd_h_timings	= true,  	.init_resources		= vc5_hdmi_init_resources,  	.csc_setup		= vc5_hdmi_csc_setup, @@ -1840,6 +1889,7 @@ static const struct vc4_hdmi_variant bcm2711_hdmi1_variant = {  		PHY_LANE_CK,  		PHY_LANE_2,  	}, +	.unsupported_odd_h_timings	= true,  	.init_resources		= vc5_hdmi_init_resources,  	.csc_setup		= vc5_hdmi_csc_setup, diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.h b/drivers/gpu/drm/vc4/vc4_hdmi.h index 63c6f8bddf1d..0526a9cf608a 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.h +++ b/drivers/gpu/drm/vc4/vc4_hdmi.h @@ -62,6 +62,9 @@ struct vc4_hdmi_variant {  	 */  	enum vc4_hdmi_phy_channel phy_lane_mapping[4]; +	/* The BCM2711 cannot deal with odd horizontal pixel timings */ +	bool unsupported_odd_h_timings; +  	/* Callback to get the resources (memory region, interrupts,  	 * clocks, etc) for that variant.  	 */ @@ -139,6 +142,14 @@ struct vc4_hdmi {  	int hpd_gpio;  	bool hpd_active_low; +	/* +	 * On some systems (like the RPi4), some modes are in the same +	 * frequency range than the WiFi channels (1440p@60Hz for +	 * example). Should we take evasive actions because that system +	 * has a wifi adapter? +	 */ +	bool disable_wifi_frequencies; +  	struct cec_adapter *cec_adap;  	struct cec_msg cec_rx_msg;  	bool cec_tx_ok; diff --git a/drivers/gpu/drm/vc4/vc4_hvs.c b/drivers/gpu/drm/vc4/vc4_hvs.c index 4d0a833366ce..b72b2bd05a81 100644 --- a/drivers/gpu/drm/vc4/vc4_hvs.c +++ b/drivers/gpu/drm/vc4/vc4_hvs.c @@ -560,7 +560,7 @@ 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_dev *vc4 = to_vc4_dev(drm);  	struct vc4_hvs *hvs = NULL;  	int ret;  	u32 dispctrl; @@ -679,7 +679,7 @@ static void vc4_hvs_unbind(struct device *dev, struct device *master,  			   void *data)  {  	struct drm_device *drm = dev_get_drvdata(master); -	struct vc4_dev *vc4 = drm->dev_private; +	struct vc4_dev *vc4 = to_vc4_dev(drm);  	struct vc4_hvs *hvs = vc4->hvs;  	if (drm_mm_node_allocated(&vc4->hvs->mitchell_netravali_filter)) diff --git a/drivers/gpu/drm/vc4/vc4_kms.c b/drivers/gpu/drm/vc4/vc4_kms.c index 149825ff5df8..ba310c0ab5f6 100644 --- a/drivers/gpu/drm/vc4/vc4_kms.c +++ b/drivers/gpu/drm/vc4/vc4_kms.c @@ -24,6 +24,8 @@  #include "vc4_drv.h"  #include "vc4_regs.h" +#define HVS_NUM_CHANNELS 3 +  struct vc4_ctm_state {  	struct drm_private_state base;  	struct drm_color_ctm *ctm; @@ -35,6 +37,17 @@ static struct vc4_ctm_state *to_vc4_ctm_state(struct drm_private_state *priv)  	return container_of(priv, struct vc4_ctm_state, base);  } +struct vc4_hvs_state { +	struct drm_private_state base; +	unsigned int unassigned_channels; +}; + +static struct vc4_hvs_state * +to_vc4_hvs_state(struct drm_private_state *priv) +{ +	return container_of(priv, struct vc4_hvs_state, base); +} +  struct vc4_load_tracker_state {  	struct drm_private_state base;  	u64 hvs_load; @@ -51,7 +64,7 @@ static struct vc4_ctm_state *vc4_get_ctm_state(struct drm_atomic_state *state,  					       struct drm_private_obj *manager)  {  	struct drm_device *dev = state->dev; -	struct vc4_dev *vc4 = dev->dev_private; +	struct vc4_dev *vc4 = to_vc4_dev(dev);  	struct drm_private_state *priv_state;  	int ret; @@ -93,6 +106,29 @@ static const struct drm_private_state_funcs vc4_ctm_state_funcs = {  	.atomic_destroy_state = vc4_ctm_destroy_state,  }; +static void vc4_ctm_obj_fini(struct drm_device *dev, void *unused) +{ +	struct vc4_dev *vc4 = to_vc4_dev(dev); + +	drm_atomic_private_obj_fini(&vc4->ctm_manager); +} + +static int vc4_ctm_obj_init(struct vc4_dev *vc4) +{ +	struct vc4_ctm_state *ctm_state; + +	drm_modeset_lock_init(&vc4->ctm_state_lock); + +	ctm_state = kzalloc(sizeof(*ctm_state), GFP_KERNEL); +	if (!ctm_state) +		return -ENOMEM; + +	drm_atomic_private_obj_init(&vc4->base, &vc4->ctm_manager, &ctm_state->base, +				    &vc4_ctm_state_funcs); + +	return drmm_add_action_or_reset(&vc4->base, vc4_ctm_obj_fini, NULL); +} +  /* Converts a DRM S31.32 value to the HW S0.9 format. */  static u16 vc4_ctm_s31_32_to_s0_9(u64 in)  { @@ -146,6 +182,19 @@ vc4_ctm_commit(struct vc4_dev *vc4, struct drm_atomic_state *state)  		  VC4_SET_FIELD(ctm_state->fifo, SCALER_OLEDOFFS_DISPFIFO));  } +static struct vc4_hvs_state * +vc4_hvs_get_global_state(struct drm_atomic_state *state) +{ +	struct vc4_dev *vc4 = to_vc4_dev(state->dev); +	struct drm_private_state *priv_state; + +	priv_state = drm_atomic_get_private_obj_state(state, &vc4->hvs_channels); +	if (IS_ERR(priv_state)) +		return ERR_CAST(priv_state); + +	return to_vc4_hvs_state(priv_state); +} +  static void vc4_hvs_pv_muxing_commit(struct vc4_dev *vc4,  				     struct drm_atomic_state *state)  { @@ -190,10 +239,7 @@ static void vc5_hvs_pv_muxing_commit(struct vc4_dev *vc4,  {  	struct drm_crtc_state *crtc_state;  	struct drm_crtc *crtc; -	unsigned char dsp2_mux = 0; -	unsigned char dsp3_mux = 3; -	unsigned char dsp4_mux = 3; -	unsigned char dsp5_mux = 3; +	unsigned char mux;  	unsigned int i;  	u32 reg; @@ -201,50 +247,59 @@ static void vc5_hvs_pv_muxing_commit(struct vc4_dev *vc4,  		struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc_state);  		struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc); -		if (!crtc_state->active) +		if (!vc4_state->update_muxing)  			continue;  		switch (vc4_crtc->data->hvs_output) {  		case 2: -			dsp2_mux = (vc4_state->assigned_channel == 2) ? 0 : 1; +			mux = (vc4_state->assigned_channel == 2) ? 0 : 1; +			reg = HVS_READ(SCALER_DISPECTRL); +			HVS_WRITE(SCALER_DISPECTRL, +				  (reg & ~SCALER_DISPECTRL_DSP2_MUX_MASK) | +				  VC4_SET_FIELD(mux, SCALER_DISPECTRL_DSP2_MUX));  			break;  		case 3: -			dsp3_mux = vc4_state->assigned_channel; +			if (vc4_state->assigned_channel == VC4_HVS_CHANNEL_DISABLED) +				mux = 3; +			else +				mux = vc4_state->assigned_channel; + +			reg = HVS_READ(SCALER_DISPCTRL); +			HVS_WRITE(SCALER_DISPCTRL, +				  (reg & ~SCALER_DISPCTRL_DSP3_MUX_MASK) | +				  VC4_SET_FIELD(mux, SCALER_DISPCTRL_DSP3_MUX));  			break;  		case 4: -			dsp4_mux = vc4_state->assigned_channel; +			if (vc4_state->assigned_channel == VC4_HVS_CHANNEL_DISABLED) +				mux = 3; +			else +				mux = vc4_state->assigned_channel; + +			reg = HVS_READ(SCALER_DISPEOLN); +			HVS_WRITE(SCALER_DISPEOLN, +				  (reg & ~SCALER_DISPEOLN_DSP4_MUX_MASK) | +				  VC4_SET_FIELD(mux, SCALER_DISPEOLN_DSP4_MUX)); +  			break;  		case 5: -			dsp5_mux = vc4_state->assigned_channel; +			if (vc4_state->assigned_channel == VC4_HVS_CHANNEL_DISABLED) +				mux = 3; +			else +				mux = vc4_state->assigned_channel; + +			reg = HVS_READ(SCALER_DISPDITHER); +			HVS_WRITE(SCALER_DISPDITHER, +				  (reg & ~SCALER_DISPDITHER_DSP5_MUX_MASK) | +				  VC4_SET_FIELD(mux, SCALER_DISPDITHER_DSP5_MUX));  			break;  		default:  			break;  		}  	} - -	reg = HVS_READ(SCALER_DISPECTRL); -	HVS_WRITE(SCALER_DISPECTRL, -		  (reg & ~SCALER_DISPECTRL_DSP2_MUX_MASK) | -		  VC4_SET_FIELD(dsp2_mux, SCALER_DISPECTRL_DSP2_MUX)); - -	reg = HVS_READ(SCALER_DISPCTRL); -	HVS_WRITE(SCALER_DISPCTRL, -		  (reg & ~SCALER_DISPCTRL_DSP3_MUX_MASK) | -		  VC4_SET_FIELD(dsp3_mux, SCALER_DISPCTRL_DSP3_MUX)); - -	reg = HVS_READ(SCALER_DISPEOLN); -	HVS_WRITE(SCALER_DISPEOLN, -		  (reg & ~SCALER_DISPEOLN_DSP4_MUX_MASK) | -		  VC4_SET_FIELD(dsp4_mux, SCALER_DISPEOLN_DSP4_MUX)); - -	reg = HVS_READ(SCALER_DISPDITHER); -	HVS_WRITE(SCALER_DISPDITHER, -		  (reg & ~SCALER_DISPDITHER_DSP5_MUX_MASK) | -		  VC4_SET_FIELD(dsp5_mux, SCALER_DISPDITHER_DSP5_MUX));  }  static void @@ -609,50 +664,148 @@ static const struct drm_private_state_funcs vc4_load_tracker_state_funcs = {  	.atomic_destroy_state = vc4_load_tracker_destroy_state,  }; -#define NUM_OUTPUTS  6 -#define NUM_CHANNELS 3 +static void vc4_load_tracker_obj_fini(struct drm_device *dev, void *unused) +{ +	struct vc4_dev *vc4 = to_vc4_dev(dev); -static int -vc4_atomic_check(struct drm_device *dev, struct drm_atomic_state *state) +	if (!vc4->load_tracker_available) +		return; + +	drm_atomic_private_obj_fini(&vc4->load_tracker); +} + +static int vc4_load_tracker_obj_init(struct vc4_dev *vc4)  { -	unsigned long unassigned_channels = GENMASK(NUM_CHANNELS - 1, 0); -	struct drm_crtc_state *old_crtc_state, *new_crtc_state; -	struct drm_crtc *crtc; -	int i, ret; +	struct vc4_load_tracker_state *load_state; -	/* -	 * Since the HVS FIFOs are shared across all the pixelvalves and -	 * the TXP (and thus all the CRTCs), we need to pull the current -	 * state of all the enabled CRTCs so that an update to a single -	 * CRTC still keeps the previous FIFOs enabled and assigned to -	 * the same CRTCs, instead of evaluating only the CRTC being -	 * modified. -	 */ -	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { -		struct drm_crtc_state *crtc_state; +	if (!vc4->load_tracker_available) +		return 0; -		if (!crtc->state->enable) -			continue; +	load_state = kzalloc(sizeof(*load_state), GFP_KERNEL); +	if (!load_state) +		return -ENOMEM; -		crtc_state = drm_atomic_get_crtc_state(state, crtc); -		if (IS_ERR(crtc_state)) -			return PTR_ERR(crtc_state); -	} +	drm_atomic_private_obj_init(&vc4->base, &vc4->load_tracker, +				    &load_state->base, +				    &vc4_load_tracker_state_funcs); + +	return drmm_add_action_or_reset(&vc4->base, vc4_load_tracker_obj_fini, NULL); +} + +static struct drm_private_state * +vc4_hvs_channels_duplicate_state(struct drm_private_obj *obj) +{ +	struct vc4_hvs_state *old_state = to_vc4_hvs_state(obj->state); +	struct vc4_hvs_state *state; + +	state = kzalloc(sizeof(*state), GFP_KERNEL); +	if (!state) +		return NULL; + +	__drm_atomic_helper_private_obj_duplicate_state(obj, &state->base); + +	state->unassigned_channels = old_state->unassigned_channels; + +	return &state->base; +} + +static void vc4_hvs_channels_destroy_state(struct drm_private_obj *obj, +					   struct drm_private_state *state) +{ +	struct vc4_hvs_state *hvs_state = to_vc4_hvs_state(state); + +	kfree(hvs_state); +} + +static const struct drm_private_state_funcs vc4_hvs_state_funcs = { +	.atomic_duplicate_state = vc4_hvs_channels_duplicate_state, +	.atomic_destroy_state = vc4_hvs_channels_destroy_state, +}; + +static void vc4_hvs_channels_obj_fini(struct drm_device *dev, void *unused) +{ +	struct vc4_dev *vc4 = to_vc4_dev(dev); + +	drm_atomic_private_obj_fini(&vc4->hvs_channels); +} + +static int vc4_hvs_channels_obj_init(struct vc4_dev *vc4) +{ +	struct vc4_hvs_state *state; + +	state = kzalloc(sizeof(*state), GFP_KERNEL); +	if (!state) +		return -ENOMEM; + +	state->unassigned_channels = GENMASK(HVS_NUM_CHANNELS - 1, 0); +	drm_atomic_private_obj_init(&vc4->base, &vc4->hvs_channels, +				    &state->base, +				    &vc4_hvs_state_funcs); + +	return drmm_add_action_or_reset(&vc4->base, vc4_hvs_channels_obj_fini, NULL); +} + +/* + * The BCM2711 HVS has up to 7 outputs connected to the pixelvalves and + * the TXP (and therefore all the CRTCs found on that platform). + * + * The naive (and our initial) implementation would just iterate over + * all the active CRTCs, try to find a suitable FIFO, and then remove it + * from the pool of available FIFOs. However, there are a few corner + * cases that need to be considered: + * + * - When running in a dual-display setup (so with two CRTCs involved), + *   we can update the state of a single CRTC (for example by changing + *   its mode using xrandr under X11) without affecting the other. In + *   this case, the other CRTC wouldn't be in the state at all, so we + *   need to consider all the running CRTCs in the DRM device to assign + *   a FIFO, not just the one in the state. + * + * - To fix the above, we can't use drm_atomic_get_crtc_state on all + *   enabled CRTCs to pull their CRTC state into the global state, since + *   a page flip would start considering their vblank to complete. Since + *   we don't have a guarantee that they are actually active, that + *   vblank might never happen, and shouldn't even be considered if we + *   want to do a page flip on a single CRTC. That can be tested by + *   doing a modetest -v first on HDMI1 and then on HDMI0. + * + * - Since we need the pixelvalve to be disabled and enabled back when + *   the FIFO is changed, we should keep the FIFO assigned for as long + *   as the CRTC is enabled, only considering it free again once that + *   CRTC has been disabled. This can be tested by booting X11 on a + *   single display, and changing the resolution down and then back up. + */ +static int vc4_pv_muxing_atomic_check(struct drm_device *dev, +				      struct drm_atomic_state *state) +{ +	struct vc4_hvs_state *hvs_new_state; +	struct drm_crtc_state *old_crtc_state, *new_crtc_state; +	struct drm_crtc *crtc; +	unsigned int i; + +	hvs_new_state = vc4_hvs_get_global_state(state); +	if (!hvs_new_state) +		return -EINVAL;  	for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state, new_crtc_state, i) { +		struct vc4_crtc_state *old_vc4_crtc_state = +			to_vc4_crtc_state(old_crtc_state);  		struct vc4_crtc_state *new_vc4_crtc_state =  			to_vc4_crtc_state(new_crtc_state);  		struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);  		unsigned int matching_channels; -		if (old_crtc_state->enable && !new_crtc_state->enable) -			new_vc4_crtc_state->assigned_channel = VC4_HVS_CHANNEL_DISABLED; - -		if (!new_crtc_state->enable) +		/* Nothing to do here, let's skip it */ +		if (old_crtc_state->enable == new_crtc_state->enable)  			continue; -		if (new_vc4_crtc_state->assigned_channel != VC4_HVS_CHANNEL_DISABLED) { -			unassigned_channels &= ~BIT(new_vc4_crtc_state->assigned_channel); +		/* Muxing will need to be modified, mark it as such */ +		new_vc4_crtc_state->update_muxing = true; + +		/* If we're disabling our CRTC, we put back our channel */ +		if (!new_crtc_state->enable) { +			hvs_new_state->unassigned_channels |= BIT(old_vc4_crtc_state->assigned_channel); +			new_vc4_crtc_state->assigned_channel = VC4_HVS_CHANNEL_DISABLED;  			continue;  		} @@ -680,17 +833,29 @@ vc4_atomic_check(struct drm_device *dev, struct drm_atomic_state *state)  		 * the future, we will need to have something smarter,  		 * but it works so far.  		 */ -		matching_channels = unassigned_channels & vc4_crtc->data->hvs_available_channels; +		matching_channels = hvs_new_state->unassigned_channels & vc4_crtc->data->hvs_available_channels;  		if (matching_channels) {  			unsigned int channel = ffs(matching_channels) - 1;  			new_vc4_crtc_state->assigned_channel = channel; -			unassigned_channels &= ~BIT(channel); +			hvs_new_state->unassigned_channels &= ~BIT(channel);  		} else {  			return -EINVAL;  		}  	} +	return 0; +} + +static int +vc4_atomic_check(struct drm_device *dev, struct drm_atomic_state *state) +{ +	int ret; + +	ret = vc4_pv_muxing_atomic_check(dev, state); +	if (ret) +		return ret; +  	ret = vc4_ctm_atomic_check(dev, state);  	if (ret < 0)  		return ret; @@ -711,8 +876,6 @@ static const struct drm_mode_config_funcs vc4_mode_funcs = {  int vc4_kms_load(struct drm_device *dev)  {  	struct vc4_dev *vc4 = to_vc4_dev(dev); -	struct vc4_ctm_state *ctm_state; -	struct vc4_load_tracker_state *load_state;  	bool is_vc5 = of_device_is_compatible(dev->dev->of_node,  					      "brcm,bcm2711-vc5");  	int ret; @@ -751,26 +914,17 @@ int vc4_kms_load(struct drm_device *dev)  	dev->mode_config.async_page_flip = true;  	dev->mode_config.allow_fb_modifiers = true; -	drm_modeset_lock_init(&vc4->ctm_state_lock); - -	ctm_state = kzalloc(sizeof(*ctm_state), GFP_KERNEL); -	if (!ctm_state) -		return -ENOMEM; - -	drm_atomic_private_obj_init(dev, &vc4->ctm_manager, &ctm_state->base, -				    &vc4_ctm_state_funcs); +	ret = vc4_ctm_obj_init(vc4); +	if (ret) +		return ret; -	if (vc4->load_tracker_available) { -		load_state = kzalloc(sizeof(*load_state), GFP_KERNEL); -		if (!load_state) { -			drm_atomic_private_obj_fini(&vc4->ctm_manager); -			return -ENOMEM; -		} +	ret = vc4_load_tracker_obj_init(vc4); +	if (ret) +		return ret; -		drm_atomic_private_obj_init(dev, &vc4->load_tracker, -					    &load_state->base, -					    &vc4_load_tracker_state_funcs); -	} +	ret = vc4_hvs_channels_obj_init(vc4); +	if (ret) +		return ret;  	drm_mode_config_reset(dev); diff --git a/drivers/gpu/drm/vc4/vc4_v3d.c b/drivers/gpu/drm/vc4/vc4_v3d.c index f7ab979721b3..65d0dac69b0b 100644 --- a/drivers/gpu/drm/vc4/vc4_v3d.c +++ b/drivers/gpu/drm/vc4/vc4_v3d.c @@ -168,7 +168,7 @@ static void vc4_v3d_init_hw(struct drm_device *dev)  int vc4_v3d_get_bin_slot(struct vc4_dev *vc4)  { -	struct drm_device *dev = vc4->dev; +	struct drm_device *dev = &vc4->base;  	unsigned long irqflags;  	int slot;  	uint64_t seqno = 0; @@ -246,7 +246,7 @@ static int bin_bo_alloc(struct vc4_dev *vc4)  	INIT_LIST_HEAD(&list);  	while (true) { -		struct vc4_bo *bo = vc4_bo_create(vc4->dev, size, true, +		struct vc4_bo *bo = vc4_bo_create(&vc4->base, size, true,  						  VC4_BO_TYPE_BIN);  		if (IS_ERR(bo)) { @@ -361,7 +361,7 @@ static int vc4_v3d_runtime_suspend(struct device *dev)  	struct vc4_v3d *v3d = dev_get_drvdata(dev);  	struct vc4_dev *vc4 = v3d->vc4; -	vc4_irq_uninstall(vc4->dev); +	vc4_irq_uninstall(&vc4->base);  	clk_disable_unprepare(v3d->clk); @@ -378,11 +378,11 @@ static int vc4_v3d_runtime_resume(struct device *dev)  	if (ret != 0)  		return ret; -	vc4_v3d_init_hw(vc4->dev); +	vc4_v3d_init_hw(&vc4->base);  	/* We disabled the IRQ as part of vc4_irq_uninstall in suspend. */ -	enable_irq(vc4->dev->irq); -	vc4_irq_postinstall(vc4->dev); +	enable_irq(vc4->base.irq); +	vc4_irq_postinstall(&vc4->base);  	return 0;  } | 
