| From 9efecb2ccd14a6d226ba2afa04f6e70b96026b3e Mon Sep 17 00:00:00 2001 |
| From: Maxime Ripard <maxime@cerno.tech> |
| Date: Thu, 26 Dec 2019 17:53:18 +0100 |
| Subject: [PATCH] drm/vc4: crtc: Assign output to channel automatically |
| |
| The HVS found in the BCM2711 has 6 outputs and 3 FIFOs, with each output |
| being connected to a pixelvalve, and some muxing between the FIFOs and |
| outputs. |
| |
| Any output cannot feed from any FIFO though, and they all have a bunch of |
| constraints. |
| |
| In order to support this, let's store the possible FIFOs each output can be |
| assigned to in the vc4_crtc_data, and use that information at atomic_check |
| time to iterate over all the CRTCs enabled and assign them FIFOs. |
| |
| The channel assigned is then set in the vc4_crtc_state so that the rest of |
| the driver can use it. |
| |
| Signed-off-by: Maxime Ripard <maxime@cerno.tech> |
| --- |
| drivers/gpu/drm/vc4/vc4_crtc.c | 37 +++++---- |
| drivers/gpu/drm/vc4/vc4_drv.h | 7 +- |
| drivers/gpu/drm/vc4/vc4_kms.c | 146 +++++++++++++++++++++++++++++++-- |
| drivers/gpu/drm/vc4/vc4_regs.h | 10 +++ |
| 4 files changed, 175 insertions(+), 25 deletions(-) |
| |
| --- a/drivers/gpu/drm/vc4/vc4_crtc.c |
| +++ b/drivers/gpu/drm/vc4/vc4_crtc.c |
| @@ -90,6 +90,7 @@ bool vc4_crtc_get_scanoutpos(struct drm_ |
| struct vc4_dev *vc4 = to_vc4_dev(dev); |
| struct drm_crtc *crtc = drm_crtc_from_index(dev, crtc_id); |
| struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc); |
| + struct vc4_crtc_state *vc4_crtc_state = to_vc4_crtc_state(crtc->state); |
| unsigned int cob_size; |
| u32 val; |
| int fifo_lines; |
| @@ -106,7 +107,7 @@ bool vc4_crtc_get_scanoutpos(struct drm_ |
| * Read vertical scanline which is currently composed for our |
| * pixelvalve by the HVS, and also the scaler status. |
| */ |
| - val = HVS_READ(SCALER_DISPSTATX(vc4_crtc->channel)); |
| + val = HVS_READ(SCALER_DISPSTATX(vc4_crtc_state->assigned_channel)); |
| |
| /* Get optional system timestamp after query. */ |
| if (etime) |
| @@ -126,7 +127,7 @@ bool vc4_crtc_get_scanoutpos(struct drm_ |
| *hpos += mode->crtc_htotal / 2; |
| } |
| |
| - cob_size = vc4_crtc_get_cob_allocation(vc4_crtc, vc4_crtc->channel); |
| + cob_size = vc4_crtc_get_cob_allocation(vc4_crtc, vc4_crtc_state->assigned_channel); |
| /* This is the offset we need for translating hvs -> pv scanout pos. */ |
| fifo_lines = cob_size / mode->crtc_hdisplay; |
| |
| @@ -213,6 +214,7 @@ vc4_crtc_lut_load(struct drm_crtc *crtc) |
| struct drm_device *dev = crtc->dev; |
| struct vc4_dev *vc4 = to_vc4_dev(dev); |
| struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc); |
| + struct vc4_crtc_state *vc4_crtc_state = to_vc4_crtc_state(crtc->state); |
| u32 i; |
| |
| /* The LUT memory is laid out with each HVS channel in order, |
| @@ -221,7 +223,7 @@ vc4_crtc_lut_load(struct drm_crtc *crtc) |
| */ |
| HVS_WRITE(SCALER_GAMADDR, |
| SCALER_GAMADDR_AUTOINC | |
| - (vc4_crtc->channel * 3 * crtc->gamma_size)); |
| + (vc4_crtc_state->assigned_channel * 3 * crtc->gamma_size)); |
| |
| for (i = 0; i < crtc->gamma_size; i++) |
| HVS_WRITE(SCALER_GAMDATA, vc4_crtc->lut_r[i]); |
| @@ -394,7 +396,7 @@ static void vc4_crtc_mode_set_nofb(struc |
| drm_print_regset32(&p, &vc4_crtc->regset); |
| } |
| |
| - if (vc4_crtc->channel == 2) { |
| + if (vc4_crtc->data->hvs_output == 2) { |
| u32 dispctrl; |
| u32 dsp3_mux; |
| |
| @@ -421,7 +423,7 @@ static void vc4_crtc_mode_set_nofb(struc |
| if (!vc4_state->feed_txp) |
| vc4_crtc_config_pv(crtc); |
| |
| - HVS_WRITE(SCALER_DISPBKGNDX(vc4_crtc->channel), |
| + HVS_WRITE(SCALER_DISPBKGNDX(vc4_state->assigned_channel), |
| SCALER_DISPBKGND_AUTOHS | |
| SCALER_DISPBKGND_GAMMA | |
| (interlace ? SCALER_DISPBKGND_INTERLACE : 0)); |
| @@ -453,7 +455,8 @@ static void vc4_crtc_atomic_disable(stru |
| struct drm_device *dev = crtc->dev; |
| struct vc4_dev *vc4 = to_vc4_dev(dev); |
| struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc); |
| - u32 chan = vc4_crtc->channel; |
| + struct vc4_crtc_state *vc4_crtc_state = to_vc4_crtc_state(old_state); |
| + u32 chan = vc4_crtc_state->assigned_channel; |
| int ret; |
| require_hvs_enabled(dev); |
| |
| @@ -532,12 +535,12 @@ static void vc4_crtc_update_dlist(struct |
| crtc->state->event = NULL; |
| } |
| |
| - HVS_WRITE(SCALER_DISPLISTX(vc4_crtc->channel), |
| + HVS_WRITE(SCALER_DISPLISTX(vc4_state->assigned_channel), |
| vc4_state->mm.start); |
| |
| spin_unlock_irqrestore(&dev->event_lock, flags); |
| } else { |
| - HVS_WRITE(SCALER_DISPLISTX(vc4_crtc->channel), |
| + HVS_WRITE(SCALER_DISPLISTX(vc4_state->assigned_channel), |
| vc4_state->mm.start); |
| } |
| } |
| @@ -586,7 +589,7 @@ static void vc4_crtc_atomic_enable(struc |
| (vc4_state->feed_txp ? |
| SCALER5_DISPCTRLX_ONESHOT : 0); |
| |
| - HVS_WRITE(SCALER_DISPCTRLX(vc4_crtc->channel), dispctrl); |
| + HVS_WRITE(SCALER_DISPCTRLX(vc4_state->assigned_channel), dispctrl); |
| |
| /* When feeding the transposer block the pixelvalve is unneeded and |
| * should not be enabled. |
| @@ -702,7 +705,6 @@ static void vc4_crtc_atomic_flush(struct |
| { |
| struct drm_device *dev = crtc->dev; |
| struct vc4_dev *vc4 = to_vc4_dev(dev); |
| - struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc); |
| struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc->state); |
| struct drm_plane *plane; |
| struct vc4_plane_state *vc4_plane_state; |
| @@ -744,8 +746,8 @@ static void vc4_crtc_atomic_flush(struct |
| /* This sets a black background color fill, as is the case |
| * with other DRM drivers. |
| */ |
| - HVS_WRITE(SCALER_DISPBKGNDX(vc4_crtc->channel), |
| - HVS_READ(SCALER_DISPBKGNDX(vc4_crtc->channel)) | |
| + HVS_WRITE(SCALER_DISPBKGNDX(vc4_state->assigned_channel), |
| + HVS_READ(SCALER_DISPBKGNDX(vc4_state->assigned_channel)) | |
| SCALER_DISPBKGND_FILL); |
| |
| /* Only update DISPLIST if the CRTC was already running and is not |
| @@ -759,7 +761,7 @@ static void vc4_crtc_atomic_flush(struct |
| vc4_crtc_update_dlist(crtc); |
| |
| if (crtc->state->color_mgmt_changed) { |
| - u32 dispbkgndx = HVS_READ(SCALER_DISPBKGNDX(vc4_crtc->channel)); |
| + u32 dispbkgndx = HVS_READ(SCALER_DISPBKGNDX(vc4_state->assigned_channel)); |
| |
| if (crtc->state->gamma_lut) { |
| vc4_crtc_update_gamma_lut(crtc); |
| @@ -771,7 +773,7 @@ static void vc4_crtc_atomic_flush(struct |
| */ |
| dispbkgndx &= ~SCALER_DISPBKGND_GAMMA; |
| } |
| - HVS_WRITE(SCALER_DISPBKGNDX(vc4_crtc->channel), dispbkgndx); |
| + HVS_WRITE(SCALER_DISPBKGNDX(vc4_state->assigned_channel), dispbkgndx); |
| } |
| |
| if (debug_dump_regs) { |
| @@ -802,7 +804,7 @@ static void vc4_crtc_handle_page_flip(st |
| struct drm_device *dev = crtc->dev; |
| struct vc4_dev *vc4 = to_vc4_dev(dev); |
| struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc->state); |
| - u32 chan = vc4_crtc->channel; |
| + u32 chan = vc4_state->assigned_channel; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&dev->event_lock, flags); |
| @@ -1002,6 +1004,7 @@ static struct drm_crtc_state *vc4_crtc_d |
| old_vc4_state = to_vc4_crtc_state(crtc->state); |
| vc4_state->feed_txp = old_vc4_state->feed_txp; |
| vc4_state->margins = old_vc4_state->margins; |
| + vc4_state->assigned_channel = old_vc4_state->assigned_channel; |
| |
| __drm_atomic_helper_crtc_duplicate_state(crtc, &vc4_state->base); |
| return &vc4_state->base; |
| @@ -1061,6 +1064,7 @@ static const struct drm_crtc_helper_func |
| }; |
| |
| static const struct vc4_crtc_data bcm2835_pv0_data = { |
| + .hvs_available_channels = BIT(0), |
| .hvs_output = 0, |
| .debugfs_name = "crtc0_regs", |
| .pixels_per_clock = 1, |
| @@ -1071,6 +1075,7 @@ static const struct vc4_crtc_data bcm283 |
| }; |
| |
| static const struct vc4_crtc_data bcm2835_pv1_data = { |
| + .hvs_available_channels = BIT(2), |
| .hvs_output = 2, |
| .debugfs_name = "crtc1_regs", |
| .pixels_per_clock = 1, |
| @@ -1081,6 +1086,7 @@ static const struct vc4_crtc_data bcm283 |
| }; |
| |
| static const struct vc4_crtc_data bcm2835_pv2_data = { |
| + .hvs_available_channels = BIT(1), |
| .hvs_output = 1, |
| .debugfs_name = "crtc2_regs", |
| .pixels_per_clock = 1, |
| @@ -1172,7 +1178,6 @@ static int vc4_crtc_bind(struct device * |
| drm_crtc_init_with_planes(drm, crtc, primary_plane, NULL, |
| &vc4_crtc_funcs, NULL); |
| drm_crtc_helper_add(crtc, &vc4_crtc_helper_funcs); |
| - vc4_crtc->channel = vc4_crtc->data->hvs_output; |
| drm_mode_crtc_set_gamma_size(crtc, ARRAY_SIZE(vc4_crtc->lut_r)); |
| drm_crtc_enable_color_mgmt(crtc, 0, false, crtc->gamma_size); |
| |
| --- a/drivers/gpu/drm/vc4/vc4_drv.h |
| +++ b/drivers/gpu/drm/vc4/vc4_drv.h |
| @@ -452,6 +452,9 @@ to_vc4_encoder(struct drm_encoder *encod |
| } |
| |
| struct vc4_crtc_data { |
| + /* Which channels of the HVS can the output source from */ |
| + unsigned int hvs_available_channels; |
| + |
| /* Which output of the HVS this pixelvalve sources from. */ |
| int hvs_output; |
| |
| @@ -471,9 +474,6 @@ struct vc4_crtc { |
| /* Timestamp at start of vblank irq - unaffected by lock delays. */ |
| ktime_t t_vblank; |
| |
| - /* Which HVS channel we're using for our CRTC. */ |
| - int channel; |
| - |
| u8 lut_r[256]; |
| u8 lut_g[256]; |
| u8 lut_b[256]; |
| @@ -495,6 +495,7 @@ struct vc4_crtc_state { |
| struct drm_mm_node mm; |
| bool feed_txp; |
| bool txp_armed; |
| + unsigned int assigned_channel; |
| |
| struct { |
| unsigned int left; |
| --- a/drivers/gpu/drm/vc4/vc4_kms.c |
| +++ b/drivers/gpu/drm/vc4/vc4_kms.c |
| @@ -11,6 +11,9 @@ |
| * crtc, HDMI encoder). |
| */ |
| |
| +#include <linux/bitfield.h> |
| +#include <linux/bitops.h> |
| + |
| #include <drm/drm_atomic.h> |
| #include <drm/drm_atomic_helper.h> |
| #include <drm/drm_crtc.h> |
| @@ -148,6 +151,72 @@ vc4_ctm_commit(struct vc4_dev *vc4, stru |
| VC4_SET_FIELD(ctm_state->fifo, SCALER_OLEDOFFS_DISPFIFO)); |
| } |
| |
| +static void vc4_hvs_pv_muxing_commit(struct vc4_dev *vc4, |
| + struct drm_atomic_state *state) |
| +{ |
| + 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 int i; |
| + u32 reg; |
| + |
| + for_each_new_crtc_in_state(state, crtc, crtc_state, i) { |
| + 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) |
| + continue; |
| + |
| + switch (vc4_crtc->data->hvs_output) { |
| + case 2: |
| + dsp2_mux = (vc4_state->assigned_channel == 2) ? 1 : 0; |
| + break; |
| + |
| + case 3: |
| + dsp3_mux = vc4_state->assigned_channel; |
| + break; |
| + |
| + case 4: |
| + dsp4_mux = vc4_state->assigned_channel; |
| + break; |
| + |
| + case 5: |
| + dsp5_mux = vc4_state->assigned_channel; |
| + break; |
| + |
| + default: |
| + break; |
| + } |
| + } |
| + |
| + reg = HVS_READ(SCALER_DISPECTRL); |
| + if (FIELD_GET(SCALER_DISPECTRL_DSP2_MUX_MASK, reg) != dsp2_mux) |
| + HVS_WRITE(SCALER_DISPECTRL, |
| + (reg & ~SCALER_DISPECTRL_DSP2_MUX_MASK) | |
| + VC4_SET_FIELD(dsp2_mux, SCALER_DISPECTRL_DSP2_MUX)); |
| + |
| + reg = HVS_READ(SCALER_DISPCTRL); |
| + if (FIELD_GET(SCALER_DISPCTRL_DSP3_MUX_MASK, reg) != dsp3_mux) |
| + HVS_WRITE(SCALER_DISPCTRL, |
| + (reg & ~SCALER_DISPCTRL_DSP3_MUX_MASK) | |
| + VC4_SET_FIELD(dsp3_mux, SCALER_DISPCTRL_DSP3_MUX)); |
| + |
| + reg = HVS_READ(SCALER_DISPEOLN); |
| + if (FIELD_GET(SCALER_DISPEOLN_DSP4_MUX_MASK, reg) != dsp4_mux) |
| + HVS_WRITE(SCALER_DISPEOLN, |
| + (reg & ~SCALER_DISPEOLN_DSP4_MUX_MASK) | |
| + VC4_SET_FIELD(dsp4_mux, SCALER_DISPEOLN_DSP4_MUX)); |
| + |
| + reg = HVS_READ(SCALER_DISPDITHER); |
| + if (FIELD_GET(SCALER_DISPDITHER_DSP5_MUX_MASK, reg) != dsp5_mux) |
| + HVS_WRITE(SCALER_DISPDITHER, |
| + (reg & ~SCALER_DISPDITHER_DSP5_MUX_MASK) | |
| + VC4_SET_FIELD(dsp5_mux, SCALER_DISPDITHER_DSP5_MUX)); |
| +} |
| + |
| static void |
| vc4_atomic_complete_commit(struct drm_atomic_state *state) |
| { |
| @@ -157,11 +226,15 @@ vc4_atomic_complete_commit(struct drm_at |
| int i; |
| |
| for (i = 0; vc4->hvs && i < dev->mode_config.num_crtc; i++) { |
| - if (!state->crtcs[i].ptr || !state->crtcs[i].commit) |
| + struct __drm_crtcs_state *_state = &state->crtcs[i]; |
| + struct vc4_crtc_state *vc4_crtc_state; |
| + |
| + if (!_state->ptr || !_state->commit) |
| continue; |
| |
| - vc4_crtc = to_vc4_crtc(state->crtcs[i].ptr); |
| - vc4_hvs_mask_underrun(dev, vc4_crtc->channel); |
| + vc4_crtc = to_vc4_crtc(_state->ptr); |
| + vc4_crtc_state = to_vc4_crtc_state(_state->state); |
| + vc4_hvs_mask_underrun(dev, vc4_crtc_state->assigned_channel); |
| } |
| |
| drm_atomic_helper_wait_for_fences(dev, state, false); |
| @@ -170,8 +243,10 @@ vc4_atomic_complete_commit(struct drm_at |
| |
| drm_atomic_helper_commit_modeset_disables(dev, state); |
| |
| - if (!vc4->firmware_kms) |
| + if (!vc4->firmware_kms) { |
| vc4_ctm_commit(vc4, state); |
| + vc4_hvs_pv_muxing_commit(vc4, state); |
| + } |
| |
| drm_atomic_helper_commit_planes(dev, state, 0); |
| |
| @@ -380,8 +455,11 @@ vc4_ctm_atomic_check(struct drm_device * |
| |
| /* CTM is being enabled or the matrix changed. */ |
| if (new_crtc_state->ctm) { |
| + struct vc4_crtc_state *vc4_crtc_state = |
| + to_vc4_crtc_state(new_crtc_state); |
| + |
| /* fifo is 1-based since 0 disables CTM. */ |
| - int fifo = to_vc4_crtc(crtc)->channel + 1; |
| + int fifo = vc4_crtc_state->assigned_channel + 1; |
| |
| /* Check userland isn't trying to turn on CTM for more |
| * than one CRTC at a time. |
| @@ -494,10 +572,66 @@ static const struct drm_private_state_fu |
| .atomic_destroy_state = vc4_load_tracker_destroy_state, |
| }; |
| |
| +#define NUM_OUTPUTS 6 |
| +#define NUM_CHANNELS 3 |
| + |
| static int |
| vc4_atomic_check(struct drm_device *dev, struct drm_atomic_state *state) |
| { |
| - int ret; |
| + unsigned long unassigned_channels = GENMASK(NUM_CHANNELS - 1, 0); |
| + struct drm_crtc_state *crtc_state; |
| + struct drm_crtc *crtc; |
| + int i, ret; |
| + |
| + for_each_new_crtc_in_state(state, crtc, crtc_state, i) { |
| + struct vc4_crtc_state *vc4_crtc_state = |
| + to_vc4_crtc_state(crtc_state); |
| + struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc); |
| + bool is_assigned = false; |
| + unsigned int channel; |
| + |
| + if (!crtc_state->active) |
| + continue; |
| + |
| + /* |
| + * The problem we have to solve here is that we have |
| + * up to 7 encoders, connected to up to 6 CRTCs. |
| + * |
| + * Those CRTCs, depending on the instance, can be |
| + * routed to 1, 2 or 3 HVS FIFOs, and we need to set |
| + * the change the muxing between FIFOs and outputs in |
| + * the HVS accordingly. |
| + * |
| + * It would be pretty hard to come up with an |
| + * algorithm that would generically solve |
| + * this. However, the current routing trees we support |
| + * allow us to simplify a bit the problem. |
| + * |
| + * Indeed, with the current supported layouts, if we |
| + * try to assign in the ascending crtc index order the |
| + * FIFOs, we can't fall into the situation where an |
| + * earlier CRTC that had multiple routes is assigned |
| + * one that was the only option for a later CRTC. |
| + * |
| + * If the layout changes and doesn't give us that in |
| + * the future, we will need to have something smarter, |
| + * but it works so far. |
| + */ |
| + for_each_set_bit(channel, &unassigned_channels, |
| + sizeof(unassigned_channels)) { |
| + |
| + if (!(BIT(channel) & vc4_crtc->data->hvs_available_channels)) |
| + continue; |
| + |
| + vc4_crtc_state->assigned_channel = channel; |
| + unassigned_channels &= ~BIT(channel); |
| + is_assigned = true; |
| + break; |
| + } |
| + |
| + if (!is_assigned) |
| + return -EINVAL; |
| + } |
| |
| ret = vc4_ctm_atomic_check(dev, state); |
| if (ret < 0) |
| --- a/drivers/gpu/drm/vc4/vc4_regs.h |
| +++ b/drivers/gpu/drm/vc4/vc4_regs.h |
| @@ -287,9 +287,19 @@ |
| |
| #define SCALER_DISPID 0x00000008 |
| #define SCALER_DISPECTRL 0x0000000c |
| +# define SCALER_DISPECTRL_DSP2_MUX_SHIFT 31 |
| +# define SCALER_DISPECTRL_DSP2_MUX_MASK VC4_MASK(31, 31) |
| + |
| #define SCALER_DISPPROF 0x00000010 |
| + |
| #define SCALER_DISPDITHER 0x00000014 |
| +# define SCALER_DISPDITHER_DSP5_MUX_SHIFT 30 |
| +# define SCALER_DISPDITHER_DSP5_MUX_MASK VC4_MASK(31, 30) |
| + |
| #define SCALER_DISPEOLN 0x00000018 |
| +# define SCALER_DISPEOLN_DSP4_MUX_SHIFT 30 |
| +# define SCALER_DISPEOLN_DSP4_MUX_MASK VC4_MASK(31, 30) |
| + |
| #define SCALER_DISPLIST0 0x00000020 |
| #define SCALER_DISPLIST1 0x00000024 |
| #define SCALER_DISPLIST2 0x00000028 |