diff options
Diffstat (limited to 'drivers/gpu/drm/vc4/vc4_kms.c')
| -rw-r--r-- | drivers/gpu/drm/vc4/vc4_kms.c | 318 | 
1 files changed, 236 insertions, 82 deletions
| 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); | 
