diff options
author | Dave Airlie <airlied@redhat.com> | 2020-09-23 09:49:48 +1000 |
---|---|---|
committer | Dave Airlie <airlied@redhat.com> | 2020-09-23 09:52:24 +1000 |
commit | 6ea6be77086f23d4b346c9946dae24593befda2e (patch) | |
tree | dc3926a543ed2b8270aa111d97ee603970560eda /drivers/gpu/drm/vc4 | |
parent | fc88fef916e8971eefeacc62241b7408b7e7939d (diff) | |
parent | 089d83418914abd4d908db117d9a3eca7f51a68c (diff) | |
download | linux-6ea6be77086f23d4b346c9946dae24593befda2e.tar.bz2 |
Merge tag 'drm-misc-next-2020-09-21' of git://anongit.freedesktop.org/drm/drm-misc into drm-next
drm-misc-next for 5.10:
UAPI Changes:
Cross-subsystem Changes:
- virtio: Merged a PR for patches that will affect drm/virtio
Core Changes:
- dev: More devm_drm convertions and removal of drm_dev_init
- atomic: Split out drm_atomic_helper_calc_timestamping_constants of
drm_atomic_helper_update_legacy_modeset_state
- ttm: More rework
Driver Changes:
- i915: selftests improvements
- panfrost: support for Amlogic SoC
- vc4: one fix
- tree-wide: conversions to devm_drm_dev_alloc,
- ast: simplifications of the atomic modesetting code
- panfrost: multiple fixes
- vc4: multiple fixes
Signed-off-by: Dave Airlie <airlied@redhat.com>
From: Maxime Ripard <maxime@cerno.tech>
Link: https://patchwork.freedesktop.org/patch/msgid/20200921152956.2gxnsdgxmwhvjyut@gilmour.lan
Diffstat (limited to 'drivers/gpu/drm/vc4')
-rw-r--r-- | drivers/gpu/drm/vc4/Makefile | 1 | ||||
-rw-r--r-- | drivers/gpu/drm/vc4/vc4_crtc.c | 354 | ||||
-rw-r--r-- | drivers/gpu/drm/vc4/vc4_drv.c | 5 | ||||
-rw-r--r-- | drivers/gpu/drm/vc4/vc4_drv.h | 43 | ||||
-rw-r--r-- | drivers/gpu/drm/vc4/vc4_hdmi.c | 1650 | ||||
-rw-r--r-- | drivers/gpu/drm/vc4/vc4_hdmi.h | 184 | ||||
-rw-r--r-- | drivers/gpu/drm/vc4/vc4_hdmi_phy.c | 521 | ||||
-rw-r--r-- | drivers/gpu/drm/vc4/vc4_hdmi_regs.h | 442 | ||||
-rw-r--r-- | drivers/gpu/drm/vc4/vc4_hvs.c | 269 | ||||
-rw-r--r-- | drivers/gpu/drm/vc4/vc4_kms.c | 246 | ||||
-rw-r--r-- | drivers/gpu/drm/vc4/vc4_plane.c | 224 | ||||
-rw-r--r-- | drivers/gpu/drm/vc4/vc4_regs.h | 177 | ||||
-rw-r--r-- | drivers/gpu/drm/vc4/vc4_txp.c | 4 |
13 files changed, 3117 insertions, 1003 deletions
diff --git a/drivers/gpu/drm/vc4/Makefile b/drivers/gpu/drm/vc4/Makefile index b303703bc7f3..d0163e18e9ca 100644 --- a/drivers/gpu/drm/vc4/Makefile +++ b/drivers/gpu/drm/vc4/Makefile @@ -12,6 +12,7 @@ vc4-y := \ vc4_kms.o \ vc4_gem.o \ vc4_hdmi.o \ + vc4_hdmi_phy.o \ vc4_vec.o \ vc4_hvs.o \ vc4_irq.o \ diff --git a/drivers/gpu/drm/vc4/vc4_crtc.c b/drivers/gpu/drm/vc4/vc4_crtc.c index 6d8fa6118fc1..a393f93390a2 100644 --- a/drivers/gpu/drm/vc4/vc4_crtc.c +++ b/drivers/gpu/drm/vc4/vc4_crtc.c @@ -65,6 +65,20 @@ static const struct debugfs_reg32 crtc_regs[] = { VC4_REG32(PV_HACT_ACT), }; +static unsigned int +vc4_crtc_get_cob_allocation(struct vc4_dev *vc4, unsigned int channel) +{ + u32 dispbase = HVS_READ(SCALER_DISPBASEX(channel)); + /* Top/base are supposed to be 4-pixel aligned, but the + * Raspberry Pi firmware fills the low bits (which are + * presumably ignored). + */ + u32 top = VC4_GET_FIELD(dispbase, SCALER_DISPBASEX_TOP) & ~3; + u32 base = VC4_GET_FIELD(dispbase, SCALER_DISPBASEX_BASE) & ~3; + + return top - base + 4; +} + static bool vc4_crtc_get_scanout_position(struct drm_crtc *crtc, bool in_vblank_irq, int *vpos, int *hpos, @@ -74,6 +88,8 @@ static bool vc4_crtc_get_scanout_position(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); + unsigned int cob_size; u32 val; int fifo_lines; int vblank_lines; @@ -89,7 +105,7 @@ static bool vc4_crtc_get_scanout_position(struct drm_crtc *crtc, * 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) @@ -109,8 +125,9 @@ static bool vc4_crtc_get_scanout_position(struct drm_crtc *crtc, *hpos += mode->crtc_htotal / 2; } + cob_size = vc4_crtc_get_cob_allocation(vc4, vc4_crtc_state->assigned_channel); /* This is the offset we need for translating hvs -> pv scanout pos. */ - fifo_lines = vc4_crtc->cob_size / mode->crtc_hdisplay; + fifo_lines = cob_size / mode->crtc_hdisplay; if (fifo_lines > 0) ret = true; @@ -189,10 +206,22 @@ void vc4_crtc_destroy(struct drm_crtc *crtc) drm_crtc_cleanup(crtc); } -static u32 vc4_get_fifo_full_level(u32 format) +static u32 vc4_get_fifo_full_level(struct vc4_crtc *vc4_crtc, u32 format) { - static const u32 fifo_len_bytes = 64; + const struct vc4_crtc_data *crtc_data = vc4_crtc_to_vc4_crtc_data(vc4_crtc); + const struct vc4_pv_data *pv_data = vc4_crtc_to_vc4_pv_data(vc4_crtc); + u32 fifo_len_bytes = pv_data->fifo_depth; + /* + * Pixels are pulled from the HVS if the number of bytes is + * lower than the FIFO full level. + * + * The latency of the pixel fetch mechanism is 6 pixels, so we + * need to convert those 6 pixels in bytes, depending on the + * format, and then subtract that from the length of the FIFO + * to make sure we never end up in a situation where the FIFO + * is full. + */ switch (format) { case PV_CONTROL_FORMAT_DSIV_16: case PV_CONTROL_FORMAT_DSIC_16: @@ -202,10 +231,30 @@ static u32 vc4_get_fifo_full_level(u32 format) case PV_CONTROL_FORMAT_24: case PV_CONTROL_FORMAT_DSIV_24: default: + /* + * For some reason, the pixelvalve4 doesn't work with + * the usual formula and will only work with 32. + */ + if (crtc_data->hvs_output == 5) + return 32; + return fifo_len_bytes - 3 * HVS_FIFO_LATENCY_PIX; } } +static u32 vc4_crtc_get_fifo_full_level_bits(struct vc4_crtc *vc4_crtc, + u32 format) +{ + u32 level = vc4_get_fifo_full_level(vc4_crtc, format); + u32 ret = 0; + + ret |= VC4_SET_FIELD((level >> 6), + PV5_CONTROL_FIFO_LEVEL_HIGH); + + return ret | VC4_SET_FIELD(level & 0x3f, + PV_CONTROL_FIFO_LEVEL); +} + /* * Returns the encoder attached to the CRTC. * @@ -230,11 +279,23 @@ static struct drm_encoder *vc4_get_crtc_encoder(struct drm_crtc *crtc) return NULL; } +static void vc4_crtc_pixelvalve_reset(struct drm_crtc *crtc) +{ + struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc); + + /* The PV needs to be disabled before it can be flushed */ + CRTC_WRITE(PV_CONTROL, CRTC_READ(PV_CONTROL) & ~PV_CONTROL_EN); + CRTC_WRITE(PV_CONTROL, CRTC_READ(PV_CONTROL) | PV_CONTROL_FIFO_CLR); +} + static void vc4_crtc_config_pv(struct drm_crtc *crtc) { + struct drm_device *dev = crtc->dev; + struct vc4_dev *vc4 = to_vc4_dev(dev); struct drm_encoder *encoder = vc4_get_crtc_encoder(crtc); struct vc4_encoder *vc4_encoder = to_vc4_encoder(encoder); struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc); + const struct vc4_pv_data *pv_data = vc4_crtc_to_vc4_pv_data(vc4_crtc); struct drm_crtc_state *state = crtc->state; struct drm_display_mode *mode = &state->adjusted_mode; bool interlace = mode->flags & DRM_MODE_FLAG_INTERLACE; @@ -242,24 +303,29 @@ static void vc4_crtc_config_pv(struct drm_crtc *crtc) bool is_dsi = (vc4_encoder->type == VC4_ENCODER_TYPE_DSI0 || vc4_encoder->type == VC4_ENCODER_TYPE_DSI1); u32 format = is_dsi ? PV_CONTROL_FORMAT_DSIV_24 : PV_CONTROL_FORMAT_24; + u8 ppc = pv_data->pixels_per_clock; + bool debug_dump_regs = false; - /* Reset the PV fifo. */ - CRTC_WRITE(PV_CONTROL, 0); - CRTC_WRITE(PV_CONTROL, PV_CONTROL_FIFO_CLR | PV_CONTROL_EN); - CRTC_WRITE(PV_CONTROL, 0); + if (debug_dump_regs) { + struct drm_printer p = drm_info_printer(&vc4_crtc->pdev->dev); + dev_info(&vc4_crtc->pdev->dev, "CRTC %d regs before:\n", + drm_crtc_index(crtc)); + drm_print_regset32(&p, &vc4_crtc->regset); + } + + vc4_crtc_pixelvalve_reset(crtc); CRTC_WRITE(PV_HORZA, - VC4_SET_FIELD((mode->htotal - - mode->hsync_end) * pixel_rep, + VC4_SET_FIELD((mode->htotal - mode->hsync_end) * pixel_rep / ppc, PV_HORZA_HBP) | - VC4_SET_FIELD((mode->hsync_end - - mode->hsync_start) * pixel_rep, + VC4_SET_FIELD((mode->hsync_end - mode->hsync_start) * pixel_rep / ppc, PV_HORZA_HSYNC)); + CRTC_WRITE(PV_HORZB, - VC4_SET_FIELD((mode->hsync_start - - mode->hdisplay) * pixel_rep, + VC4_SET_FIELD((mode->hsync_start - mode->hdisplay) * pixel_rep / ppc, PV_HORZB_HFP) | - VC4_SET_FIELD(mode->hdisplay * pixel_rep, PV_HORZB_HACTIVE)); + VC4_SET_FIELD(mode->hdisplay * pixel_rep / ppc, + PV_HORZB_HACTIVE)); CRTC_WRITE(PV_VERTA, VC4_SET_FIELD(mode->crtc_vtotal - mode->crtc_vsync_end, @@ -306,35 +372,20 @@ static void vc4_crtc_config_pv(struct drm_crtc *crtc) if (is_dsi) CRTC_WRITE(PV_HACT_ACT, mode->hdisplay * pixel_rep); - CRTC_WRITE(PV_CONTROL, + if (vc4->hvs->hvs5) + CRTC_WRITE(PV_MUX_CFG, + VC4_SET_FIELD(PV_MUX_CFG_RGB_PIXEL_MUX_MODE_NO_SWAP, + PV_MUX_CFG_RGB_PIXEL_MUX_MODE)); + + CRTC_WRITE(PV_CONTROL, PV_CONTROL_FIFO_CLR | + vc4_crtc_get_fifo_full_level_bits(vc4_crtc, format) | VC4_SET_FIELD(format, PV_CONTROL_FORMAT) | - VC4_SET_FIELD(vc4_get_fifo_full_level(format), - PV_CONTROL_FIFO_LEVEL) | VC4_SET_FIELD(pixel_rep - 1, PV_CONTROL_PIXEL_REP) | PV_CONTROL_CLR_AT_START | PV_CONTROL_TRIGGER_UNDERFLOW | PV_CONTROL_WAIT_HSTART | VC4_SET_FIELD(vc4_encoder->clock_select, - PV_CONTROL_CLK_SELECT) | - PV_CONTROL_FIFO_CLR | - PV_CONTROL_EN); -} - -static void vc4_crtc_mode_set_nofb(struct drm_crtc *crtc) -{ - struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc); - bool debug_dump_regs = false; - - if (debug_dump_regs) { - struct drm_printer p = drm_info_printer(&vc4_crtc->pdev->dev); - dev_info(&vc4_crtc->pdev->dev, "CRTC %d regs before:\n", - drm_crtc_index(crtc)); - drm_print_regset32(&p, &vc4_crtc->regset); - } - - vc4_crtc_config_pv(crtc); - - vc4_hvs_mode_set_nofb(crtc); + PV_CONTROL_CLK_SELECT)); if (debug_dump_regs) { struct drm_printer p = drm_info_printer(&vc4_crtc->pdev->dev); @@ -352,24 +403,86 @@ static void require_hvs_enabled(struct drm_device *dev) SCALER_DISPCTRL_ENABLE); } +static int vc4_crtc_disable(struct drm_crtc *crtc, unsigned int channel) +{ + struct drm_encoder *encoder = vc4_get_crtc_encoder(crtc); + struct vc4_encoder *vc4_encoder = to_vc4_encoder(encoder); + struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc); + struct drm_device *dev = crtc->dev; + int ret; + + CRTC_WRITE(PV_V_CONTROL, + CRTC_READ(PV_V_CONTROL) & ~PV_VCONTROL_VIDEN); + ret = wait_for(!(CRTC_READ(PV_V_CONTROL) & PV_VCONTROL_VIDEN), 1); + WARN_ONCE(ret, "Timeout waiting for !PV_VCONTROL_VIDEN\n"); + + /* + * This delay is needed to avoid to get a pixel stuck in an + * unflushable FIFO between the pixelvalve and the HDMI + * controllers on the BCM2711. + * + * Timing is fairly sensitive here, so mdelay is the safest + * approach. + * + * If it was to be reworked, the stuck pixel happens on a + * BCM2711 when changing mode with a good probability, so a + * script that changes mode on a regular basis should trigger + * the bug after less than 10 attempts. It manifests itself with + * every pixels being shifted by one to the right, and thus the + * last pixel of a line actually being displayed as the first + * pixel on the next line. + */ + mdelay(20); + + if (vc4_encoder && vc4_encoder->post_crtc_disable) + vc4_encoder->post_crtc_disable(encoder); + + vc4_crtc_pixelvalve_reset(crtc); + vc4_hvs_stop_channel(dev, channel); + + if (vc4_encoder && vc4_encoder->post_crtc_powerdown) + vc4_encoder->post_crtc_powerdown(encoder); + + return 0; +} + +int vc4_crtc_disable_at_boot(struct drm_crtc *crtc) +{ + struct drm_device *drm = crtc->dev; + struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc); + int channel; + + if (!(of_device_is_compatible(vc4_crtc->pdev->dev.of_node, + "brcm,bcm2711-pixelvalve2") || + of_device_is_compatible(vc4_crtc->pdev->dev.of_node, + "brcm,bcm2711-pixelvalve4"))) + return 0; + + if (!(CRTC_READ(PV_CONTROL) & PV_CONTROL_EN)) + return 0; + + if (!(CRTC_READ(PV_V_CONTROL) & PV_VCONTROL_VIDEN)) + return 0; + + channel = vc4_hvs_get_fifo_from_output(drm, vc4_crtc->data->hvs_output); + if (channel < 0) + return 0; + + return vc4_crtc_disable(crtc, channel); +} + static void vc4_crtc_atomic_disable(struct drm_crtc *crtc, struct drm_crtc_state *old_state) { + struct vc4_crtc_state *old_vc4_state = to_vc4_crtc_state(old_state); struct drm_device *dev = crtc->dev; - struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc); - int ret; require_hvs_enabled(dev); /* Disable vblank irq handling before crtc is disabled. */ drm_crtc_vblank_off(crtc); - CRTC_WRITE(PV_V_CONTROL, - CRTC_READ(PV_V_CONTROL) & ~PV_VCONTROL_VIDEN); - ret = wait_for(!(CRTC_READ(PV_V_CONTROL) & PV_VCONTROL_VIDEN), 1); - WARN_ONCE(ret, "Timeout waiting for !PV_VCONTROL_VIDEN\n"); - - vc4_hvs_atomic_disable(crtc, old_state); + vc4_crtc_disable(crtc, old_vc4_state->assigned_channel); /* * Make sure we issue a vblank event after disabling the CRTC if @@ -390,6 +503,8 @@ static void vc4_crtc_atomic_enable(struct drm_crtc *crtc, { struct drm_device *dev = crtc->dev; struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc); + struct drm_encoder *encoder = vc4_get_crtc_encoder(crtc); + struct vc4_encoder *vc4_encoder = to_vc4_encoder(encoder); require_hvs_enabled(dev); @@ -400,11 +515,24 @@ static void vc4_crtc_atomic_enable(struct drm_crtc *crtc, vc4_hvs_atomic_enable(crtc, old_state); + if (vc4_encoder->pre_crtc_configure) + vc4_encoder->pre_crtc_configure(encoder); + + vc4_crtc_config_pv(crtc); + + CRTC_WRITE(PV_CONTROL, CRTC_READ(PV_CONTROL) | PV_CONTROL_EN); + + if (vc4_encoder->pre_crtc_enable) + vc4_encoder->pre_crtc_enable(encoder); + /* When feeding the transposer block the pixelvalve is unneeded and * should not be enabled. */ CRTC_WRITE(PV_V_CONTROL, CRTC_READ(PV_V_CONTROL) | PV_VCONTROL_VIDEN); + + if (vc4_encoder->post_crtc_enable) + vc4_encoder->post_crtc_enable(encoder); } static enum drm_mode_status vc4_crtc_mode_valid(struct drm_crtc *crtc, @@ -499,7 +627,7 @@ static void vc4_crtc_handle_page_flip(struct vc4_crtc *vc4_crtc) 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); @@ -516,7 +644,7 @@ static void vc4_crtc_handle_page_flip(struct vc4_crtc *vc4_crtc) * the CRTC and encoder already reconfigured, leading to * underruns. This can be seen when reconfiguring the CRTC. */ - vc4_hvs_unmask_underrun(dev, vc4_crtc->channel); + vc4_hvs_unmask_underrun(dev, chan); } spin_unlock_irqrestore(&dev->event_lock, flags); } @@ -698,6 +826,7 @@ struct drm_crtc_state *vc4_crtc_duplicate_state(struct drm_crtc *crtc) 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; @@ -747,7 +876,6 @@ static const struct drm_crtc_funcs vc4_crtc_funcs = { }; 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_hvs_atomic_flush, @@ -758,9 +886,12 @@ static const struct drm_crtc_helper_funcs vc4_crtc_helper_funcs = { static const struct vc4_pv_data bcm2835_pv0_data = { .base = { - .hvs_channel = 0, + .hvs_available_channels = BIT(0), + .hvs_output = 0, }, .debugfs_name = "crtc0_regs", + .fifo_depth = 64, + .pixels_per_clock = 1, .encoder_types = { [PV_CONTROL_CLK_SELECT_DSI] = VC4_ENCODER_TYPE_DSI0, [PV_CONTROL_CLK_SELECT_DPI_SMI_HDMI] = VC4_ENCODER_TYPE_DPI, @@ -769,9 +900,12 @@ static const struct vc4_pv_data bcm2835_pv0_data = { static const struct vc4_pv_data bcm2835_pv1_data = { .base = { - .hvs_channel = 2, + .hvs_available_channels = BIT(2), + .hvs_output = 2, }, .debugfs_name = "crtc1_regs", + .fifo_depth = 64, + .pixels_per_clock = 1, .encoder_types = { [PV_CONTROL_CLK_SELECT_DSI] = VC4_ENCODER_TYPE_DSI1, [PV_CONTROL_CLK_SELECT_DPI_SMI_HDMI] = VC4_ENCODER_TYPE_SMI, @@ -780,19 +914,94 @@ static const struct vc4_pv_data bcm2835_pv1_data = { static const struct vc4_pv_data bcm2835_pv2_data = { .base = { - .hvs_channel = 1, + .hvs_available_channels = BIT(1), + .hvs_output = 1, }, .debugfs_name = "crtc2_regs", + .fifo_depth = 64, + .pixels_per_clock = 1, .encoder_types = { - [PV_CONTROL_CLK_SELECT_DPI_SMI_HDMI] = VC4_ENCODER_TYPE_HDMI, + [PV_CONTROL_CLK_SELECT_DPI_SMI_HDMI] = VC4_ENCODER_TYPE_HDMI0, [PV_CONTROL_CLK_SELECT_VEC] = VC4_ENCODER_TYPE_VEC, }, }; +static const struct vc4_pv_data bcm2711_pv0_data = { + .base = { + .hvs_available_channels = BIT(0), + .hvs_output = 0, + }, + .debugfs_name = "crtc0_regs", + .fifo_depth = 64, + .pixels_per_clock = 1, + .encoder_types = { + [0] = VC4_ENCODER_TYPE_DSI0, + [1] = VC4_ENCODER_TYPE_DPI, + }, +}; + +static const struct vc4_pv_data bcm2711_pv1_data = { + .base = { + .hvs_available_channels = BIT(0) | BIT(1) | BIT(2), + .hvs_output = 3, + }, + .debugfs_name = "crtc1_regs", + .fifo_depth = 64, + .pixels_per_clock = 1, + .encoder_types = { + [0] = VC4_ENCODER_TYPE_DSI1, + [1] = VC4_ENCODER_TYPE_SMI, + }, +}; + +static const struct vc4_pv_data bcm2711_pv2_data = { + .base = { + .hvs_available_channels = BIT(0) | BIT(1) | BIT(2), + .hvs_output = 4, + }, + .debugfs_name = "crtc2_regs", + .fifo_depth = 256, + .pixels_per_clock = 2, + .encoder_types = { + [0] = VC4_ENCODER_TYPE_HDMI0, + }, +}; + +static const struct vc4_pv_data bcm2711_pv3_data = { + .base = { + .hvs_available_channels = BIT(1), + .hvs_output = 1, + }, + .debugfs_name = "crtc3_regs", + .fifo_depth = 64, + .pixels_per_clock = 1, + .encoder_types = { + [0] = VC4_ENCODER_TYPE_VEC, + }, +}; + +static const struct vc4_pv_data bcm2711_pv4_data = { + .base = { + .hvs_available_channels = BIT(0) | BIT(1) | BIT(2), + .hvs_output = 5, + }, + .debugfs_name = "crtc4_regs", + .fifo_depth = 64, + .pixels_per_clock = 2, + .encoder_types = { + [0] = VC4_ENCODER_TYPE_HDMI1, + }, +}; + static const struct of_device_id vc4_crtc_dt_match[] = { { .compatible = "brcm,bcm2835-pixelvalve0", .data = &bcm2835_pv0_data }, { .compatible = "brcm,bcm2835-pixelvalve1", .data = &bcm2835_pv1_data }, { .compatible = "brcm,bcm2835-pixelvalve2", .data = &bcm2835_pv2_data }, + { .compatible = "brcm,bcm2711-pixelvalve0", .data = &bcm2711_pv0_data }, + { .compatible = "brcm,bcm2711-pixelvalve1", .data = &bcm2711_pv1_data }, + { .compatible = "brcm,bcm2711-pixelvalve2", .data = &bcm2711_pv2_data }, + { .compatible = "brcm,bcm2711-pixelvalve3", .data = &bcm2711_pv3_data }, + { .compatible = "brcm,bcm2711-pixelvalve4", .data = &bcm2711_pv4_data }, {} }; @@ -819,26 +1028,11 @@ static void vc4_set_crtc_possible_masks(struct drm_device *drm, } } -static void -vc4_crtc_get_cob_allocation(struct vc4_crtc *vc4_crtc) -{ - struct drm_device *drm = vc4_crtc->base.dev; - struct vc4_dev *vc4 = to_vc4_dev(drm); - u32 dispbase = HVS_READ(SCALER_DISPBASEX(vc4_crtc->channel)); - /* Top/base are supposed to be 4-pixel aligned, but the - * Raspberry Pi firmware fills the low bits (which are - * presumably ignored). - */ - u32 top = VC4_GET_FIELD(dispbase, SCALER_DISPBASEX_TOP) & ~3; - u32 base = VC4_GET_FIELD(dispbase, SCALER_DISPBASEX_BASE) & ~3; - - vc4_crtc->cob_size = top - base + 4; -} - int vc4_crtc_init(struct drm_device *drm, struct vc4_crtc *vc4_crtc, const struct drm_crtc_funcs *crtc_funcs, const struct drm_crtc_helper_funcs *crtc_helper_funcs) { + struct vc4_dev *vc4 = to_vc4_dev(drm); struct drm_crtc *crtc = &vc4_crtc->base; struct drm_plane *primary_plane; unsigned int i; @@ -858,15 +1052,17 @@ int vc4_crtc_init(struct drm_device *drm, struct vc4_crtc *vc4_crtc, drm_crtc_init_with_planes(drm, crtc, primary_plane, NULL, crtc_funcs, NULL); drm_crtc_helper_add(crtc, crtc_helper_funcs); - vc4_crtc->channel = vc4_crtc->data->hvs_channel; - drm_mode_crtc_set_gamma_size(crtc, ARRAY_SIZE(vc4_crtc->lut_r)); - drm_crtc_enable_color_mgmt(crtc, 0, false, crtc->gamma_size); - /* We support CTM, but only for one CRTC at a time. It's therefore - * implemented as private driver state in vc4_kms, not here. - */ - drm_crtc_enable_color_mgmt(crtc, 0, true, crtc->gamma_size); - vc4_crtc_get_cob_allocation(vc4_crtc); + if (!vc4->hvs->hvs5) { + drm_mode_crtc_set_gamma_size(crtc, ARRAY_SIZE(vc4_crtc->lut_r)); + + drm_crtc_enable_color_mgmt(crtc, 0, false, crtc->gamma_size); + + /* We support CTM, but only for one CRTC at a time. It's therefore + * implemented as private driver state in vc4_kms, not here. + */ + drm_crtc_enable_color_mgmt(crtc, 0, true, crtc->gamma_size); + } for (i = 0; i < crtc->gamma_size; i++) { vc4_crtc->lut_r[i] = i; @@ -915,7 +1111,9 @@ static int vc4_crtc_bind(struct device *dev, struct device *master, void *data) CRTC_WRITE(PV_INTEN, 0); CRTC_WRITE(PV_INTSTAT, PV_INT_VFP_START); ret = devm_request_irq(dev, platform_get_irq(pdev, 0), - vc4_crtc_irq_handler, 0, "vc4 crtc", vc4_crtc); + vc4_crtc_irq_handler, + IRQF_SHARED, + "vc4 crtc", vc4_crtc); if (ret) goto err_destroy_planes; diff --git a/drivers/gpu/drm/vc4/vc4_drv.c b/drivers/gpu/drm/vc4/vc4_drv.c index 38343d2fb4fb..f1a5fd5dab6f 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.c +++ b/drivers/gpu/drm/vc4/vc4_drv.c @@ -252,6 +252,7 @@ static int vc4_drm_bind(struct device *dev) struct drm_device *drm; struct vc4_dev *vc4; struct device_node *node; + struct drm_crtc *crtc; int ret = 0; dev->coherent_dma_mask = DMA_BIT_MASK(32); @@ -298,6 +299,9 @@ static int vc4_drm_bind(struct device *dev) if (ret < 0) goto unbind_all; + drm_for_each_crtc(crtc, drm) + vc4_crtc_disable_at_boot(crtc); + ret = drm_dev_register(drm, 0); if (ret < 0) goto unbind_all; @@ -368,6 +372,7 @@ static int vc4_platform_drm_remove(struct platform_device *pdev) } static const struct of_device_id vc4_of_match[] = { + { .compatible = "brcm,bcm2711-vc5", }, { .compatible = "brcm,bcm2835-vc4", }, { .compatible = "brcm,cygnus-vc4", }, {}, diff --git a/drivers/gpu/drm/vc4/vc4_drv.h b/drivers/gpu/drm/vc4/vc4_drv.h index fa19160c801f..8c8d96b6289f 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.h +++ b/drivers/gpu/drm/vc4/vc4_drv.h @@ -73,7 +73,6 @@ struct vc4_perfmon { struct vc4_dev { struct drm_device *dev; - struct vc4_hdmi *hdmi; struct vc4_hvs *hvs; struct vc4_v3d *v3d; struct vc4_dpi *dpi; @@ -201,6 +200,9 @@ struct vc4_dev { int power_refcount; + /* Set to true when the load tracker is supported. */ + bool load_tracker_available; + /* Set to true when the load tracker is active. */ bool load_tracker_enabled; @@ -320,6 +322,8 @@ struct vc4_hvs { void __iomem *regs; u32 __iomem *dlist; + struct clk *core_clk; + /* Memory manager for CRTCs to allocate space in the display * list. Units are dwords. */ @@ -329,7 +333,11 @@ struct vc4_hvs { spinlock_t mm_lock; struct drm_mm_node mitchell_netravali_filter; + struct debugfs_regset32 regset; + + /* HVS version 5 flag, therefore requires updated dlist structures */ + bool hvs5; }; struct vc4_plane { @@ -420,7 +428,8 @@ to_vc4_plane_state(struct drm_plane_state *state) enum vc4_encoder_type { VC4_ENCODER_TYPE_NONE, - VC4_ENCODER_TYPE_HDMI, + VC4_ENCODER_TYPE_HDMI0, + VC4_ENCODER_TYPE_HDMI1, VC4_ENCODER_TYPE_VEC, VC4_ENCODER_TYPE_DSI0, VC4_ENCODER_TYPE_DSI1, @@ -432,6 +441,13 @@ struct vc4_encoder { struct drm_encoder base; enum vc4_encoder_type type; u32 clock_select; + + void (*pre_crtc_configure)(struct drm_encoder *encoder); + void (*pre_crtc_enable)(struct drm_encoder *encoder); + void (*post_crtc_enable)(struct drm_encoder *encoder); + + void (*post_crtc_disable)(struct drm_encoder *encoder); + void (*post_crtc_powerdown)(struct drm_encoder *encoder); }; static inline struct vc4_encoder * @@ -441,13 +457,22 @@ to_vc4_encoder(struct drm_encoder *encoder) } struct vc4_crtc_data { - /* Which channel of the HVS this pixelvalve sources from. */ - int hvs_channel; + /* Bitmask of channels (FIFOs) of the HVS that the output can source from */ + unsigned int hvs_available_channels; + + /* Which output of the HVS this pixelvalve sources from. */ + int hvs_output; }; struct vc4_pv_data { struct vc4_crtc_data base; + /* Depth of the PixelValve FIFO in bytes */ + unsigned int fifo_depth; + + /* Number of pixels output per clock period */ + u8 pixels_per_clock; + enum vc4_encoder_type encoder_types[4]; const char *debugfs_name; @@ -462,14 +487,9 @@ 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]; - /* Size in pixels of the COB memory allocated to this CRTC. */ - u32 cob_size; struct drm_pending_vblank_event *event; @@ -502,6 +522,7 @@ struct vc4_crtc_state { struct drm_mm_node mm; bool feed_txp; bool txp_armed; + unsigned int assigned_channel; struct { unsigned int left; @@ -794,6 +815,7 @@ void vc4_bo_remove_from_purgeable_pool(struct vc4_bo *bo); /* vc4_crtc.c */ extern struct platform_driver vc4_crtc_driver; +int vc4_crtc_disable_at_boot(struct drm_crtc *crtc); int vc4_crtc_init(struct drm_device *drm, struct vc4_crtc *vc4_crtc, const struct drm_crtc_funcs *crtc_funcs, const struct drm_crtc_helper_funcs *crtc_helper_funcs); @@ -888,11 +910,12 @@ void vc4_irq_reset(struct drm_device *dev); /* vc4_hvs.c */ extern struct platform_driver vc4_hvs_driver; +void vc4_hvs_stop_channel(struct drm_device *dev, unsigned int output); +int vc4_hvs_get_fifo_from_output(struct drm_device *dev, unsigned int output); int vc4_hvs_atomic_check(struct drm_crtc *crtc, struct drm_crtc_state *state); void vc4_hvs_atomic_enable(struct drm_crtc *crtc, struct drm_crtc_state *old_state); void vc4_hvs_atomic_disable(struct drm_crtc *crtc, struct drm_crtc_state *old_state); void vc4_hvs_atomic_flush(struct drm_crtc *crtc, struct drm_crtc_state *state); -void vc4_hvs_mode_set_nofb(struct drm_crtc *crtc); void vc4_hvs_dump_state(struct drm_device *dev); void vc4_hvs_unmask_underrun(struct drm_device *dev, int channel); void vc4_hvs_mask_underrun(struct drm_device *dev, int channel); diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index 15a11cd4de25..03825596a308 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -43,177 +43,101 @@ #include <linux/of_platform.h> #include <linux/pm_runtime.h> #include <linux/rational.h> +#include <linux/reset.h> #include <sound/dmaengine_pcm.h> #include <sound/pcm_drm_eld.h> #include <sound/pcm_params.h> #include <sound/soc.h> #include "media/cec.h" #include "vc4_drv.h" +#include "vc4_hdmi.h" +#include "vc4_hdmi_regs.h" #include "vc4_regs.h" -#define HSM_CLOCK_FREQ 163682864 -#define CEC_CLOCK_FREQ 40000 -#define CEC_CLOCK_DIV (HSM_CLOCK_FREQ / CEC_CLOCK_FREQ) - -/* HDMI audio information */ -struct vc4_hdmi_audio { - struct snd_soc_card card; - struct snd_soc_dai_link link; - struct snd_soc_dai_link_component cpu; - struct snd_soc_dai_link_component codec; - struct snd_soc_dai_link_component platform; - int samplerate; - int channels; - struct snd_dmaengine_dai_dma_data dma_data; - struct snd_pcm_substream *substream; -}; +#define VC5_HDMI_HORZA_HFP_SHIFT 16 +#define VC5_HDMI_HORZA_HFP_MASK VC4_MASK(28, 16) +#define VC5_HDMI_HORZA_VPOS BIT(15) +#define VC5_HDMI_HORZA_HPOS BIT(14) +#define VC5_HDMI_HORZA_HAP_SHIFT 0 +#define VC5_HDMI_HORZA_HAP_MASK VC4_MASK(13, 0) -/* General HDMI hardware state. */ -struct vc4_hdmi { - struct platform_device *pdev; - - struct drm_encoder *encoder; - struct drm_connector *connector; +#define VC5_HDMI_HORZB_HBP_SHIFT 16 +#define VC5_HDMI_HORZB_HBP_MASK VC4_MASK(26, 16) +#define VC5_HDMI_HORZB_HSP_SHIFT 0 +#define VC5_HDMI_HORZB_HSP_MASK VC4_MASK(10, 0) - struct vc4_hdmi_audio audio; +#define VC5_HDMI_VERTA_VSP_SHIFT 24 +#define VC5_HDMI_VERTA_VSP_MASK VC4_MASK(28, 24) +#define VC5_HDMI_VERTA_VFP_SHIFT 16 +#define VC5_HDMI_VERTA_VFP_MASK VC4_MASK(22, 16) +#define VC5_HDMI_VERTA_VAL_SHIFT 0 +#define VC5_HDMI_VERTA_VAL_MASK VC4_MASK(12, 0) - struct i2c_adapter *ddc; - void __iomem *hdmicore_regs; - void __iomem *hd_regs; - int hpd_gpio; - bool hpd_active_low; +#define VC5_HDMI_VERTB_VSPO_SHIFT 16 +#define VC5_HDMI_VERTB_VSPO_MASK VC4_MASK(29, 16) - struct cec_adapter *cec_adap; - struct cec_msg cec_rx_msg; - bool cec_tx_ok; - bool cec_irq_was_rx; +# define VC4_HD_M_SW_RST BIT(2) +# define VC4_HD_M_ENABLE BIT(0) - struct clk *pixel_clock; - struct clk *hsm_clock; +#define CEC_CLOCK_FREQ 40000 +#define VC4_HSM_MID_CLOCK 149985000 - struct debugfs_regset32 hdmi_regset; - struct debugfs_regset32 hd_regset; -}; +static int vc4_hdmi_debugfs_regs(struct seq_file *m, void *unused) +{ + struct drm_info_node *node = (struct drm_info_node *)m->private; + struct vc4_hdmi *vc4_hdmi = node->info_ent->data; + struct drm_printer p = drm_seq_file_printer(m); -#define HDMI_READ(offset) readl(vc4->hdmi->hdmicore_regs + offset) -#define HDMI_WRITE(offset, val) writel(val, vc4->hdmi->hdmicore_regs + offset) -#define HD_READ(offset) readl(vc4->hdmi->hd_regs + offset) -#define HD_WRITE(offset, val) writel(val, vc4->hdmi->hd_regs + offset) + drm_print_regset32(&p, &vc4_hdmi->hdmi_regset); + drm_print_regset32(&p, &vc4_hdmi->hd_regset); -/* VC4 HDMI encoder KMS struct */ -struct vc4_hdmi_encoder { - struct vc4_encoder base; - bool hdmi_monitor; - bool limited_rgb_range; -}; + return 0; +} -static inline struct vc4_hdmi_encoder * -to_vc4_hdmi_encoder(struct drm_encoder *encoder) +static void vc4_hdmi_reset(struct vc4_hdmi *vc4_hdmi) { - return container_of(encoder, struct vc4_hdmi_encoder, base.base); -} + HDMI_WRITE(HDMI_M_CTL, VC4_HD_M_SW_RST); + udelay(1); + HDMI_WRITE(HDMI_M_CTL, 0); -/* VC4 HDMI connector KMS struct */ -struct vc4_hdmi_connector { - struct drm_connector base; + HDMI_WRITE(HDMI_M_CTL, VC4_HD_M_ENABLE); - /* 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; -}; + HDMI_WRITE(HDMI_SW_RESET_CONTROL, + VC4_HDMI_SW_RESET_HDMI | + VC4_HDMI_SW_RESET_FORMAT_DETECT); -static inline struct vc4_hdmi_connector * -to_vc4_hdmi_connector(struct drm_connector *connector) -{ - return container_of(connector, struct vc4_hdmi_connector, base); + HDMI_WRITE(HDMI_SW_RESET_CONTROL, 0); } -static const struct debugfs_reg32 hdmi_regs[] = { - VC4_REG32(VC4_HDMI_CORE_REV), - VC4_REG32(VC4_HDMI_SW_RESET_CONTROL), - VC4_REG32(VC4_HDMI_HOTPLUG_INT), - VC4_REG32(VC4_HDMI_HOTPLUG), - VC4_REG32(VC4_HDMI_MAI_CHANNEL_MAP), - VC4_REG32(VC4_HDMI_MAI_CONFIG), - VC4_REG32(VC4_HDMI_MAI_FORMAT), - VC4_REG32(VC4_HDMI_AUDIO_PACKET_CONFIG), - VC4_REG32(VC4_HDMI_RAM_PACKET_CONFIG), - VC4_REG32(VC4_HDMI_HORZA), - VC4_REG32(VC4_HDMI_HORZB), - VC4_REG32(VC4_HDMI_FIFO_CTL), - VC4_REG32(VC4_HDMI_SCHEDULER_CONTROL), - VC4_REG32(VC4_HDMI_VERTA0), - VC4_REG32(VC4_HDMI_VERTA1), - VC4_REG32(VC4_HDMI_VERTB0), - VC4_REG32(VC4_HDMI_VERTB1), - VC4_REG32(VC4_HDMI_TX_PHY_RESET_CTL), - VC4_REG32(VC4_HDMI_TX_PHY_CTL0), - - VC4_REG32(VC4_HDMI_CEC_CNTRL_1), - VC4_REG32(VC4_HDMI_CEC_CNTRL_2), - VC4_REG32(VC4_HDMI_CEC_CNTRL_3), - VC4_REG32(VC4_HDMI_CEC_CNTRL_4), - VC4_REG32(VC4_HDMI_CEC_CNTRL_5), - VC4_REG32(VC4_HDMI_CPU_STATUS), - VC4_REG32(VC4_HDMI_CPU_MASK_STATUS), - - VC4_REG32(VC4_HDMI_CEC_RX_DATA_1), - VC4_REG32(VC4_HDMI_CEC_RX_DATA_2), - VC4_REG32(VC4_HDMI_CEC_RX_DATA_3), - VC4_REG32(VC4_HDMI_CEC_RX_DATA_4), - VC4_REG32(VC4_HDMI_CEC_TX_DATA_1), - VC4_REG32(VC4_HDMI_CEC_TX_DATA_2), - VC4_REG32(VC4_HDMI_CEC_TX_DATA_3), - VC4_REG32(VC4_HDMI_CEC_TX_DATA_4), -}; - -static const struct debugfs_reg32 hd_regs[] = { - VC4_REG32(VC4_HD_M_CTL), - VC4_REG32(VC4_HD_MAI_CTL), - VC4_REG32(VC4_HD_MAI_THR), - VC4_REG32(VC4_HD_MAI_FMT), - VC4_REG32(VC4_HD_MAI_SMP), - VC4_REG32(VC4_HD_VID_CTL), - VC4_REG32(VC4_HD_CSC_CTL), - VC4_REG32(VC4_HD_FRAME_COUNT), -}; - -static int vc4_hdmi_debugfs_regs(struct seq_file *m, void *unused) +static void vc5_hdmi_reset(struct vc4_hdmi *vc4_hdmi) { - struct drm_info_node *node = (struct drm_info_node *)m->private; - struct drm_device *dev = node->minor->dev; - struct vc4_dev *vc4 = to_vc4_dev(dev); - struct vc4_hdmi *hdmi = vc4->hdmi; - struct drm_printer p = drm_seq_file_printer(m); + reset_control_reset(vc4_hdmi->reset); - drm_print_regset32(&p, &hdmi->hdmi_regset); - drm_print_regset32(&p, &hdmi->hd_regset); + HDMI_WRITE(HDMI_DVP_CTL, 0); - return 0; + HDMI_WRITE(HDMI_CLOCK_STOP, + HDMI_READ(HDMI_CLOCK_STOP) | VC4_DVP_HT_CLOCK_STOP_PIXEL); } static enum drm_connector_status vc4_hdmi_connector_detect(struct drm_connector *connector, bool force) { - struct drm_device *dev = connector->dev; - struct vc4_dev *vc4 = to_vc4_dev(dev); + struct vc4_hdmi *vc4_hdmi = connector_to_vc4_hdmi(connector); - if (vc4->hdmi->hpd_gpio) { - if (gpio_get_value_cansleep(vc4->hdmi->hpd_gpio) ^ - vc4->hdmi->hpd_active_low) + if (vc4_hdmi->hpd_gpio) { + if (gpio_get_value_cansleep(vc4_hdmi->hpd_gpio) ^ + vc4_hdmi->hpd_active_low) return connector_status_connected; - cec_phys_addr_invalidate(vc4->hdmi->cec_adap); + cec_phys_addr_invalidate(vc4_hdmi->cec_adap); return connector_status_disconnected; } - if (drm_probe_ddc(vc4->hdmi->ddc)) + if (drm_probe_ddc(vc4_hdmi->ddc)) return connector_status_connected; - if (HDMI_READ(VC4_HDMI_HOTPLUG) & VC4_HDMI_HOTPLUG_CONNECTED) + if (HDMI_READ(HDMI_HOTPLUG) & VC4_HDMI_HOTPLUG_CONNECTED) return connector_status_connected; - cec_phys_addr_invalidate(vc4->hdmi->cec_adap); + cec_phys_addr_invalidate(vc4_hdmi->cec_adap); return connector_status_disconnected; } @@ -225,17 +149,13 @@ static void vc4_hdmi_connector_destroy(struct drm_connector *connector) static int vc4_hdmi_connector_get_modes(struct drm_connector *connector) { - struct vc4_hdmi_connector *vc4_connector = - to_vc4_hdmi_connector(connector); - struct drm_encoder *encoder = vc4_connector->encoder; - struct vc4_hdmi_encoder *vc4_encoder = to_vc4_hdmi_encoder(encoder); - struct drm_device *dev = connector->dev; - struct vc4_dev *vc4 = to_vc4_dev(dev); + struct vc4_hdmi *vc4_hdmi = connector_to_vc4_hdmi(connector); + struct vc4_hdmi_encoder *vc4_encoder = &vc4_hdmi->encoder; int ret = 0; struct edid *edid; - edid = drm_get_edid(connector, vc4->hdmi->ddc); - cec_s_phys_addr_from_edid(vc4->hdmi->cec_adap, edid); + edid = drm_get_edid(connector, vc4_hdmi->ddc); + cec_s_phys_addr_from_edid(vc4_hdmi->cec_adap, edid); if (!edid) return -ENODEV; @@ -267,32 +187,23 @@ static const struct drm_connector_helper_funcs vc4_hdmi_connector_helper_funcs = .get_modes = vc4_hdmi_connector_get_modes, }; -static struct drm_connector *vc4_hdmi_connector_init(struct drm_device *dev, - struct drm_encoder *encoder, - struct i2c_adapter *ddc) +static int vc4_hdmi_connector_init(struct drm_device *dev, + struct vc4_hdmi *vc4_hdmi) { - struct drm_connector *connector; - struct vc4_hdmi_connector *hdmi_connector; + struct drm_connector *connector = &vc4_hdmi->connector; + struct drm_encoder *encoder = &vc4_hdmi->encoder.base.base; int ret; - hdmi_connector = devm_kzalloc(dev->dev, sizeof(*hdmi_connector), - GFP_KERNEL); - if (!hdmi_connector) - return ERR_PTR(-ENOMEM); - connector = &hdmi_connector->base; - - hdmi_connector->encoder = encoder; - drm_connector_init_with_ddc(dev, connector, &vc4_hdmi_connector_funcs, DRM_MODE_CONNECTOR_HDMIA, - ddc); + vc4_hdmi->ddc); drm_connector_helper_add(connector, &vc4_hdmi_connector_helper_funcs); /* Create and attach TV margin props to this connector. */ ret = drm_mode_create_tv_margin_properties(dev); if (ret) - return ERR_PTR(ret); + return ret; drm_connector_attach_tv_margin_properties(connector); @@ -304,35 +215,37 @@ static struct drm_connector *vc4_hdmi_connector_init(struct drm_device *dev, drm_connector_attach_encoder(connector, encoder); - return connector; + return 0; } static int vc4_hdmi_stop_packet(struct drm_encoder *encoder, enum hdmi_infoframe_type type) { - struct drm_device *dev = encoder->dev; - struct vc4_dev *vc4 = to_vc4_dev(dev); + struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); u32 packet_id = type - 0x80; - HDMI_WRITE(VC4_HDMI_RAM_PACKET_CONFIG, - HDMI_READ(VC4_HDMI_RAM_PACKET_CONFIG) & ~BIT(packet_id)); + HDMI_WRITE(HDMI_RAM_PACKET_CONFIG, + HDMI_READ(HDMI_RAM_PACKET_CONFIG) & ~BIT(packet_id)); - return wait_for(!(HDMI_READ(VC4_HDMI_RAM_PACKET_STATUS) & + return wait_for(!(HDMI_READ(HDMI_RAM_PACKET_STATUS) & BIT(packet_id)), 100); } static void vc4_hdmi_write_infoframe(struct drm_encoder *encoder, union hdmi_infoframe *frame) { - struct drm_device *dev = encoder->dev; - struct vc4_dev *vc4 = to_vc4_dev(dev); + struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); u32 packet_id = frame->any.type - 0x80; - u32 packet_reg = VC4_HDMI_RAM_PACKET(packet_id); + const struct vc4_hdmi_register *ram_packet_start = + &vc4_hdmi->variant->registers[HDMI_RAM_PACKET_START]; + u32 packet_reg = ram_packet_start->offset + VC4_HDMI_PACKET_STRIDE * packet_id; + void __iomem *base = __vc4_hdmi_get_field_base(vc4_hdmi, + ram_packet_start->reg); uint8_t buffer[VC4_HDMI_PACKET_STRIDE]; ssize_t len, i; int ret; - WARN_ONCE(!(HDMI_READ(VC4_HDMI_RAM_PACKET_CONFIG) & + WARN_ONCE(!(HDMI_READ(HDMI_RAM_PACKET_CONFIG) & VC4_HDMI_RAM_PACKET_ENABLE), "Packet RAM has to be on to store the packet."); @@ -347,23 +260,23 @@ static void vc4_hdmi_write_infoframe(struct drm_encoder *encoder, } for (i = 0; i < len; i += 7) { - HDMI_WRITE(packet_reg, - buffer[i + 0] << 0 | - buffer[i + 1] << 8 | - buffer[i + 2] << 16); + writel(buffer[i + 0] << 0 | + buffer[i + 1] << 8 | + buffer[i + 2] << 16, + base + packet_reg); packet_reg += 4; - HDMI_WRITE(packet_reg, - buffer[i + 3] << 0 | - buffer[i + 4] << 8 | - buffer[i + 5] << 16 | - buffer[i + 6] << 24); + writel(buffer[i + 3] << 0 | + buffer[i + 4] << 8 | + buffer[i + 5] << 16 | + buffer[i + 6] << 24, + base + packet_reg); packet_reg += 4; } - HDMI_WRITE(VC4_HDMI_RAM_PACKET_CONFIG, - HDMI_READ(VC4_HDMI_RAM_PACKET_CONFIG) | BIT(packet_id)); - ret = wait_for((HDMI_READ(VC4_HDMI_RAM_PACKET_STATUS) & + HDMI_WRITE(HDMI_RAM_PACKET_CONFIG, + HDMI_READ(HDMI_RAM_PACKET_CONFIG) | BIT(packet_id)); + ret = wait_for((HDMI_READ(HDMI_RAM_PACKET_STATUS) & BIT(packet_id)), 100); if (ret) DRM_ERROR("Failed to wait for infoframe to start: %d\n", ret); @@ -371,24 +284,24 @@ static void vc4_hdmi_write_infoframe(struct drm_encoder *encoder, static void vc4_hdmi_set_avi_infoframe(struct drm_encoder *encoder) { + struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); struct vc4_hdmi_encoder *vc4_encoder = to_vc4_hdmi_encoder(encoder); - struct vc4_dev *vc4 = encoder->dev->dev_private; - struct vc4_hdmi *hdmi = vc4->hdmi; - struct drm_connector_state *cstate = hdmi->connector->state; + struct drm_connector *connector = &vc4_hdmi->connector; + struct drm_connector_state *cstate = connector->state; struct drm_crtc *crtc = encoder->crtc; const struct drm_display_mode *mode = &crtc->state->adjusted_mode; union hdmi_infoframe frame; int ret; ret = drm_hdmi_avi_infoframe_from_display_mode(&frame.avi, - hdmi->connector, mode); + connector, mode); if (ret < 0) { DRM_ERROR("couldn't fill AVI infoframe\n"); return; } drm_hdmi_avi_infoframe_quant_range(&frame.avi, - hdmi->connector, mode, + connector, mode, vc4_encoder->limited_rgb_range ? HDMI_QUANTIZATION_RANGE_LIMITED : HDMI_QUANTIZATION_RANGE_FULL); @@ -416,9 +329,7 @@ static void vc4_hdmi_set_spd_infoframe(struct drm_encoder *encoder) static void vc4_hdmi_set_audio_infoframe(struct drm_encoder *encoder) { - struct drm_device *drm = encoder->dev; - struct vc4_dev *vc4 = drm->dev_private; - struct vc4_hdmi *hdmi = vc4->hdmi; + struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); union hdmi_infoframe frame; int ret; @@ -427,45 +338,139 @@ static void vc4_hdmi_set_audio_infoframe(struct drm_encoder *encoder) frame.audio.coding_type = HDMI_AUDIO_CODING_TYPE_STREAM; frame.audio.sample_frequency = HDMI_AUDIO_SAMPLE_FREQUENCY_STREAM; frame.audio.sample_size = HDMI_AUDIO_SAMPLE_SIZE_STREAM; - frame.audio.channels = hdmi->audio.channels; + frame.audio.channels = vc4_hdmi->audio.channels; vc4_hdmi_write_infoframe(encoder, &frame); } static void vc4_hdmi_set_infoframes(struct drm_encoder *encoder) { + struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); + vc4_hdmi_set_avi_infoframe(encoder); vc4_hdmi_set_spd_infoframe(encoder); + /* + * If audio was streaming, then we need to reenabled the audio + * infoframe here during encoder_enable. + */ + if (vc4_hdmi->audio.streaming) + vc4_hdmi_set_audio_infoframe(encoder); } -static void vc4_hdmi_encoder_disable(struct drm_encoder *encoder) +static void vc4_hdmi_encoder_post_crtc_disable(struct drm_encoder *encoder) { - struct drm_device *dev = encoder->dev; - struct vc4_dev *vc4 = to_vc4_dev(dev); - struct vc4_hdmi *hdmi = vc4->hdmi; + struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); + + HDMI_WRITE(HDMI_RAM_PACKET_CONFIG, 0); + + HDMI_WRITE(HDMI_VID_CTL, HDMI_READ(HDMI_VID_CTL) | + VC4_HD_VID_CTL_CLRRGB | VC4_HD_VID_CTL_CLRSYNC); + + HDMI_WRITE(HDMI_VID_CTL, + HDMI_READ(HDMI_VID_CTL) | VC4_HD_VID_CTL_BLANKPIX); +} + +static void vc4_hdmi_encoder_post_crtc_powerdown(struct drm_encoder *encoder) +{ + struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); int ret; - HDMI_WRITE(VC4_HDMI_RAM_PACKET_CONFIG, 0); + if (vc4_hdmi->variant->phy_disable) + vc4_hdmi->variant->phy_disable(vc4_hdmi); - HDMI_WRITE(VC4_HDMI_TX_PHY_RESET_CTL, 0xf << 16); - HD_WRITE(VC4_HD_VID_CTL, - HD_READ(VC4_HD_VID_CTL) & ~VC4_HD_VID_CTL_ENABLE); + HDMI_WRITE(HDMI_VID_CTL, + HDMI_READ(HDMI_VID_CTL) & ~VC4_HD_VID_CTL_ENABLE); - clk_disable_unprepare(hdmi->pixel_clock); + clk_disable_unprepare(vc4_hdmi->pixel_bvb_clock); + clk_disable_unprepare(vc4_hdmi->hsm_clock); + clk_disable_unprepare(vc4_hdmi->pixel_clock); - ret = pm_runtime_put(&hdmi->pdev->dev); + ret = pm_runtime_put(&vc4_hdmi->pdev->dev); if (ret < 0) DRM_ERROR("Failed to release power domain: %d\n", ret); } -static void vc4_hdmi_encoder_enable(struct drm_encoder *encoder) +static void vc4_hdmi_encoder_disable(struct drm_encoder *encoder) +{ +} + +static void vc4_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, bool enable) +{ + u32 csc_ctl; + + csc_ctl = VC4_SET_FIELD(VC4_HD_CSC_CTL_ORDER_BGR, + VC4_HD_CSC_CTL_ORDER); + + if (enable) { + /* CEA VICs other than #1 requre limited range RGB + * output unless overridden by an AVI infoframe. + * Apply a colorspace conversion to squash 0-255 down + * to 16-235. The matrix here is: + * + * [ 0 0 0.8594 16] + * [ 0 0.8594 0 16] + * [ 0.8594 0 0 16] + * [ 0 0 0 1] + */ + csc_ctl |= VC4_HD_CSC_CTL_ENABLE; + csc_ctl |= VC4_HD_CSC_CTL_RGB2YCC; + csc_ctl |= VC4_SET_FIELD(VC4_HD_CSC_CTL_MODE_CUSTOM, + VC4_HD_CSC_CTL_MODE); + + HDMI_WRITE(HDMI_CSC_12_11, (0x000 << 16) | 0x000); + HDMI_WRITE(HDMI_CSC_14_13, (0x100 << 16) | 0x6e0); + HDMI_WRITE(HDMI_CSC_22_21, (0x6e0 << 16) | 0x000); + HDMI_WRITE(HDMI_CSC_24_23, (0x100 << 16) | 0x000); + HDMI_WRITE(HDMI_CSC_32_31, (0x000 << 16) | 0x6e0); + HDMI_WRITE(HDMI_CSC_34_33, (0x100 << 16) | 0x000); + } + + /* The RGB order applies even when CSC is disabled. */ + HDMI_WRITE(HDMI_CSC_CTL, csc_ctl); +} + +static void vc5_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, bool enable) +{ + u32 csc_ctl; + + csc_ctl = 0x07; /* RGB_CONVERT_MODE = custom matrix, || USE_RGB_TO_YCBCR */ + + if (enable) { + /* CEA VICs other than #1 requre limited range RGB + * output unless overridden by an AVI infoframe. + * Apply a colorspace conversion to squash 0-255 down + * to 16-235. The matrix here is: + * + * [ 0.8594 0 0 16] + * [ 0 0.8594 0 16] + * [ 0 0 0.8594 16] + * [ 0 0 0 1] + * Matrix is signed 2p13 fixed point, with signed 9p6 offsets + */ + HDMI_WRITE(HDMI_CSC_12_11, (0x0000 << 16) | 0x1b80); + HDMI_WRITE(HDMI_CSC_14_13, (0x0400 << 16) | 0x0000); + HDMI_WRITE(HDMI_CSC_22_21, (0x1b80 << 16) | 0x0000); + HDMI_WRITE(HDMI_CSC_24_23, (0x0400 << 16) | 0x0000); + HDMI_WRITE(HDMI_CSC_32_31, (0x0000 << 16) | 0x0000); + HDMI_WRITE(HDMI_CSC_34_33, (0x0400 << 16) | 0x1b80); + } else { + /* Still use the matrix for full range, but make it unity. + * Matrix is signed 2p13 fixed point, with signed 9p6 offsets + */ + HDMI_WRITE(HDMI_CSC_12_11, (0x0000 << 16) | 0x2000); + HDMI_WRITE(HDMI_CSC_14_13, (0x0000 << 16) | 0x0000); + HDMI_WRITE(HDMI_CSC_22_21, (0x2000 << 16) | 0x0000); + HDMI_WRITE(HDMI_CSC_24_23, (0x0000 << 16) | 0x0000); + HDMI_WRITE(HDMI_CSC_32_31, (0x0000 << 16) | 0x0000); + HDMI_WRITE(HDMI_CSC_34_33, (0x0000 << 16) | 0x2000); + } + + HDMI_WRITE(HDMI_CSC_CTL, csc_ctl); +} + +static void vc4_hdmi_set_timings(struct vc4_hdmi *vc4_hdmi, + struct drm_display_mode *mode) { - struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode; - struct vc4_hdmi_encoder *vc4_encoder = to_vc4_hdmi_encoder(encoder); - struct drm_device *dev = encoder->dev; - struct vc4_dev *vc4 = to_vc4_dev(dev); - struct vc4_hdmi *hdmi = vc4->hdmi; - bool debug_dump_regs = false; bool hsync_pos = mode->flags & DRM_MODE_FLAG_PHSYNC; bool vsync_pos = mode->flags & DRM_MODE_FLAG_PVSYNC; bool interlaced = mode->flags & DRM_MODE_FLAG_INTERLACE; @@ -483,213 +488,285 @@ static void vc4_hdmi_encoder_enable(struct drm_encoder *encoder) mode->crtc_vsync_end - interlaced, VC4_HDMI_VERTB_VBP)); - u32 csc_ctl; + + HDMI_WRITE(HDMI_HORZA, + (vsync_pos ? VC4_HDMI_HORZA_VPOS : 0) | + (hsync_pos ? VC4_HDMI_HORZA_HPOS : 0) | + VC4_SET_FIELD(mode->hdisplay * pixel_rep, + VC4_HDMI_HORZA_HAP)); + + HDMI_WRITE(HDMI_HORZB, + VC4_SET_FIELD((mode->htotal - + mode->hsync_end) * pixel_rep, + VC4_HDMI_HORZB_HBP) | + VC4_SET_FIELD((mode->hsync_end - + mode->hsync_start) * pixel_rep, + VC4_HDMI_HORZB_HSP) | + VC4_SET_FIELD((mode->hsync_start - + mode->hdisplay) * pixel_rep, + VC4_HDMI_HORZB_HFP)); + + HDMI_WRITE(HDMI_VERTA0, verta); + HDMI_WRITE(HDMI_VERTA1, verta); + + HDMI_WRITE(HDMI_VERTB0, vertb_even); + HDMI_WRITE(HDMI_VERTB1, vertb); +} +static void vc5_hdmi_set_timings(struct vc4_hdmi *vc4_hdmi, + struct drm_display_mode *mode) +{ + bool hsync_pos = mode->flags & DRM_MODE_FLAG_PHSYNC; + bool vsync_pos = mode->flags & DRM_MODE_FLAG_PVSYNC; + bool interlaced = mode->flags & DRM_MODE_FLAG_INTERLACE; + u32 pixel_rep = (mode->flags & DRM_MODE_FLAG_DBLCLK) ? 2 : 1; + u32 verta = (VC4_SET_FIELD(mode->crtc_vsync_end - mode->crtc_vsync_start, + VC5_HDMI_VERTA_VSP) | + VC4_SET_FIELD(mode->crtc_vsync_start - mode->crtc_vdisplay, + VC5_HDMI_VERTA_VFP) | + VC4_SET_FIELD(mode->crtc_vdisplay, VC5_HDMI_VERTA_VAL)); + u32 vertb = (VC4_SET_FIELD(0, VC5_HDMI_VERTB_VSPO) | + VC4_SET_FIELD(mode->crtc_vtotal - mode->crtc_vsync_end, + VC4_HDMI_VERTB_VBP)); + u32 vertb_even = (VC4_SET_FIELD(0, VC5_HDMI_VERTB_VSPO) | + VC4_SET_FIELD(mode->crtc_vtotal - + mode->crtc_vsync_end - + interlaced, + VC4_HDMI_VERTB_VBP)); + + HDMI_WRITE(HDMI_VEC_INTERFACE_XBAR, 0x354021); + HDMI_WRITE(HDMI_HORZA, + (vsync_pos ? VC5_HDMI_HORZA_VPOS : 0) | + (hsync_pos ? VC5_HDMI_HORZA_HPOS : 0) | + VC4_SET_FIELD(mode->hdisplay * pixel_rep, + VC5_HDMI_HORZA_HAP) | + VC4_SET_FIELD((mode->hsync_start - + mode->hdisplay) * pixel_rep, + VC5_HDMI_HORZA_HFP)); + + HDMI_WRITE(HDMI_HORZB, + VC4_SET_FIELD((mode->htotal - + mode->hsync_end) * pixel_rep, + VC5_HDMI_HORZB_HBP) | + VC4_SET_FIELD((mode->hsync_end - + mode->hsync_start) * pixel_rep, + VC5_HDMI_HORZB_HSP)); + + HDMI_WRITE(HDMI_VERTA0, verta); + HDMI_WRITE(HDMI_VERTA1, verta); + + HDMI_WRITE(HDMI_VERTB0, vertb_even); + HDMI_WRITE(HDMI_VERTB1, vertb); + + HDMI_WRITE(HDMI_CLOCK_STOP, 0); +} + +static void vc4_hdmi_recenter_fifo(struct vc4_hdmi *vc4_hdmi) +{ + u32 drift; + int ret; + + drift = HDMI_READ(HDMI_FIFO_CTL); + drift &= VC4_HDMI_FIFO_VALID_WRITE_MASK; + + HDMI_WRITE(HDMI_FIFO_CTL, + drift & ~VC4_HDMI_FIFO_CTL_RECENTER); + HDMI_WRITE(HDMI_FIFO_CTL, + drift | VC4_HDMI_FIFO_CTL_RECENTER); + usleep_range(1000, 1100); + HDMI_WRITE(HDMI_FIFO_CTL, + drift & ~VC4_HDMI_FIFO_CTL_RECENTER); + HDMI_WRITE(HDMI_FIFO_CTL, + drift | VC4_HDMI_FIFO_CTL_RECENTER); + + ret = wait_for(HDMI_READ(HDMI_FIFO_CTL) & + VC4_HDMI_FIFO_CTL_RECENTER_DONE, 1); + WARN_ONCE(ret, "Timeout waiting for " + "VC4_HDMI_FIFO_CTL_RECENTER_DONE"); +} + +static void vc4_hdmi_encoder_pre_crtc_configure(struct drm_encoder *encoder) +{ + struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode; + struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); + unsigned long pixel_rate, hsm_rate; int ret; - ret = pm_runtime_get_sync(&hdmi->pdev->dev); + ret = pm_runtime_get_sync(&vc4_hdmi->pdev->dev); if (ret < 0) { DRM_ERROR("Failed to retain power domain: %d\n", ret); return; } - ret = clk_set_rate(hdmi->pixel_clock, - mode->clock * 1000 * - ((mode->flags & DRM_MODE_FLAG_DBLCLK) ? 2 : 1)); + pixel_rate = mode->clock * 1000 * ((mode->flags & DRM_MODE_FLAG_DBLCLK) ? 2 : 1); + ret = clk_set_rate(vc4_hdmi->pixel_clock, pixel_rate); if (ret) { DRM_ERROR("Failed to set pixel clock rate: %d\n", ret); return; } - ret = clk_prepare_enable(hdmi->pixel_clock); + ret = clk_prepare_enable(vc4_hdmi->pixel_clock); if (ret) { DRM_ERROR("Failed to turn on pixel clock: %d\n", ret); return; } - HDMI_WRITE(VC4_HDMI_SW_RESET_CONTROL, - VC4_HDMI_SW_RESET_HDMI | - VC4_HDMI_SW_RESET_FORMAT_DETECT); - - HDMI_WRITE(VC4_HDMI_SW_RESET_CONTROL, 0); - - /* PHY should be in reset, like - * vc4_hdmi_encoder_disable() does. + /* + * As stated in RPi's vc4 firmware "HDMI state machine (HSM) clock must + * be faster than pixel clock, infinitesimally faster, tested in + * simulation. Otherwise, exact value is unimportant for HDMI + * operation." This conflicts with bcm2835's vc4 documentation, which + * states HSM's clock has to be at least 108% of the pixel clock. + * + * Real life tests reveal that vc4's firmware statement holds up, and + * users are able to use pixel clocks closer to HSM's, namely for + * 1920x1200@60Hz. So it was decided to have leave a 1% margin between + * both clocks. Which, for RPi0-3 implies a maximum pixel clock of + * 162MHz. + * + * Additionally, the AXI clock needs to be at least 25% of + * pixel clock, but HSM ends up being the limiting factor. */ - HDMI_WRITE(VC4_HDMI_TX_PHY_RESET_CTL, 0xf << 16); + hsm_rate = max_t(unsigned long, 120000000, (pixel_rate / 100) * 101); + ret = clk_set_min_rate(vc4_hdmi->hsm_clock, hsm_rate); + if (ret) { + DRM_ERROR("Failed to set HSM clock rate: %d\n", ret); + return; + } - HDMI_WRITE(VC4_HDMI_TX_PHY_RESET_CTL, 0); + ret = clk_prepare_enable(vc4_hdmi->hsm_clock); + if (ret) { + DRM_ERROR("Failed to turn on HSM clock: %d\n", ret); + clk_disable_unprepare(vc4_hdmi->pixel_clock); + return; + } - if (debug_dump_regs) { - struct drm_printer p = drm_info_printer(&hdmi->pdev->dev); + /* + * FIXME: When the pixel freq is 594MHz (4k60), this needs to be setup + * at 300MHz. + */ + ret = clk_set_min_rate(vc4_hdmi->pixel_bvb_clock, + (hsm_rate > VC4_HSM_MID_CLOCK ? 150000000 : 75000000)); + if (ret) { + DRM_ERROR("Failed to set pixel bvb clock rate: %d\n", ret); + clk_disable_unprepare(vc4_hdmi->hsm_clock); + clk_disable_unprepare(vc4_hdmi->pixel_clock); + return; + } - dev_info(&hdmi->pdev->dev, "HDMI regs before:\n"); - drm_print_regset32(&p, &hdmi->hdmi_regset); - drm_print_regset32(&p, &hdmi->hd_regset); + ret = clk_prepare_enable(vc4_hdmi->pixel_bvb_clock); + if (ret) { + DRM_ERROR("Failed to turn on pixel bvb clock: %d\n", ret); + clk_disable_unprepare(vc4_hdmi->hsm_clock); + clk_disable_unprepare(vc4_hdmi->pixel_clock); + return; } - HD_WRITE(VC4_HD_VID_CTL, 0); + if (vc4_hdmi->variant->reset) + vc4_hdmi->variant->reset(vc4_hdmi); - HDMI_WRITE(VC4_HDMI_SCHEDULER_CONTROL, - HDMI_READ(VC4_HDMI_SCHEDULER_CONTROL) | + if (vc4_hdmi->variant->phy_init) + vc4_hdmi->variant->phy_init(vc4_hdmi, mode); + + HDMI_WRITE(HDMI_SCHEDULER_CONTROL, + HDMI_READ(HDMI_SCHEDULER_CONTROL) | VC4_HDMI_SCHEDULER_CONTROL_MANUAL_FORMAT | VC4_HDMI_SCHEDULER_CONTROL_IGNORE_VSYNC_PREDICTS); - HDMI_WRITE(VC4_HDMI_HORZA, - (vsync_pos ? VC4_HDMI_HORZA_VPOS : 0) | - (hsync_pos ? VC4_HDMI_HORZA_HPOS : 0) | - VC4_SET_FIELD(mode->hdisplay * pixel_rep, - VC4_HDMI_HORZA_HAP)); - - HDMI_WRITE(VC4_HDMI_HORZB, - VC4_SET_FIELD((mode->htotal - - mode->hsync_end) * pixel_rep, - VC4_HDMI_HORZB_HBP) | - VC4_SET_FIELD((mode->hsync_end - - mode->hsync_start) * pixel_rep, - VC4_HDMI_HORZB_HSP) | - VC4_SET_FIELD((mode->hsync_start - - mode->hdisplay) * pixel_rep, - VC4_HDMI_HORZB_HFP)); - - HDMI_WRITE(VC4_HDMI_VERTA0, verta); - HDMI_WRITE(VC4_HDMI_VERTA1, verta); - - HDMI_WRITE(VC4_HDMI_VERTB0, vertb_even); - HDMI_WRITE(VC4_HDMI_VERTB1, vertb); - - HD_WRITE(VC4_HD_VID_CTL, - (vsync_pos ? 0 : VC4_HD_VID_CTL_VSYNC_LOW) | - (hsync_pos ? 0 : VC4_HD_VID_CTL_HSYNC_LOW)); + if (vc4_hdmi->variant->set_timings) + vc4_hdmi->variant->set_timings(vc4_hdmi, mode); +} - csc_ctl = VC4_SET_FIELD(VC4_HD_CSC_CTL_ORDER_BGR, - VC4_HD_CSC_CTL_ORDER); +static void vc4_hdmi_encoder_pre_crtc_enable(struct drm_encoder *encoder) +{ + struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode; + struct vc4_hdmi_encoder *vc4_encoder = to_vc4_hdmi_encoder(encoder); + struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); if (vc4_encoder->hdmi_monitor && - drm_default_rgb_quant_range(mode) == - HDMI_QUANTIZATION_RANGE_LIMITED) { - /* CEA VICs other than #1 requre limited range RGB - * output unless overridden by an AVI infoframe. - * Apply a colorspace conversion to squash 0-255 down - * to 16-235. The matrix here is: - * - * [ 0 0 0.8594 16] - * [ 0 0.8594 0 16] - * [ 0.8594 0 0 16] - * [ 0 0 0 1] - */ - csc_ctl |= VC4_HD_CSC_CTL_ENABLE; - csc_ctl |= VC4_HD_CSC_CTL_RGB2YCC; - csc_ctl |= VC4_SET_FIELD(VC4_HD_CSC_CTL_MODE_CUSTOM, - VC4_HD_CSC_CTL_MODE); + drm_default_rgb_quant_range(mode) == HDMI_QUANTIZATION_RANGE_LIMITED) { + if (vc4_hdmi->variant->csc_setup) + vc4_hdmi->variant->csc_setup(vc4_hdmi, true); - HD_WRITE(VC4_HD_CSC_12_11, (0x000 << 16) | 0x000); - HD_WRITE(VC4_HD_CSC_14_13, (0x100 << 16) | 0x6e0); - HD_WRITE(VC4_HD_CSC_22_21, (0x6e0 << 16) | 0x000); - HD_WRITE(VC4_HD_CSC_24_23, (0x100 << 16) | 0x000); - HD_WRITE(VC4_HD_CSC_32_31, (0x000 << 16) | 0x6e0); - HD_WRITE(VC4_HD_CSC_34_33, (0x100 << 16) | 0x000); vc4_encoder->limited_rgb_range = true; } else { + if (vc4_hdmi->variant->csc_setup) + vc4_hdmi->variant->csc_setup(vc4_hdmi, false); + vc4_encoder->limited_rgb_range = false; } - /* The RGB order applies even when CSC is disabled. */ - HD_WRITE(VC4_HD_CSC_CTL, csc_ctl); - - HDMI_WRITE(VC4_HDMI_FIFO_CTL, VC4_HDMI_FIFO_CTL_MASTER_SLAVE_N); + HDMI_WRITE(HDMI_FIFO_CTL, VC4_HDMI_FIFO_CTL_MASTER_SLAVE_N); +} - if (debug_dump_regs) { - struct drm_printer p = drm_info_printer(&hdmi->pdev->dev); +static void vc4_hdmi_encoder_post_crtc_enable(struct drm_encoder *encoder) +{ + struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode; + struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); + struct vc4_hdmi_encoder *vc4_encoder = to_vc4_hdmi_encoder(encoder); + bool hsync_pos = mode->flags & DRM_MODE_FLAG_PHSYNC; + bool vsync_pos = mode->flags & DRM_MODE_FLAG_PVSYNC; + int ret; - dev_info(&hdmi->pdev->dev, "HDMI regs after:\n"); - drm_print_regset32(&p, &hdmi->hdmi_regset); - drm_print_regset32(&p, &hdmi->hd_regset); - } + HDMI_WRITE(HDMI_VID_CTL, + VC4_HD_VID_CTL_ENABLE | + VC4_HD_VID_CTL_UNDERFLOW_ENABLE | + VC4_HD_VID_CTL_FRAME_COUNTER_RESET | + (vsync_pos ? 0 : VC4_HD_VID_CTL_VSYNC_LOW) | + (hsync_pos ? 0 : VC4_HD_VID_CTL_HSYNC_LOW)); - HD_WRITE(VC4_HD_VID_CTL, - HD_READ(VC4_HD_VID_CTL) | - VC4_HD_VID_CTL_ENABLE | - VC4_HD_VID_CTL_UNDERFLOW_ENABLE | - VC4_HD_VID_CTL_FRAME_COUNTER_RESET); + HDMI_WRITE(HDMI_VID_CTL, + HDMI_READ(HDMI_VID_CTL) & ~VC4_HD_VID_CTL_BLANKPIX); if (vc4_encoder->hdmi_monitor) { - HDMI_WRITE(VC4_HDMI_SCHEDULER_CONTROL, - HDMI_READ(VC4_HDMI_SCHEDULER_CONTROL) | + HDMI_WRITE(HDMI_SCHEDULER_CONTROL, + HDMI_READ(HDMI_SCHEDULER_CONTROL) | VC4_HDMI_SCHEDULER_CONTROL_MODE_HDMI); - ret = wait_for(HDMI_READ(VC4_HDMI_SCHEDULER_CONTROL) & + ret = wait_for(HDMI_READ(HDMI_SCHEDULER_CONTROL) & VC4_HDMI_SCHEDULER_CONTROL_HDMI_ACTIVE, 1000); WARN_ONCE(ret, "Timeout waiting for " "VC4_HDMI_SCHEDULER_CONTROL_HDMI_ACTIVE\n"); } else { - HDMI_WRITE(VC4_HDMI_RAM_PACKET_CONFIG, - HDMI_READ(VC4_HDMI_RAM_PACKET_CONFIG) & + HDMI_WRITE(HDMI_RAM_PACKET_CONFIG, + HDMI_READ(HDMI_RAM_PACKET_CONFIG) & ~(VC4_HDMI_RAM_PACKET_ENABLE)); - HDMI_WRITE(VC4_HDMI_SCHEDULER_CONTROL, - HDMI_READ(VC4_HDMI_SCHEDULER_CONTROL) & + HDMI_WRITE(HDMI_SCHEDULER_CONTROL, + HDMI_READ(HDMI_SCHEDULER_CONTROL) & ~VC4_HDMI_SCHEDULER_CONTROL_MODE_HDMI); - ret = wait_for(!(HDMI_READ(VC4_HDMI_SCHEDULER_CONTROL) & + ret = wait_for(!(HDMI_READ(HDMI_SCHEDULER_CONTROL) & VC4_HDMI_SCHEDULER_CONTROL_HDMI_ACTIVE), 1000); WARN_ONCE(ret, "Timeout waiting for " "!VC4_HDMI_SCHEDULER_CONTROL_HDMI_ACTIVE\n"); } if (vc4_encoder->hdmi_monitor) { - u32 drift; - - WARN_ON(!(HDMI_READ(VC4_HDMI_SCHEDULER_CONTROL) & + WARN_ON(!(HDMI_READ(HDMI_SCHEDULER_CONTROL) & VC4_HDMI_SCHEDULER_CONTROL_HDMI_ACTIVE)); - HDMI_WRITE(VC4_HDMI_SCHEDULER_CONTROL, - HDMI_READ(VC4_HDMI_SCHEDULER_CONTROL) | + HDMI_WRITE(HDMI_SCHEDULER_CONTROL, + HDMI_READ(HDMI_SCHEDULER_CONTROL) | VC4_HDMI_SCHEDULER_CONTROL_VERT_ALWAYS_KEEPOUT); - HDMI_WRITE(VC4_HDMI_RAM_PACKET_CONFIG, + HDMI_WRITE(HDMI_RAM_PACKET_CONFIG, VC4_HDMI_RAM_PACKET_ENABLE); vc4_hdmi_set_infoframes(encoder); - - drift = HDMI_READ(VC4_HDMI_FIFO_CTL); - drift &= VC4_HDMI_FIFO_VALID_WRITE_MASK; - - HDMI_WRITE(VC4_HDMI_FIFO_CTL, - drift & ~VC4_HDMI_FIFO_CTL_RECENTER); - HDMI_WRITE(VC4_HDMI_FIFO_CTL, - drift | VC4_HDMI_FIFO_CTL_RECENTER); - usleep_range(1000, 1100); - HDMI_WRITE(VC4_HDMI_FIFO_CTL, - drift & ~VC4_HDMI_FIFO_CTL_RECENTER); - HDMI_WRITE(VC4_HDMI_FIFO_CTL, - drift | VC4_HDMI_FIFO_CTL_RECENTER); - - ret = wait_for(HDMI_READ(VC4_HDMI_FIFO_CTL) & - VC4_HDMI_FIFO_CTL_RECENTER_DONE, 1); - WARN_ONCE(ret, "Timeout waiting for " - "VC4_HDMI_FIFO_CTL_RECENTER_DONE"); } + + vc4_hdmi_recenter_fifo(vc4_hdmi); +} + +static void vc4_hdmi_encoder_enable(struct drm_encoder *encoder) +{ } static enum drm_mode_status -vc4_hdmi_encoder_mode_valid(struct drm_encoder *crtc, +vc4_hdmi_encoder_mode_valid(struct drm_encoder *encoder, const struct drm_display_mode *mode) { - /* - * As stated in RPi's vc4 firmware "HDMI state machine (HSM) clock must - * be faster than pixel clock, infinitesimally faster, tested in - * simulation. Otherwise, exact value is unimportant for HDMI - * operation." This conflicts with bcm2835's vc4 documentation, which - * states HSM's clock has to be at least 108% of the pixel clock. - * - * Real life tests reveal that vc4's firmware statement holds up, and - * users are able to use pixel clocks closer to HSM's, namely for - * 1920x1200@60Hz. So it was decided to have leave a 1% margin between - * both clocks. Which, for RPi0-3 implies a maximum pixel clock of - * 162MHz. - * - * Additionally, the AXI clock needs to be at least 25% of - * pixel clock, but HSM ends up being the limiting factor. - */ - if (mode->clock > HSM_CLOCK_FREQ / (1000 * 101 / 100)) + struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); + + if ((mode->clock * 1000) > vc4_hdmi->variant->max_pixel_clock) return MODE_CLOCK_HIGH; return MODE_OK; @@ -701,34 +778,54 @@ static const struct drm_encoder_helper_funcs vc4_hdmi_encoder_helper_funcs = { .enable = vc4_hdmi_encoder_enable, }; +static u32 vc4_hdmi_channel_map(struct vc4_hdmi *vc4_hdmi, u32 channel_mask) +{ + int i; + u32 channel_map = 0; + + for (i = 0; i < 8; i++) { + if (channel_mask & BIT(i)) + channel_map |= i << (3 * i); + } + return channel_map; +} + +static u32 vc5_hdmi_channel_map(struct vc4_hdmi *vc4_hdmi, u32 channel_mask) +{ + int i; + u32 channel_map = 0; + + for (i = 0; i < 8; i++) { + if (channel_mask & BIT(i)) + channel_map |= i << (4 * i); + } + return channel_map; +} + /* HDMI audio codec callbacks */ -static void vc4_hdmi_audio_set_mai_clock(struct vc4_hdmi *hdmi) +static void vc4_hdmi_audio_set_mai_clock(struct vc4_hdmi *vc4_hdmi) { - struct drm_device *drm = hdmi->encoder->dev; - struct vc4_dev *vc4 = to_vc4_dev(drm); - u32 hsm_clock = clk_get_rate(hdmi->hsm_clock); + u32 hsm_clock = clk_get_rate(vc4_hdmi->audio_clock); unsigned long n, m; - rational_best_approximation(hsm_clock, hdmi->audio.samplerate, + rational_best_approximation(hsm_clock, vc4_hdmi->audio.samplerate, VC4_HD_MAI_SMP_N_MASK >> VC4_HD_MAI_SMP_N_SHIFT, (VC4_HD_MAI_SMP_M_MASK >> VC4_HD_MAI_SMP_M_SHIFT) + 1, &n, &m); - HD_WRITE(VC4_HD_MAI_SMP, - VC4_SET_FIELD(n, VC4_HD_MAI_SMP_N) | - VC4_SET_FIELD(m - 1, VC4_HD_MAI_SMP_M)); + HDMI_WRITE(HDMI_MAI_SMP, + VC4_SET_FIELD(n, VC4_HD_MAI_SMP_N) | + VC4_SET_FIELD(m - 1, VC4_HD_MAI_SMP_M)); } -static void vc4_hdmi_set_n_cts(struct vc4_hdmi *hdmi) +static void vc4_hdmi_set_n_cts(struct vc4_hdmi *vc4_hdmi) { - struct drm_encoder *encoder = hdmi->encoder; + struct drm_encoder *encoder = &vc4_hdmi->encoder.base.base; struct drm_crtc *crtc = encoder->crtc; - struct drm_device *drm = encoder->dev; - struct vc4_dev *vc4 = to_vc4_dev(drm); const struct drm_display_mode *mode = &crtc->state->adjusted_mode; - u32 samplerate = hdmi->audio.samplerate; + u32 samplerate = vc4_hdmi->audio.samplerate; u32 n, cts; u64 tmp; @@ -737,7 +834,7 @@ static void vc4_hdmi_set_n_cts(struct vc4_hdmi *hdmi) do_div(tmp, 128 * samplerate); cts = tmp; - HDMI_WRITE(VC4_HDMI_CRP_CFG, + HDMI_WRITE(HDMI_CRP_CFG, VC4_HDMI_CRP_CFG_EXTERNAL_CTS_EN | VC4_SET_FIELD(n, VC4_HDMI_CRP_CFG_N)); @@ -746,8 +843,8 @@ static void vc4_hdmi_set_n_cts(struct vc4_hdmi *hdmi) * providing a CTS_1 value. The two CTS values are alternated * between based on the period fields */ - HDMI_WRITE(VC4_HDMI_CTS_0, cts); - HDMI_WRITE(VC4_HDMI_CTS_1, cts); + HDMI_WRITE(HDMI_CTS_0, cts); + HDMI_WRITE(HDMI_CTS_1, cts); } static inline struct vc4_hdmi *dai_to_hdmi(struct snd_soc_dai *dai) @@ -760,26 +857,25 @@ static inline struct vc4_hdmi *dai_to_hdmi(struct snd_soc_dai *dai) static int vc4_hdmi_audio_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - struct vc4_hdmi *hdmi = dai_to_hdmi(dai); - struct drm_encoder *encoder = hdmi->encoder; - struct vc4_dev *vc4 = to_vc4_dev(encoder->dev); + struct vc4_hdmi *vc4_hdmi = dai_to_hdmi(dai); + struct drm_encoder *encoder = &vc4_hdmi->encoder.base.base; + struct drm_connector *connector = &vc4_hdmi->connector; int ret; - if (hdmi->audio.substream && hdmi->audio.substream != substream) + if (vc4_hdmi->audio.substream && vc4_hdmi->audio.substream != substream) return -EINVAL; - hdmi->audio.substream = substream; + vc4_hdmi->audio.substream = substream; /* * If the HDMI encoder hasn't probed, or the encoder is * currently in DVI mode, treat the codec dai as missing. */ - if (!encoder->crtc || !(HDMI_READ(VC4_HDMI_RAM_PACKET_CONFIG) & + if (!encoder->crtc || !(HDMI_READ(HDMI_RAM_PACKET_CONFIG) & VC4_HDMI_RAM_PACKET_ENABLE)) return -ENODEV; - ret = snd_pcm_hw_constraint_eld(substream->runtime, - hdmi->connector->eld); + ret = snd_pcm_hw_constraint_eld(substream->runtime, connector->eld); if (ret) return ret; @@ -791,34 +887,33 @@ static int vc4_hdmi_audio_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) return 0; } -static void vc4_hdmi_audio_reset(struct vc4_hdmi *hdmi) +static void vc4_hdmi_audio_reset(struct vc4_hdmi *vc4_hdmi) { - struct drm_encoder *encoder = hdmi->encoder; - struct drm_device *drm = encoder->dev; - struct device *dev = &hdmi->pdev->dev; - struct vc4_dev *vc4 = to_vc4_dev(drm); + struct drm_encoder *encoder = &vc4_hdmi->encoder.base.base; + struct device *dev = &vc4_hdmi->pdev->dev; int ret; + vc4_hdmi->audio.streaming = false; ret = vc4_hdmi_stop_packet(encoder, HDMI_INFOFRAME_TYPE_AUDIO); if (ret) dev_err(dev, "Failed to stop audio infoframe: %d\n", ret); - HD_WRITE(VC4_HD_MAI_CTL, VC4_HD_MAI_CTL_RESET); - HD_WRITE(VC4_HD_MAI_CTL, VC4_HD_MAI_CTL_ERRORF); - HD_WRITE(VC4_HD_MAI_CTL, VC4_HD_MAI_CTL_FLUSH); + HDMI_WRITE(HDMI_MAI_CTL, VC4_HD_MAI_CTL_RESET); + HDMI_WRITE(HDMI_MAI_CTL, VC4_HD_MAI_CTL_ERRORF); + HDMI_WRITE(HDMI_MAI_CTL, VC4_HD_MAI_CTL_FLUSH); } static void vc4_hdmi_audio_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - struct vc4_hdmi *hdmi = dai_to_hdmi(dai); + struct vc4_hdmi *vc4_hdmi = dai_to_hdmi(dai); - if (substream != hdmi->audio.substream) + if (substream != vc4_hdmi->audio.substream) return; - vc4_hdmi_audio_reset(hdmi); + vc4_hdmi_audio_reset(vc4_hdmi); - hdmi->audio.substream = NULL; + vc4_hdmi->audio.substream = NULL; } /* HDMI audio codec callbacks */ @@ -826,72 +921,65 @@ static int vc4_hdmi_audio_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct vc4_hdmi *hdmi = dai_to_hdmi(dai); - struct drm_encoder *encoder = hdmi->encoder; - struct drm_device *drm = encoder->dev; - struct device *dev = &hdmi->pdev->dev; - struct vc4_dev *vc4 = to_vc4_dev(drm); + struct vc4_hdmi *vc4_hdmi = dai_to_hdmi(dai); + struct device *dev = &vc4_hdmi->pdev->dev; u32 audio_packet_config, channel_mask; - u32 channel_map, i; + u32 channel_map; - if (substream != hdmi->audio.substream) + if (substream != vc4_hdmi->audio.substream) return -EINVAL; dev_dbg(dev, "%s: %u Hz, %d bit, %d channels\n", __func__, params_rate(params), params_width(params), params_channels(params)); - hdmi->audio.channels = params_channels(params); - hdmi->audio.samplerate = params_rate(params); + vc4_hdmi->audio.channels = params_channels(params); + vc4_hdmi->audio.samplerate = params_rate(params); - HD_WRITE(VC4_HD_MAI_CTL, - VC4_HD_MAI_CTL_RESET | - VC4_HD_MAI_CTL_FLUSH | - VC4_HD_MAI_CTL_DLATE | - VC4_HD_MAI_CTL_ERRORE | - VC4_HD_MAI_CTL_ERRORF); + HDMI_WRITE(HDMI_MAI_CTL, + VC4_HD_MAI_CTL_RESET | + VC4_HD_MAI_CTL_FLUSH | + VC4_HD_MAI_CTL_DLATE | + VC4_HD_MAI_CTL_ERRORE | + VC4_HD_MAI_CTL_ERRORF); - vc4_hdmi_audio_set_mai_clock(hdmi); + vc4_hdmi_audio_set_mai_clock(vc4_hdmi); + /* The B frame identifier should match the value used by alsa-lib (8) */ audio_packet_config = VC4_HDMI_AUDIO_PACKET_ZERO_DATA_ON_SAMPLE_FLAT | VC4_HDMI_AUDIO_PACKET_ZERO_DATA_ON_INACTIVE_CHANNELS | - VC4_SET_FIELD(0xf, VC4_HDMI_AUDIO_PACKET_B_FRAME_IDENTIFIER); + VC4_SET_FIELD(0x8, VC4_HDMI_AUDIO_PACKET_B_FRAME_IDENTIFIER); - channel_mask = GENMASK(hdmi->audio.channels - 1, 0); + channel_mask = GENMASK(vc4_hdmi->audio.channels - 1, 0); audio_packet_config |= VC4_SET_FIELD(channel_mask, VC4_HDMI_AUDIO_PACKET_CEA_MASK); /* Set the MAI threshold. This logic mimics the firmware's. */ - if (hdmi->audio.samplerate > 96000) { - HD_WRITE(VC4_HD_MAI_THR, - VC4_SET_FIELD(0x12, VC4_HD_MAI_THR_DREQHIGH) | - VC4_SET_FIELD(0x12, VC4_HD_MAI_THR_DREQLOW)); - } else if (hdmi->audio.samplerate > 48000) { - HD_WRITE(VC4_HD_MAI_THR, - VC4_SET_FIELD(0x14, VC4_HD_MAI_THR_DREQHIGH) | - VC4_SET_FIELD(0x12, VC4_HD_MAI_THR_DREQLOW)); + if (vc4_hdmi->audio.samplerate > 96000) { + HDMI_WRITE(HDMI_MAI_THR, + VC4_SET_FIELD(0x12, VC4_HD_MAI_THR_DREQHIGH) | + VC4_SET_FIELD(0x12, VC4_HD_MAI_THR_DREQLOW)); + } else if (vc4_hdmi->audio.samplerate > 48000) { + HDMI_WRITE(HDMI_MAI_THR, + VC4_SET_FIELD(0x14, VC4_HD_MAI_THR_DREQHIGH) | + VC4_SET_FIELD(0x12, VC4_HD_MAI_THR_DREQLOW)); } else { - HD_WRITE(VC4_HD_MAI_THR, - VC4_SET_FIELD(0x10, VC4_HD_MAI_THR_PANICHIGH) | - VC4_SET_FIELD(0x10, VC4_HD_MAI_THR_PANICLOW) | - VC4_SET_FIELD(0x10, VC4_HD_MAI_THR_DREQHIGH) | - VC4_SET_FIELD(0x10, VC4_HD_MAI_THR_DREQLOW)); + HDMI_WRITE(HDMI_MAI_THR, + VC4_SET_FIELD(0x10, VC4_HD_MAI_THR_PANICHIGH) | + VC4_SET_FIELD(0x10, VC4_HD_MAI_THR_PANICLOW) | + VC4_SET_FIELD(0x10, VC4_HD_MAI_THR_DREQHIGH) | + VC4_SET_FIELD(0x10, VC4_HD_MAI_THR_DREQLOW)); } - HDMI_WRITE(VC4_HDMI_MAI_CONFIG, + HDMI_WRITE(HDMI_MAI_CONFIG, VC4_HDMI_MAI_CONFIG_BIT_REVERSE | VC4_SET_FIELD(channel_mask, VC4_HDMI_MAI_CHANNEL_MASK)); - channel_map = 0; - for (i = 0; i < 8; i++) { - if (channel_mask & BIT(i)) - channel_map |= i << (3 * i); - } - - HDMI_WRITE(VC4_HDMI_MAI_CHANNEL_MAP, channel_map); - HDMI_WRITE(VC4_HDMI_AUDIO_PACKET_CONFIG, audio_packet_config); - vc4_hdmi_set_n_cts(hdmi); + channel_map = vc4_hdmi->variant->channel_map(vc4_hdmi, channel_mask); + HDMI_WRITE(HDMI_MAI_CHANNEL_MAP, channel_map); + HDMI_WRITE(HDMI_AUDIO_PACKET_CONFIG, audio_packet_config); + vc4_hdmi_set_n_cts(vc4_hdmi); return 0; } @@ -899,30 +987,33 @@ static int vc4_hdmi_audio_hw_params(struct snd_pcm_substream *substream, static int vc4_hdmi_audio_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai) { - struct vc4_hdmi *hdmi = dai_to_hdmi(dai); - struct drm_encoder *encoder = hdmi->encoder; - struct drm_device *drm = encoder->dev; - struct vc4_dev *vc4 = to_vc4_dev(drm); + 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); - HDMI_WRITE(VC4_HDMI_TX_PHY_CTL0, - HDMI_READ(VC4_HDMI_TX_PHY_CTL0) & - ~VC4_HDMI_TX_PHY_RNG_PWRDN); - HD_WRITE(VC4_HD_MAI_CTL, - VC4_SET_FIELD(hdmi->audio.channels, - VC4_HD_MAI_CTL_CHNUM) | - VC4_HD_MAI_CTL_ENABLE); + vc4_hdmi->audio.streaming = true; + + if (vc4_hdmi->variant->phy_rng_enable) + vc4_hdmi->variant->phy_rng_enable(vc4_hdmi); + + HDMI_WRITE(HDMI_MAI_CTL, + VC4_SET_FIELD(vc4_hdmi->audio.channels, + VC4_HD_MAI_CTL_CHNUM) | + VC4_HD_MAI_CTL_ENABLE); break; case SNDRV_PCM_TRIGGER_STOP: - HD_WRITE(VC4_HD_MAI_CTL, - VC4_HD_MAI_CTL_DLATE | - VC4_HD_MAI_CTL_ERRORE | - VC4_HD_MAI_CTL_ERRORF); - HDMI_WRITE(VC4_HDMI_TX_PHY_CTL0, - HDMI_READ(VC4_HDMI_TX_PHY_CTL0) | - VC4_HDMI_TX_PHY_RNG_PWRDN); + HDMI_WRITE(HDMI_MAI_CTL, + VC4_HD_MAI_CTL_DLATE | + VC4_HD_MAI_CTL_ERRORE | + VC4_HD_MAI_CTL_ERRORF); + + if (vc4_hdmi->variant->phy_rng_disable) + vc4_hdmi->variant->phy_rng_disable(vc4_hdmi); + + vc4_hdmi->audio.streaming = false; + break; default: break; @@ -943,10 +1034,11 @@ static int vc4_hdmi_audio_eld_ctl_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); - struct vc4_hdmi *hdmi = snd_component_to_hdmi(component); + struct vc4_hdmi *vc4_hdmi = snd_component_to_hdmi(component); + struct drm_connector *connector = &vc4_hdmi->connector; uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; - uinfo->count = sizeof(hdmi->connector->eld); + uinfo->count = sizeof(connector->eld); return 0; } @@ -955,10 +1047,11 @@ static int vc4_hdmi_audio_eld_ctl_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); - struct vc4_hdmi *hdmi = snd_component_to_hdmi(component); + struct vc4_hdmi *vc4_hdmi = snd_component_to_hdmi(component); + struct drm_connector *connector = &vc4_hdmi->connector; - memcpy(ucontrol->value.bytes.data, hdmi->connector->eld, - sizeof(hdmi->connector->eld)); + memcpy(ucontrol->value.bytes.data, connector->eld, + sizeof(connector->eld)); return 0; } @@ -1023,9 +1116,9 @@ static const struct snd_soc_component_driver vc4_hdmi_audio_cpu_dai_comp = { static int vc4_hdmi_audio_cpu_dai_probe(struct snd_soc_dai *dai) { - struct vc4_hdmi *hdmi = dai_to_hdmi(dai); + struct vc4_hdmi *vc4_hdmi = dai_to_hdmi(dai); - snd_soc_dai_init_dma_data(dai, &hdmi->audio.dma_data, NULL); + snd_soc_dai_init_dma_data(dai, &vc4_hdmi->audio.dma_data, NULL); return 0; } @@ -1051,12 +1144,15 @@ static const struct snd_dmaengine_pcm_config pcm_conf = { .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, }; -static int vc4_hdmi_audio_init(struct vc4_hdmi *hdmi) +static int vc4_hdmi_audio_init(struct vc4_hdmi *vc4_hdmi) { - struct snd_soc_dai_link *dai_link = &hdmi->audio.link; - struct snd_soc_card *card = &hdmi->audio.card; - struct device *dev = &hdmi->pdev->dev; + const struct vc4_hdmi_register *mai_data = + &vc4_hdmi->variant->registers[HDMI_MAI_DATA]; + struct snd_soc_dai_link *dai_link = &vc4_hdmi->audio.link; + struct snd_soc_card *card = &vc4_hdmi->audio.card; + struct device *dev = &vc4_hdmi->pdev->dev; const __be32 *addr; + int index; int ret; if (!of_find_property(dev->of_node, "dmas", NULL)) { @@ -1065,6 +1161,11 @@ static int vc4_hdmi_audio_init(struct vc4_hdmi *hdmi) return 0; } + if (mai_data->reg != VC4_HD) { + WARN_ONCE(true, "MAI isn't in the HD block\n"); + return -EINVAL; + } + /* * Get the physical address of VC4_HD_MAI_DATA. We need to retrieve * the bus address specified in the DT, because the physical address @@ -1072,10 +1173,16 @@ static int vc4_hdmi_audio_init(struct vc4_hdmi *hdmi) * for DMA transfers. * This VC/MMU should probably be exposed to avoid this kind of hacks. */ - addr = of_get_address(dev->of_node, 1, NULL, NULL); - hdmi->audio.dma_data.addr = be32_to_cpup(addr) + VC4_HD_MAI_DATA; - hdmi->audio.dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; - hdmi->audio.dma_data.maxburst = 2; + index = of_property_match_string(dev->of_node, "reg-names", "hd"); + /* Before BCM2711, we don't have a named register range */ + if (index < 0) + index = 1; + + addr = of_get_address(dev->of_node, index, NULL, NULL); + + vc4_hdmi->audio.dma_data.addr = be32_to_cpup(addr) + mai_data->offset; + vc4_hdmi->audio.dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + vc4_hdmi->audio.dma_data.maxburst = 2; ret = devm_snd_dmaengine_pcm_register(dev, &pcm_conf, 0); if (ret) { @@ -1098,9 +1205,9 @@ static int vc4_hdmi_audio_init(struct vc4_hdmi *hdmi) return ret; } - dai_link->cpus = &hdmi->audio.cpu; - dai_link->codecs = &hdmi->audio.codec; - dai_link->platforms = &hdmi->audio.platform; + dai_link->cpus = &vc4_hdmi->audio.cpu; + dai_link->codecs = &vc4_hdmi->audio.codec; + dai_link->platforms = &vc4_hdmi->audio.platform; dai_link->num_cpus = 1; dai_link->num_codecs = 1; @@ -1115,7 +1222,7 @@ static int vc4_hdmi_audio_init(struct vc4_hdmi *hdmi) card->dai_link = dai_link; card->num_links = 1; - card->name = "vc4-hdmi"; + card->name = vc4_hdmi->variant->card_name; card->dev = dev; /* @@ -1125,7 +1232,7 @@ static int vc4_hdmi_audio_init(struct vc4_hdmi *hdmi) * now stored in card->drvdata and should be retrieved with * snd_soc_card_get_drvdata() if needed. */ - snd_soc_card_set_drvdata(card, hdmi); + snd_soc_card_set_drvdata(card, vc4_hdmi); ret = devm_snd_soc_register_card(dev, card); if (ret) dev_err(dev, "Could not register sound card: %d\n", ret); @@ -1137,35 +1244,35 @@ static int vc4_hdmi_audio_init(struct vc4_hdmi *hdmi) #ifdef CONFIG_DRM_VC4_HDMI_CEC static irqreturn_t vc4_cec_irq_handler_thread(int irq, void *priv) { - struct vc4_dev *vc4 = priv; - struct vc4_hdmi *hdmi = vc4->hdmi; - - if (hdmi->cec_irq_was_rx) { - if (hdmi->cec_rx_msg.len) - cec_received_msg(hdmi->cec_adap, &hdmi->cec_rx_msg); - } else if (hdmi->cec_tx_ok) { - cec_transmit_done(hdmi->cec_adap, CEC_TX_STATUS_OK, + struct vc4_hdmi *vc4_hdmi = priv; + + if (vc4_hdmi->cec_irq_was_rx) { + if (vc4_hdmi->cec_rx_msg.len) + cec_received_msg(vc4_hdmi->cec_adap, + &vc4_hdmi->cec_rx_msg); + } else if (vc4_hdmi->cec_tx_ok) { + cec_transmit_done(vc4_hdmi->cec_adap, CEC_TX_STATUS_OK, 0, 0, 0, 0); } else { /* * This CEC implementation makes 1 retry, so if we * get a NACK, then that means it made 2 attempts. */ - cec_transmit_done(hdmi->cec_adap, CEC_TX_STATUS_NACK, + cec_transmit_done(vc4_hdmi->cec_adap, CEC_TX_STATUS_NACK, 0, 2, 0, 0); } return IRQ_HANDLED; } -static void vc4_cec_read_msg(struct vc4_dev *vc4, u32 cntrl1) +static void vc4_cec_read_msg(struct vc4_hdmi *vc4_hdmi, u32 cntrl1) { - struct cec_msg *msg = &vc4->hdmi->cec_rx_msg; + struct cec_msg *msg = &vc4_hdmi->cec_rx_msg; unsigned int i; msg->len = 1 + ((cntrl1 & VC4_HDMI_CEC_REC_WRD_CNT_MASK) >> VC4_HDMI_CEC_REC_WRD_CNT_SHIFT); for (i = 0; i < msg->len; i += 4) { - u32 val = HDMI_READ(VC4_HDMI_CEC_RX_DATA_1 + i); + u32 val = HDMI_READ(HDMI_CEC_RX_DATA_1 + i); msg->msg[i] = val & 0xff; msg->msg[i + 1] = (val >> 8) & 0xff; @@ -1176,38 +1283,37 @@ static void vc4_cec_read_msg(struct vc4_dev *vc4, u32 cntrl1) static irqreturn_t vc4_cec_irq_handler(int irq, void *priv) { - struct vc4_dev *vc4 = priv; - struct vc4_hdmi *hdmi = vc4->hdmi; - u32 stat = HDMI_READ(VC4_HDMI_CPU_STATUS); + struct vc4_hdmi *vc4_hdmi = priv; + u32 stat = HDMI_READ(HDMI_CEC_CPU_STATUS); u32 cntrl1, cntrl5; if (!(stat & VC4_HDMI_CPU_CEC)) return IRQ_NONE; - hdmi->cec_rx_msg.len = 0; - cntrl1 = HDMI_READ(VC4_HDMI_CEC_CNTRL_1); - cntrl5 = HDMI_READ(VC4_HDMI_CEC_CNTRL_5); - hdmi->cec_irq_was_rx = cntrl5 & VC4_HDMI_CEC_RX_CEC_INT; - if (hdmi->cec_irq_was_rx) { - vc4_cec_read_msg(vc4, cntrl1); + vc4_hdmi->cec_rx_msg.len = 0; + cntrl1 = HDMI_READ(HDMI_CEC_CNTRL_1); + cntrl5 = HDMI_READ(HDMI_CEC_CNTRL_5); + vc4_hdmi->cec_irq_was_rx = cntrl5 & VC4_HDMI_CEC_RX_CEC_INT; + if (vc4_hdmi->cec_irq_was_rx) { + vc4_cec_read_msg(vc4_hdmi, cntrl1); cntrl1 |= VC4_HDMI_CEC_CLEAR_RECEIVE_OFF; - HDMI_WRITE(VC4_HDMI_CEC_CNTRL_1, cntrl1); + HDMI_WRITE(HDMI_CEC_CNTRL_1, cntrl1); cntrl1 &= ~VC4_HDMI_CEC_CLEAR_RECEIVE_OFF; } else { - hdmi->cec_tx_ok = cntrl1 & VC4_HDMI_CEC_TX_STATUS_GOOD; + vc4_hdmi->cec_tx_ok = cntrl1 & VC4_HDMI_CEC_TX_STATUS_GOOD; cntrl1 &= ~VC4_HDMI_CEC_START_XMIT_BEGIN; } - HDMI_WRITE(VC4_HDMI_CEC_CNTRL_1, cntrl1); - HDMI_WRITE(VC4_HDMI_CPU_CLEAR, VC4_HDMI_CPU_CEC); + HDMI_WRITE(HDMI_CEC_CNTRL_1, cntrl1); + HDMI_WRITE(HDMI_CEC_CPU_CLEAR, VC4_HDMI_CPU_CEC); return IRQ_WAKE_THREAD; } static int vc4_hdmi_cec_adap_enable(struct cec_adapter *adap, bool enable) { - struct vc4_dev *vc4 = cec_get_drvdata(adap); + struct vc4_hdmi *vc4_hdmi = cec_get_drvdata(adap); /* clock period in microseconds */ const u32 usecs = 1000000 / CEC_CLOCK_FREQ; - u32 val = HDMI_READ(VC4_HDMI_CEC_CNTRL_5); + u32 val = HDMI_READ(HDMI_CEC_CNTRL_5); val &= ~(VC4_HDMI_CEC_TX_SW_RESET | VC4_HDMI_CEC_RX_SW_RESET | VC4_HDMI_CEC_CNT_TO_4700_US_MASK | @@ -1216,30 +1322,30 @@ static int vc4_hdmi_cec_adap_enable(struct cec_adapter *adap, bool enable) ((4500 / usecs) << VC4_HDMI_CEC_CNT_TO_4500_US_SHIFT); if (enable) { - HDMI_WRITE(VC4_HDMI_CEC_CNTRL_5, val | + HDMI_WRITE(HDMI_CEC_CNTRL_5, val | VC4_HDMI_CEC_TX_SW_RESET | VC4_HDMI_CEC_RX_SW_RESET); - HDMI_WRITE(VC4_HDMI_CEC_CNTRL_5, val); - HDMI_WRITE(VC4_HDMI_CEC_CNTRL_2, - ((1500 / usecs) << VC4_HDMI_CEC_CNT_TO_1500_US_SHIFT) | - ((1300 / usecs) << VC4_HDMI_CEC_CNT_TO_1300_US_SHIFT) | - ((800 / usecs) << VC4_HDMI_CEC_CNT_TO_800_US_SHIFT) | - ((600 / usecs) << VC4_HDMI_CEC_CNT_TO_600_US_SHIFT) | - ((400 / usecs) << VC4_HDMI_CEC_CNT_TO_400_US_SHIFT)); - HDMI_WRITE(VC4_HDMI_CEC_CNTRL_3, - ((2750 / usecs) << VC4_HDMI_CEC_CNT_TO_2750_US_SHIFT) | - ((2400 / usecs) << VC4_HDMI_CEC_CNT_TO_2400_US_SHIFT) | - ((2050 / usecs) << VC4_HDMI_CEC_CNT_TO_2050_US_SHIFT) | - ((1700 / usecs) << VC4_HDMI_CEC_CNT_TO_1700_US_SHIFT)); - HDMI_WRITE(VC4_HDMI_CEC_CNTRL_4, - ((4300 / usecs) << VC4_HDMI_CEC_CNT_TO_4300_US_SHIFT) | - ((3900 / usecs) << VC4_HDMI_CEC_CNT_TO_3900_US_SHIFT) | - ((3600 / usecs) << VC4_HDMI_CEC_CNT_TO_3600_US_SHIFT) | - ((3500 / usecs) << VC4_HDMI_CEC_CNT_TO_3500_US_SHIFT)); - - HDMI_WRITE(VC4_HDMI_CPU_MASK_CLEAR, VC4_HDMI_CPU_CEC); + HDMI_WRITE(HDMI_CEC_CNTRL_5, val); + HDMI_WRITE(HDMI_CEC_CNTRL_2, + ((1500 / usecs) << VC4_HDMI_CEC_CNT_TO_1500_US_SHIFT) | + ((1300 / usecs) << VC4_HDMI_CEC_CNT_TO_1300_US_SHIFT) | + ((800 / usecs) << VC4_HDMI_CEC_CNT_TO_800_US_SHIFT) | + ((600 / usecs) << VC4_HDMI_CEC_CNT_TO_600_US_SHIFT) | + ((400 / usecs) << VC4_HDMI_CEC_CNT_TO_400_US_SHIFT)); + HDMI_WRITE(HDMI_CEC_CNTRL_3, + ((2750 / usecs) << VC4_HDMI_CEC_CNT_TO_2750_US_SHIFT) | + ((2400 / usecs) << VC4_HDMI_CEC_CNT_TO_2400_US_SHIFT) | + ((2050 / usecs) << VC4_HDMI_CEC_CNT_TO_2050_US_SHIFT) | + ((1700 / usecs) << VC4_HDMI_CEC_CNT_TO_1700_US_SHIFT)); + HDMI_WRITE(HDMI_CEC_CNTRL_4, + ((4300 / usecs) << VC4_HDMI_CEC_CNT_TO_4300_US_SHIFT) | + ((3900 / usecs) << VC4_HDMI_CEC_CNT_TO_3900_US_SHIFT) | + ((3600 / usecs) << VC4_HDMI_CEC_CNT_TO_3600_US_SHIFT) | + ((3500 / usecs) << VC4_HDMI_CEC_CNT_TO_3500_US_SHIFT)); + + HDMI_WRITE(HDMI_CEC_CPU_MASK_CLEAR, VC4_HDMI_CPU_CEC); } else { - HDMI_WRITE(VC4_HDMI_CPU_MASK_SET, VC4_HDMI_CPU_CEC); - HDMI_WRITE(VC4_HDMI_CEC_CNTRL_5, val | + HDMI_WRITE(HDMI_CEC_CPU_MASK_SET, VC4_HDMI_CPU_CEC); + HDMI_WRITE(HDMI_CEC_CNTRL_5, val | VC4_HDMI_CEC_TX_SW_RESET | VC4_HDMI_CEC_RX_SW_RESET); } return 0; @@ -1247,10 +1353,10 @@ static int vc4_hdmi_cec_adap_enable(struct cec_adapter *adap, bool enable) static int vc4_hdmi_cec_adap_log_addr(struct cec_adapter *adap, u8 log_addr) { - struct vc4_dev *vc4 = cec_get_drvdata(adap); + struct vc4_hdmi *vc4_hdmi = cec_get_drvdata(adap); - HDMI_WRITE(VC4_HDMI_CEC_CNTRL_1, - (HDMI_READ(VC4_HDMI_CEC_CNTRL_1) & ~VC4_HDMI_CEC_ADDR_MASK) | + HDMI_WRITE(HDMI_CEC_CNTRL_1, + (HDMI_READ(HDMI_CEC_CNTRL_1) & ~VC4_HDMI_CEC_ADDR_MASK) | (log_addr & 0xf) << VC4_HDMI_CEC_ADDR_SHIFT); return 0; } @@ -1258,25 +1364,25 @@ static int vc4_hdmi_cec_adap_log_addr(struct cec_adapter *adap, u8 log_addr) static int vc4_hdmi_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, u32 signal_free_time, struct cec_msg *msg) { - struct vc4_dev *vc4 = cec_get_drvdata(adap); + struct vc4_hdmi *vc4_hdmi = cec_get_drvdata(adap); u32 val; unsigned int i; for (i = 0; i < msg->len; i += 4) - HDMI_WRITE(VC4_HDMI_CEC_TX_DATA_1 + i, + HDMI_WRITE(HDMI_CEC_TX_DATA_1 + i, (msg->msg[i]) | (msg->msg[i + 1] << 8) | (msg->msg[i + 2] << 16) | (msg->msg[i + 3] << 24)); - val = HDMI_READ(VC4_HDMI_CEC_CNTRL_1); + val = HDMI_READ(HDMI_CEC_CNTRL_1); val &= ~VC4_HDMI_CEC_START_XMIT_BEGIN; - HDMI_WRITE(VC4_HDMI_CEC_CNTRL_1, val); + HDMI_WRITE(HDMI_CEC_CNTRL_1, val); val &= ~VC4_HDMI_CEC_MESSAGE_LENGTH_MASK; val |= (msg->len - 1) << VC4_HDMI_CEC_MESSAGE_LENGTH_SHIFT; val |= VC4_HDMI_CEC_START_XMIT_BEGIN; - HDMI_WRITE(VC4_HDMI_CEC_CNTRL_1, val); + HDMI_WRITE(HDMI_CEC_CNTRL_1, val); return 0; } @@ -1285,61 +1391,275 @@ static const struct cec_adap_ops vc4_hdmi_cec_adap_ops = { .adap_log_addr = vc4_hdmi_cec_adap_log_addr, .adap_transmit = vc4_hdmi_cec_adap_transmit, }; -#endif -static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) +static int vc4_hdmi_cec_init(struct vc4_hdmi *vc4_hdmi) { -#ifdef CONFIG_DRM_VC4_HDMI_CEC struct cec_connector_info conn_info; -#endif - 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_hdmi *hdmi; - struct vc4_hdmi_encoder *vc4_hdmi_encoder; - struct device_node *ddc_node; + struct platform_device *pdev = vc4_hdmi->pdev; u32 value; int ret; - hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL); - if (!hdmi) + if (!vc4_hdmi->variant->cec_available) + return 0; + + vc4_hdmi->cec_adap = cec_allocate_adapter(&vc4_hdmi_cec_adap_ops, + vc4_hdmi, "vc4", + CEC_CAP_DEFAULTS | + CEC_CAP_CONNECTOR_INFO, 1); + ret = PTR_ERR_OR_ZERO(vc4_hdmi->cec_adap); + if (ret < 0) + return ret; + + cec_fill_conn_info_from_drm(&conn_info, &vc4_hdmi->connector); + cec_s_conn_info(vc4_hdmi->cec_adap, &conn_info); + + HDMI_WRITE(HDMI_CEC_CPU_MASK_SET, 0xffffffff); + value = HDMI_READ(HDMI_CEC_CNTRL_1); + value &= ~VC4_HDMI_CEC_DIV_CLK_CNT_MASK; + /* + * Set the logical address to Unregistered and set the clock + * divider: the hsm_clock rate and this divider setting will + * give a 40 kHz CEC clock. + */ + value |= VC4_HDMI_CEC_ADDR_MASK | + (4091 << VC4_HDMI_CEC_DIV_CLK_CNT_SHIFT); + HDMI_WRITE(HDMI_CEC_CNTRL_1, value); + ret = devm_request_threaded_irq(&pdev->dev, platform_get_irq(pdev, 0), + vc4_cec_irq_handler, + vc4_cec_irq_handler_thread, 0, + "vc4 hdmi cec", vc4_hdmi); + if (ret) + goto err_delete_cec_adap; + + ret = cec_register_adapter(vc4_hdmi->cec_adap, &pdev->dev); + if (ret < 0) + goto err_delete_cec_adap; + + return 0; + +err_delete_cec_adap: + cec_delete_adapter(vc4_hdmi->cec_adap); + + return ret; +} + +static void vc4_hdmi_cec_exit(struct vc4_hdmi *vc4_hdmi) +{ + cec_unregister_adapter(vc4_hdmi->cec_adap); +} +#else +static int vc4_hdmi_cec_init(struct vc4_hdmi *vc4_hdmi) +{ + return 0; +} + +static void vc4_hdmi_cec_exit(struct vc4_hdmi *vc4_hdmi) {}; + +#endif + +static int vc4_hdmi_build_regset(struct vc4_hdmi *vc4_hdmi, + struct debugfs_regset32 *regset, + enum vc4_hdmi_regs reg) +{ + const struct vc4_hdmi_variant *variant = vc4_hdmi->variant; + struct debugfs_reg32 *regs, *new_regs; + unsigned int count = 0; + unsigned int i; + + regs = kcalloc(variant->num_registers, sizeof(*regs), + GFP_KERNEL); + if (!regs) return -ENOMEM; - vc4_hdmi_encoder = devm_kzalloc(dev, sizeof(*vc4_hdmi_encoder), - GFP_KERNEL); - if (!vc4_hdmi_encoder) + for (i = 0; i < variant->num_registers; i++) { + const struct vc4_hdmi_register *field = &variant->registers[i]; + + if (field->reg != reg) + continue; + + regs[count].name = field->name; + regs[count].offset = field->offset; + count++; + } + + new_regs = krealloc(regs, count * sizeof(*regs), GFP_KERNEL); + if (!new_regs) return -ENOMEM; - vc4_hdmi_encoder->base.type = VC4_ENCODER_TYPE_HDMI; - hdmi->encoder = &vc4_hdmi_encoder->base.base; - - hdmi->pdev = pdev; - hdmi->hdmicore_regs = vc4_ioremap_regs(pdev, 0); - if (IS_ERR(hdmi->hdmicore_regs)) - return PTR_ERR(hdmi->hdmicore_regs); - - hdmi->hd_regs = vc4_ioremap_regs(pdev, 1); - if (IS_ERR(hdmi->hd_regs)) - return PTR_ERR(hdmi->hd_regs); - - hdmi->hdmi_regset.base = hdmi->hdmicore_regs; - hdmi->hdmi_regset.regs = hdmi_regs; - hdmi->hdmi_regset.nregs = ARRAY_SIZE(hdmi_regs); - hdmi->hd_regset.base = hdmi->hd_regs; - hdmi->hd_regset.regs = hd_regs; - hdmi->hd_regset.nregs = ARRAY_SIZE(hd_regs); - - hdmi->pixel_clock = devm_clk_get(dev, "pixel"); - if (IS_ERR(hdmi->pixel_clock)) { - ret = PTR_ERR(hdmi->pixel_clock); + + regset->base = __vc4_hdmi_get_field_base(vc4_hdmi, reg); + regset->regs = new_regs; + regset->nregs = count; + + return 0; +} + +static int vc4_hdmi_init_resources(struct vc4_hdmi *vc4_hdmi) +{ + struct platform_device *pdev = vc4_hdmi->pdev; + struct device *dev = &pdev->dev; + int ret; + + vc4_hdmi->hdmicore_regs = vc4_ioremap_regs(pdev, 0); + if (IS_ERR(vc4_hdmi->hdmicore_regs)) + return PTR_ERR(vc4_hdmi->hdmicore_regs); + + vc4_hdmi->hd_regs = vc4_ioremap_regs(pdev, 1); + if (IS_ERR(vc4_hdmi->hd_regs)) + return PTR_ERR(vc4_hdmi->hd_regs); + + ret = vc4_hdmi_build_regset(vc4_hdmi, &vc4_hdmi->hd_regset, VC4_HD); + if (ret) + return ret; + + ret = vc4_hdmi_build_regset(vc4_hdmi, &vc4_hdmi->hdmi_regset, VC4_HDMI); + if (ret) + return ret; + + vc4_hdmi->pixel_clock = devm_clk_get(dev, "pixel"); + if (IS_ERR(vc4_hdmi->pixel_clock)) { + ret = PTR_ERR(vc4_hdmi->pixel_clock); if (ret != -EPROBE_DEFER) DRM_ERROR("Failed to get pixel clock\n"); return ret; } - hdmi->hsm_clock = devm_clk_get(dev, "hdmi"); - if (IS_ERR(hdmi->hsm_clock)) { + + vc4_hdmi->hsm_clock = devm_clk_get(dev, "hdmi"); + if (IS_ERR(vc4_hdmi->hsm_clock)) { DRM_ERROR("Failed to get HDMI state machine clock\n"); - return PTR_ERR(hdmi->hsm_clock); + return PTR_ERR(vc4_hdmi->hsm_clock); } + vc4_hdmi->audio_clock = vc4_hdmi->hsm_clock; + + return 0; +} + +static int vc5_hdmi_init_resources(struct vc4_hdmi *vc4_hdmi) +{ + struct platform_device *pdev = vc4_hdmi->pdev; + struct device *dev = &pdev->dev; + struct resource *res; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "hdmi"); + if (!res) + return -ENODEV; + + vc4_hdmi->hdmicore_regs = devm_ioremap(dev, res->start, + resource_size(res)); + if (!vc4_hdmi->hdmicore_regs) + return -ENOMEM; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "hd"); + if (!res) + return -ENODEV; + + vc4_hdmi->hd_regs = devm_ioremap(dev, res->start, resource_size(res)); + if (!vc4_hdmi->hd_regs) + return -ENOMEM; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "cec"); + if (!res) + return -ENODEV; + + vc4_hdmi->cec_regs = devm_ioremap(dev, res->start, resource_size(res)); + if (!vc4_hdmi->cec_regs) + return -ENOMEM; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "csc"); + if (!res) + return -ENODEV; + + vc4_hdmi->csc_regs = devm_ioremap(dev, res->start, resource_size(res)); + if (!vc4_hdmi->csc_regs) + return -ENOMEM; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dvp"); + if (!res) + return -ENODEV; + + vc4_hdmi->dvp_regs = devm_ioremap(dev, res->start, resource_size(res)); + if (!vc4_hdmi->dvp_regs) + return -ENOMEM; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy"); + if (!res) + return -ENODEV; + + vc4_hdmi->phy_regs = devm_ioremap(dev, res->start, resource_size(res)); + if (!vc4_hdmi->phy_regs) + return -ENOMEM; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "packet"); + if (!res) + return -ENODEV; + + vc4_hdmi->ram_regs = devm_ioremap(dev, res->start, resource_size(res)); + if (!vc4_hdmi->ram_regs) + return -ENOMEM; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rm"); + if (!res) + return -ENODEV; + + vc4_hdmi->rm_regs = devm_ioremap(dev, res->start, resource_size(res)); + if (!vc4_hdmi->rm_regs) + return -ENOMEM; + + vc4_hdmi->hsm_clock = devm_clk_get(dev, "hdmi"); + if (IS_ERR(vc4_hdmi->hsm_clock)) { + DRM_ERROR("Failed to get HDMI state machine clock\n"); + return PTR_ERR(vc4_hdmi->hsm_clock); + } + + vc4_hdmi->pixel_bvb_clock = devm_clk_get(dev, "bvb"); + if (IS_ERR(vc4_hdmi->pixel_bvb_clock)) { + DRM_ERROR("Failed to get pixel bvb clock\n"); + return PTR_ERR(vc4_hdmi->pixel_bvb_clock); + } + + vc4_hdmi->audio_clock = devm_clk_get(dev, "audio"); + if (IS_ERR(vc4_hdmi->audio_clock)) { + DRM_ERROR("Failed to get audio clock\n"); + return PTR_ERR(vc4_hdmi->audio_clock); + } + + vc4_hdmi->reset = devm_reset_control_get(dev, NULL); + if (IS_ERR(vc4_hdmi->reset)) { + DRM_ERROR("Failed to get HDMI reset line\n"); + return PTR_ERR(vc4_hdmi->reset); + } + + return 0; +} + +static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) +{ + const struct vc4_hdmi_variant *variant = of_device_get_match_data(dev); + struct platform_device *pdev = to_platform_device(dev); + struct drm_device *drm = dev_get_drvdata(master); + struct vc4_hdmi *vc4_hdmi; + struct drm_encoder *encoder; + struct device_node *ddc_node; + u32 value; + int ret; + + vc4_hdmi = devm_kzalloc(dev, sizeof(*vc4_hdmi), GFP_KERNEL); + if (!vc4_hdmi) + return -ENOMEM; + + dev_set_drvdata(dev, vc4_hdmi); + encoder = &vc4_hdmi->encoder.base.base; + vc4_hdmi->encoder.base.type = variant->encoder_type; + vc4_hdmi->encoder.base.pre_crtc_configure = vc4_hdmi_encoder_pre_crtc_configure; + vc4_hdmi->encoder.base.pre_crtc_enable = vc4_hdmi_encoder_pre_crtc_enable; + vc4_hdmi->encoder.base.post_crtc_enable = vc4_hdmi_encoder_post_crtc_enable; + vc4_hdmi->encoder.base.post_crtc_disable = vc4_hdmi_encoder_post_crtc_disable; + vc4_hdmi->encoder.base.post_crtc_powerdown = vc4_hdmi_encoder_post_crtc_powerdown; + vc4_hdmi->pdev = pdev; + vc4_hdmi->variant = variant; + + ret = variant->init_resources(vc4_hdmi); + if (ret) + return ret; ddc_node = of_parse_phandle(dev->of_node, "ddc", 0); if (!ddc_node) { @@ -1347,123 +1667,62 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) return -ENODEV; } - hdmi->ddc = of_find_i2c_adapter_by_node(ddc_node); + vc4_hdmi->ddc = of_find_i2c_adapter_by_node(ddc_node); of_node_put(ddc_node); - if (!hdmi->ddc) { + if (!vc4_hdmi->ddc) { DRM_DEBUG("Failed to get ddc i2c adapter by node\n"); return -EPROBE_DEFER; } - /* This is the rate that is set by the firmware. The number - * needs to be a bit higher than the pixel clock rate - * (generally 148.5Mhz). - */ - ret = clk_set_rate(hdmi->hsm_clock, HSM_CLOCK_FREQ); - if (ret) { - DRM_ERROR("Failed to set HSM clock rate: %d\n", ret); - goto err_put_i2c; - } - - ret = clk_prepare_enable(hdmi->hsm_clock); - if (ret) { - DRM_ERROR("Failed to turn on HDMI state machine clock: %d\n", - ret); - goto err_put_i2c; - } - /* Only use the GPIO HPD pin if present in the DT, otherwise * we'll use the HDMI core's register. */ if (of_find_property(dev->of_node, "hpd-gpios", &value)) { enum of_gpio_flags hpd_gpio_flags; - hdmi->hpd_gpio = of_get_named_gpio_flags(dev->of_node, - "hpd-gpios", 0, - &hpd_gpio_flags); - if (hdmi->hpd_gpio < 0) { - ret = hdmi->hpd_gpio; + vc4_hdmi->hpd_gpio = of_get_named_gpio_flags(dev->of_node, + "hpd-gpios", 0, + &hpd_gpio_flags); + if (vc4_hdmi->hpd_gpio < 0) { + ret = vc4_hdmi->hpd_gpio; goto err_unprepare_hsm; } - hdmi->hpd_active_low = hpd_gpio_flags & OF_GPIO_ACTIVE_LOW; + vc4_hdmi->hpd_active_low = hpd_gpio_flags & OF_GPIO_ACTIVE_LOW; } - vc4->hdmi = hdmi; - - /* HDMI core must be enabled. */ - if (!(HD_READ(VC4_HD_M_CTL) & VC4_HD_M_ENABLE)) { - HD_WRITE(VC4_HD_M_CTL, VC4_HD_M_SW_RST); - udelay(1); - HD_WRITE(VC4_HD_M_CTL, 0); - - HD_WRITE(VC4_HD_M_CTL, VC4_HD_M_ENABLE); - } pm_runtime_enable(dev); - drm_simple_encoder_init(drm, hdmi->encoder, DRM_MODE_ENCODER_TMDS); - drm_encoder_helper_add(hdmi->encoder, &vc4_hdmi_encoder_helper_funcs); + drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS); + drm_encoder_helper_add(encoder, &vc4_hdmi_encoder_helper_funcs); - hdmi->connector = - vc4_hdmi_connector_init(drm, hdmi->encoder, hdmi->ddc); - if (IS_ERR(hdmi->connector)) { - ret = PTR_ERR(hdmi->connector); + ret = vc4_hdmi_connector_init(drm, vc4_hdmi); + if (ret) goto err_destroy_encoder; - } -#ifdef CONFIG_DRM_VC4_HDMI_CEC - hdmi->cec_adap = cec_allocate_adapter(&vc4_hdmi_cec_adap_ops, - vc4, "vc4", - CEC_CAP_DEFAULTS | - CEC_CAP_CONNECTOR_INFO, 1); - ret = PTR_ERR_OR_ZERO(hdmi->cec_adap); - if (ret < 0) - goto err_destroy_conn; - cec_fill_conn_info_from_drm(&conn_info, hdmi->connector); - cec_s_conn_info(hdmi->cec_adap, &conn_info); - - HDMI_WRITE(VC4_HDMI_CPU_MASK_SET, 0xffffffff); - value = HDMI_READ(VC4_HDMI_CEC_CNTRL_1); - value &= ~VC4_HDMI_CEC_DIV_CLK_CNT_MASK; - /* - * Set the logical address to Unregistered and set the clock - * divider: the hsm_clock rate and this divider setting will - * give a 40 kHz CEC clock. - */ - value |= VC4_HDMI_CEC_ADDR_MASK | - (4091 << VC4_HDMI_CEC_DIV_CLK_CNT_SHIFT); - HDMI_WRITE(VC4_HDMI_CEC_CNTRL_1, value); - ret = devm_request_threaded_irq(dev, platform_get_irq(pdev, 0), - vc4_cec_irq_handler, - vc4_cec_irq_handler_thread, 0, - "vc4 hdmi cec", vc4); + ret = vc4_hdmi_cec_init(vc4_hdmi); if (ret) - goto err_delete_cec_adap; - ret = cec_register_adapter(hdmi->cec_adap, dev); - if (ret < 0) - goto err_delete_cec_adap; -#endif + goto err_destroy_conn; - ret = vc4_hdmi_audio_init(hdmi); + ret = vc4_hdmi_audio_init(vc4_hdmi); if (ret) - goto err_destroy_encoder; + goto err_free_cec; - vc4_debugfs_add_file(drm, "hdmi_regs", vc4_hdmi_debugfs_regs, hdmi); + vc4_debugfs_add_file(drm, variant->debugfs_name, + vc4_hdmi_debugfs_regs, + vc4_hdmi); return 0; -#ifdef CONFIG_DRM_VC4_HDMI_CEC -err_delete_cec_adap: - cec_delete_adapter(hdmi->cec_adap); +err_free_cec: + vc4_hdmi_cec_exit(vc4_hdmi); err_destroy_conn: - vc4_hdmi_connector_destroy(hdmi->connector); -#endif + vc4_hdmi_connector_destroy(&vc4_hdmi->connector); err_destroy_encoder: - drm_encoder_cleanup(hdmi->encoder); + drm_encoder_cleanup(encoder); err_unprepare_hsm: - clk_disable_unprepare(hdmi->hsm_clock); pm_runtime_disable(dev); -err_put_i2c: - put_device(&hdmi->ddc->dev); + put_device(&vc4_hdmi->ddc->dev); return ret; } @@ -1471,20 +1730,39 @@ err_put_i2c: static void vc4_hdmi_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_hdmi *hdmi = vc4->hdmi; + struct vc4_hdmi *vc4_hdmi; - cec_unregister_adapter(hdmi->cec_adap); - vc4_hdmi_connector_destroy(hdmi->connector); - drm_encoder_cleanup(hdmi->encoder); + /* + * ASoC makes it a bit hard to retrieve a pointer to the + * vc4_hdmi structure. Registering the card will overwrite our + * device drvdata with a pointer to the snd_soc_card structure, + * which can then be used to retrieve whatever drvdata we want + * to associate. + * + * However, that doesn't fly in the case where we wouldn't + * register an ASoC card (because of an old DT that is missing + * the dmas properties for example), then the card isn't + * registered and the device drvdata wouldn't be set. + * + * We can deal with both cases by making sure a snd_soc_card + * pointer and a vc4_hdmi structure are pointing to the same + * memory address, so we can treat them indistinctly without any + * issue. + */ + BUILD_BUG_ON(offsetof(struct vc4_hdmi_audio, card) != 0); + BUILD_BUG_ON(offsetof(struct vc4_hdmi, audio) != 0); + vc4_hdmi = dev_get_drvdata(dev); - clk_disable_unprepare(hdmi->hsm_clock); - pm_runtime_disable(dev); + kfree(vc4_hdmi->hdmi_regset.regs); + kfree(vc4_hdmi->hd_regset.regs); - put_device(&hdmi->ddc->dev); + vc4_hdmi_cec_exit(vc4_hdmi); + vc4_hdmi_connector_destroy(&vc4_hdmi->connector); + drm_encoder_cleanup(&vc4_hdmi->encoder.base.base); + + pm_runtime_disable(dev); - vc4->hdmi = NULL; + put_device(&vc4_hdmi->ddc->dev); } static const struct component_ops vc4_hdmi_ops = { @@ -1503,8 +1781,80 @@ static int vc4_hdmi_dev_remove(struct platform_device *pdev) return 0; } +static const struct vc4_hdmi_variant bcm2835_variant = { + .encoder_type = VC4_ENCODER_TYPE_HDMI0, + .debugfs_name = "hdmi_regs", + .card_name = "vc4-hdmi", + .max_pixel_clock = 162000000, + .cec_available = true, + .registers = vc4_hdmi_fields, + .num_registers = ARRAY_SIZE(vc4_hdmi_fields), + + .init_resources = vc4_hdmi_init_resources, + .csc_setup = vc4_hdmi_csc_setup, + .reset = vc4_hdmi_reset, + .set_timings = vc4_hdmi_set_timings, + .phy_init = vc4_hdmi_phy_init, + .phy_disable = vc4_hdmi_phy_disable, + .phy_rng_enable = vc4_hdmi_phy_rng_enable, + .phy_rng_disable = vc4_hdmi_phy_rng_disable, + .channel_map = vc4_hdmi_channel_map, +}; + +static const struct vc4_hdmi_variant bcm2711_hdmi0_variant = { + .encoder_type = VC4_ENCODER_TYPE_HDMI0, + .debugfs_name = "hdmi0_regs", + .card_name = "vc4-hdmi-0", + .max_pixel_clock = 297000000, + .registers = vc5_hdmi_hdmi0_fields, + .num_registers = ARRAY_SIZE(vc5_hdmi_hdmi0_fields), + .phy_lane_mapping = { + PHY_LANE_0, + PHY_LANE_1, + PHY_LANE_2, + PHY_LANE_CK, + }, + + .init_resources = vc5_hdmi_init_resources, + .csc_setup = vc5_hdmi_csc_setup, + .reset = vc5_hdmi_reset, + .set_timings = vc5_hdmi_set_timings, + .phy_init = vc5_hdmi_phy_init, + .phy_disable = vc5_hdmi_phy_disable, + .phy_rng_enable = vc5_hdmi_phy_rng_enable, + .phy_rng_disable = vc5_hdmi_phy_rng_disable, + .channel_map = vc5_hdmi_channel_map, +}; + +static const struct vc4_hdmi_variant bcm2711_hdmi1_variant = { + .encoder_type = VC4_ENCODER_TYPE_HDMI1, + .debugfs_name = "hdmi1_regs", + .card_name = "vc4-hdmi-1", + .max_pixel_clock = 297000000, + .registers = vc5_hdmi_hdmi1_fields, + .num_registers = ARRAY_SIZE(vc5_hdmi_hdmi1_fields), + .phy_lane_mapping = { + PHY_LANE_1, + PHY_LANE_0, + PHY_LANE_CK, + PHY_LANE_2, + }, + + .init_resources = vc5_hdmi_init_resources, + .csc_setup = vc5_hdmi_csc_setup, + .reset = vc5_hdmi_reset, + .set_timings = vc5_hdmi_set_timings, + .phy_init = vc5_hdmi_phy_init, + .phy_disable = vc5_hdmi_phy_disable, + .phy_rng_enable = vc5_hdmi_phy_rng_enable, + .phy_rng_disable = vc5_hdmi_phy_rng_disable, + .channel_map = vc5_hdmi_channel_map, +}; + static const struct of_device_id vc4_hdmi_dt_match[] = { - { .compatible = "brcm,bcm2835-hdmi" }, + { .compatible = "brcm,bcm2835-hdmi", .data = &bcm2835_variant }, + { .compatible = "brcm,bcm2711-hdmi0", .data = &bcm2711_hdmi0_variant }, + { .compatible = "brcm,bcm2711-hdmi1", .data = &bcm2711_hdmi1_variant }, {} }; diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.h b/drivers/gpu/drm/vc4/vc4_hdmi.h new file mode 100644 index 000000000000..63c6f8bddf1d --- /dev/null +++ b/drivers/gpu/drm/vc4/vc4_hdmi.h @@ -0,0 +1,184 @@ +#ifndef _VC4_HDMI_H_ +#define _VC4_HDMI_H_ + +#include <drm/drm_connector.h> +#include <media/cec.h> +#include <sound/dmaengine_pcm.h> +#include <sound/soc.h> + +#include "vc4_drv.h" + +/* VC4 HDMI encoder KMS struct */ +struct vc4_hdmi_encoder { + struct vc4_encoder base; + bool hdmi_monitor; + bool limited_rgb_range; +}; + +static inline struct vc4_hdmi_encoder * +to_vc4_hdmi_encoder(struct drm_encoder *encoder) +{ + return container_of(encoder, struct vc4_hdmi_encoder, base.base); +} + +struct drm_display_mode; + +struct vc4_hdmi; +struct vc4_hdmi_register; + +enum vc4_hdmi_phy_channel { + PHY_LANE_0 = 0, + PHY_LANE_1, + PHY_LANE_2, + PHY_LANE_CK, +}; + +struct vc4_hdmi_variant { + /* Encoder Type for that controller */ + enum vc4_encoder_type encoder_type; + + /* ALSA card name */ + const char *card_name; + + /* Filename to expose the registers in debugfs */ + const char *debugfs_name; + + /* Set to true when the CEC support is available */ + bool cec_available; + + /* Maximum pixel clock supported by the controller (in Hz) */ + unsigned long long max_pixel_clock; + + /* List of the registers available on that variant */ + const struct vc4_hdmi_register *registers; + + /* Number of registers on that variant */ + unsigned int num_registers; + + /* BCM2711 Only. + * The variants don't map the lane in the same order in the + * PHY, so this is an array mapping the HDMI channel (index) + * to the PHY lane (value). + */ + enum vc4_hdmi_phy_channel phy_lane_mapping[4]; + + /* Callback to get the resources (memory region, interrupts, + * clocks, etc) for that variant. + */ + int (*init_resources)(struct vc4_hdmi *vc4_hdmi); + + /* Callback to reset the HDMI block */ + void (*reset)(struct vc4_hdmi *vc4_hdmi); + + /* Callback to enable / disable the CSC */ + void (*csc_setup)(struct vc4_hdmi *vc4_hdmi, bool enable); + + /* Callback to configure the video timings in the HDMI block */ + void (*set_timings)(struct vc4_hdmi *vc4_hdmi, + struct drm_display_mode *mode); + + /* Callback to initialize the PHY according to the mode */ + void (*phy_init)(struct vc4_hdmi *vc4_hdmi, + struct drm_display_mode *mode); + + /* Callback to disable the PHY */ + void (*phy_disable)(struct vc4_hdmi *vc4_hdmi); + + /* Callback to enable the RNG in the PHY */ + void (*phy_rng_enable)(struct vc4_hdmi *vc4_hdmi); + + /* Callback to disable the RNG in the PHY */ + void (*phy_rng_disable)(struct vc4_hdmi *vc4_hdmi); + + /* Callback to get channel map */ + u32 (*channel_map)(struct vc4_hdmi *vc4_hdmi, u32 channel_mask); +}; + +/* HDMI audio information */ +struct vc4_hdmi_audio { + struct snd_soc_card card; + struct snd_soc_dai_link link; + struct snd_soc_dai_link_component cpu; + struct snd_soc_dai_link_component codec; + struct snd_soc_dai_link_component platform; + int samplerate; + int channels; + struct snd_dmaengine_dai_dma_data dma_data; + struct snd_pcm_substream *substream; + + bool streaming; +}; + +/* General HDMI hardware state. */ +struct vc4_hdmi { + struct vc4_hdmi_audio audio; + + struct platform_device *pdev; + const struct vc4_hdmi_variant *variant; + + struct vc4_hdmi_encoder encoder; + struct drm_connector connector; + + struct i2c_adapter *ddc; + void __iomem *hdmicore_regs; + void __iomem *hd_regs; + + /* VC5 Only */ + void __iomem *cec_regs; + /* VC5 Only */ + void __iomem *csc_regs; + /* VC5 Only */ + void __iomem *dvp_regs; + /* VC5 Only */ + void __iomem *phy_regs; + /* VC5 Only */ + void __iomem *ram_regs; + /* VC5 Only */ + void __iomem *rm_regs; + + int hpd_gpio; + bool hpd_active_low; + + struct cec_adapter *cec_adap; + struct cec_msg cec_rx_msg; + bool cec_tx_ok; + bool cec_irq_was_rx; + + struct clk *pixel_clock; + struct clk *hsm_clock; + struct clk *audio_clock; + struct clk *pixel_bvb_clock; + + struct reset_control *reset; + + struct debugfs_regset32 hdmi_regset; + struct debugfs_regset32 hd_regset; +}; + +static inline struct vc4_hdmi * +connector_to_vc4_hdmi(struct drm_connector *connector) +{ + return container_of(connector, struct vc4_hdmi, connector); +} + +static inline struct vc4_hdmi * +encoder_to_vc4_hdmi(struct drm_encoder *encoder) +{ + struct vc4_hdmi_encoder *_encoder = to_vc4_hdmi_encoder(encoder); + + return container_of(_encoder, struct vc4_hdmi, encoder); +} + +void vc4_hdmi_phy_init(struct vc4_hdmi *vc4_hdmi, + struct drm_display_mode *mode); +void vc4_hdmi_phy_disable(struct vc4_hdmi *vc4_hdmi); +void vc4_hdmi_phy_rng_enable(struct vc4_hdmi *vc4_hdmi); +void vc4_hdmi_phy_rng_disable(struct vc4_hdmi *vc4_hdmi); + +void vc5_hdmi_phy_init(struct vc4_hdmi *vc4_hdmi, + struct drm_display_mode *mode); +void vc5_hdmi_phy_disable(struct vc4_hdmi *vc4_hdmi); +void vc5_hdmi_phy_rng_enable(struct vc4_hdmi *vc4_hdmi); +void vc5_hdmi_phy_rng_disable(struct vc4_hdmi *vc4_hdmi); + +#endif /* _VC4_HDMI_H_ */ diff --git a/drivers/gpu/drm/vc4/vc4_hdmi_phy.c b/drivers/gpu/drm/vc4/vc4_hdmi_phy.c new file mode 100644 index 000000000000..057796b54c51 --- /dev/null +++ b/drivers/gpu/drm/vc4/vc4_hdmi_phy.c @@ -0,0 +1,521 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2015 Broadcom + * Copyright (c) 2014 The Linux Foundation. All rights reserved. + * Copyright (C) 2013 Red Hat + * Author: Rob Clark <robdclark@gmail.com> + */ + +#include "vc4_hdmi.h" +#include "vc4_regs.h" +#include "vc4_hdmi_regs.h" + +#define VC4_HDMI_TX_PHY_RESET_CTL_PLL_RESETB BIT(5) +#define VC4_HDMI_TX_PHY_RESET_CTL_PLLDIV_RESETB BIT(4) +#define VC4_HDMI_TX_PHY_RESET_CTL_TX_CK_RESET BIT(3) +#define VC4_HDMI_TX_PHY_RESET_CTL_TX_2_RESET BIT(2) +#define VC4_HDMI_TX_PHY_RESET_CTL_TX_1_RESET BIT(1) +#define VC4_HDMI_TX_PHY_RESET_CTL_TX_0_RESET BIT(0) + +#define VC4_HDMI_TX_PHY_POWERDOWN_CTL_RNDGEN_PWRDN BIT(4) + +#define VC4_HDMI_TX_PHY_CTL_0_PREEMP_2_PREEMP_SHIFT 29 +#define VC4_HDMI_TX_PHY_CTL_0_PREEMP_2_PREEMP_MASK VC4_MASK(31, 29) +#define VC4_HDMI_TX_PHY_CTL_0_PREEMP_2_MAINDRV_SHIFT 24 +#define VC4_HDMI_TX_PHY_CTL_0_PREEMP_2_MAINDRV_MASK VC4_MASK(28, 24) +#define VC4_HDMI_TX_PHY_CTL_0_PREEMP_1_PREEMP_SHIFT 21 +#define VC4_HDMI_TX_PHY_CTL_0_PREEMP_1_PREEMP_MASK VC4_MASK(23, 21) +#define VC4_HDMI_TX_PHY_CTL_0_PREEMP_1_MAINDRV_SHIFT 16 +#define VC4_HDMI_TX_PHY_CTL_0_PREEMP_1_MAINDRV_MASK VC4_MASK(20, 16) +#define VC4_HDMI_TX_PHY_CTL_0_PREEMP_0_PREEMP_SHIFT 13 +#define VC4_HDMI_TX_PHY_CTL_0_PREEMP_0_PREEMP_MASK VC4_MASK(15, 13) +#define VC4_HDMI_TX_PHY_CTL_0_PREEMP_0_MAINDRV_SHIFT 8 +#define VC4_HDMI_TX_PHY_CTL_0_PREEMP_0_MAINDRV_MASK VC4_MASK(12, 8) +#define VC4_HDMI_TX_PHY_CTL_0_PREEMP_CK_PREEMP_SHIFT 5 +#define VC4_HDMI_TX_PHY_CTL_0_PREEMP_CK_PREEMP_MASK VC4_MASK(7, 5) +#define VC4_HDMI_TX_PHY_CTL_0_PREEMP_CK_MAINDRV_SHIFT 0 +#define VC4_HDMI_TX_PHY_CTL_0_PREEMP_CK_MAINDRV_MASK VC4_MASK(4, 0) + +#define VC4_HDMI_TX_PHY_CTL_1_RES_SEL_DATA2_SHIFT 15 +#define VC4_HDMI_TX_PHY_CTL_1_RES_SEL_DATA2_MASK VC4_MASK(19, 15) +#define VC4_HDMI_TX_PHY_CTL_1_RES_SEL_DATA1_SHIFT 10 +#define VC4_HDMI_TX_PHY_CTL_1_RES_SEL_DATA1_MASK VC4_MASK(14, 10) +#define VC4_HDMI_TX_PHY_CTL_1_RES_SEL_DATA0_SHIFT 5 +#define VC4_HDMI_TX_PHY_CTL_1_RES_SEL_DATA0_MASK VC4_MASK(9, 5) +#define VC4_HDMI_TX_PHY_CTL_1_RES_SEL_CK_SHIFT 0 +#define VC4_HDMI_TX_PHY_CTL_1_RES_SEL_CK_MASK VC4_MASK(4, 0) + +#define VC4_HDMI_TX_PHY_CTL_2_VCO_GAIN_SHIFT 16 +#define VC4_HDMI_TX_PHY_CTL_2_VCO_GAIN_MASK VC4_MASK(19, 16) +#define VC4_HDMI_TX_PHY_CTL_2_TERM_RES_SELDATA2_SHIFT 12 +#define VC4_HDMI_TX_PHY_CTL_2_TERM_RES_SELDATA2_MASK VC4_MASK(15, 12) +#define VC4_HDMI_TX_PHY_CTL_2_TERM_RES_SELDATA1_SHIFT 8 +#define VC4_HDMI_TX_PHY_CTL_2_TERM_RES_SELDATA1_MASK VC4_MASK(11, 8) +#define VC4_HDMI_TX_PHY_CTL_2_TERM_RES_SELDATA0_SHIFT 4 +#define VC4_HDMI_TX_PHY_CTL_2_TERM_RES_SELDATA0_MASK VC4_MASK(7, 4) +#define VC4_HDMI_TX_PHY_CTL_2_TERM_RES_SELCK_SHIFT 0 +#define VC4_HDMI_TX_PHY_CTL_2_TERM_RES_SELCK_MASK VC4_MASK(3, 0) + +#define VC4_HDMI_TX_PHY_CTL_3_RP_SHIFT 17 +#define VC4_HDMI_TX_PHY_CTL_3_RP_MASK VC4_MASK(19, 17) +#define VC4_HDMI_TX_PHY_CTL_3_RZ_SHIFT 12 +#define VC4_HDMI_TX_PHY_CTL_3_RZ_MASK VC4_MASK(16, 12) +#define VC4_HDMI_TX_PHY_CTL_3_CP1_SHIFT 10 +#define VC4_HDMI_TX_PHY_CTL_3_CP1_MASK VC4_MASK(11, 10) +#define VC4_HDMI_TX_PHY_CTL_3_CP_SHIFT 8 +#define VC4_HDMI_TX_PHY_CTL_3_CP_MASK VC4_MASK(9, 8) +#define VC4_HDMI_TX_PHY_CTL_3_CZ_SHIFT 6 +#define VC4_HDMI_TX_PHY_CTL_3_CZ_MASK VC4_MASK(7, 6) +#define VC4_HDMI_TX_PHY_CTL_3_ICP_SHIFT 0 +#define VC4_HDMI_TX_PHY_CTL_3_ICP_MASK VC4_MASK(5, 0) + +#define VC4_HDMI_TX_PHY_PLL_CTL_0_MASH11_MODE BIT(13) +#define VC4_HDMI_TX_PHY_PLL_CTL_0_VC_RANGE_EN BIT(12) +#define VC4_HDMI_TX_PHY_PLL_CTL_0_EMULATE_VC_LOW BIT(11) +#define VC4_HDMI_TX_PHY_PLL_CTL_0_EMULATE_VC_HIGH BIT(10) +#define VC4_HDMI_TX_PHY_PLL_CTL_0_VCO_SEL_SHIFT 9 +#define VC4_HDMI_TX_PHY_PLL_CTL_0_VCO_SEL_MASK VC4_MASK(9, 9) +#define VC4_HDMI_TX_PHY_PLL_CTL_0_VCO_FB_DIV2 BIT(8) +#define VC4_HDMI_TX_PHY_PLL_CTL_0_VCO_POST_DIV2 BIT(7) +#define VC4_HDMI_TX_PHY_PLL_CTL_0_VCO_CONT_EN BIT(6) +#define VC4_HDMI_TX_PHY_PLL_CTL_0_ENA_VCO_CLK BIT(5) + +#define VC4_HDMI_TX_PHY_PLL_CTL_1_CPP_SHIFT 16 +#define VC4_HDMI_TX_PHY_PLL_CTL_1_CPP_MASK VC4_MASK(27, 16) +#define VC4_HDMI_TX_PHY_PLL_CTL_1_FREQ_DOUBLER_DELAY_SHIFT 14 +#define VC4_HDMI_TX_PHY_PLL_CTL_1_FREQ_DOUBLER_DELAY_MASK VC4_MASK(15, 14) +#define VC4_HDMI_TX_PHY_PLL_CTL_1_FREQ_DOUBLER_ENABLE BIT(13) +#define VC4_HDMI_TX_PHY_PLL_CTL_1_POST_RST_SEL_SHIFT 11 +#define VC4_HDMI_TX_PHY_PLL_CTL_1_POST_RST_SEL_MASK VC4_MASK(12, 11) + +#define VC4_HDMI_TX_PHY_CLK_DIV_VCO_SHIFT 8 +#define VC4_HDMI_TX_PHY_CLK_DIV_VCO_MASK VC4_MASK(15, 8) + +#define VC4_HDMI_TX_PHY_PLL_CFG_PDIV_SHIFT 0 +#define VC4_HDMI_TX_PHY_PLL_CFG_PDIV_MASK VC4_MASK(3, 0) + +#define VC4_HDMI_TX_PHY_CHANNEL_SWAP_TXCK_OUT_SEL_MASK VC4_MASK(13, 12) +#define VC4_HDMI_TX_PHY_CHANNEL_SWAP_TXCK_OUT_SEL_SHIFT 12 +#define VC4_HDMI_TX_PHY_CHANNEL_SWAP_TX2_OUT_SEL_MASK VC4_MASK(9, 8) +#define VC4_HDMI_TX_PHY_CHANNEL_SWAP_TX2_OUT_SEL_SHIFT 8 +#define VC4_HDMI_TX_PHY_CHANNEL_SWAP_TX1_OUT_SEL_MASK VC4_MASK(5, 4) +#define VC4_HDMI_TX_PHY_CHANNEL_SWAP_TX1_OUT_SEL_SHIFT 4 +#define VC4_HDMI_TX_PHY_CHANNEL_SWAP_TX0_OUT_SEL_MASK VC4_MASK(1, 0) +#define VC4_HDMI_TX_PHY_CHANNEL_SWAP_TX0_OUT_SEL_SHIFT 0 + +#define VC4_HDMI_TX_PHY_PLL_CALIBRATION_CONFIG_1_MIN_LIMIT_MASK VC4_MASK(27, 0) +#define VC4_HDMI_TX_PHY_PLL_CALIBRATION_CONFIG_1_MIN_LIMIT_SHIFT 0 + +#define VC4_HDMI_TX_PHY_PLL_CALIBRATION_CONFIG_2_MAX_LIMIT_MASK VC4_MASK(27, 0) +#define VC4_HDMI_TX_PHY_PLL_CALIBRATION_CONFIG_2_MAX_LIMIT_SHIFT 0 + +#define VC4_HDMI_TX_PHY_PLL_CALIBRATION_CONFIG_4_STABLE_THRESHOLD_MASK VC4_MASK(31, 16) +#define VC4_HDMI_TX_PHY_PLL_CALIBRATION_CONFIG_4_STABLE_THRESHOLD_SHIFT 16 +#define VC4_HDMI_TX_PHY_PLL_CALIBRATION_CONFIG_4_HOLD_THRESHOLD_MASK VC4_MASK(15, 0) +#define VC4_HDMI_TX_PHY_PLL_CALIBRATION_CONFIG_4_HOLD_THRESHOLD_SHIFT 0 + +#define VC4_HDMI_RM_CONTROL_EN_FREEZE_COUNTERS BIT(19) +#define VC4_HDMI_RM_CONTROL_EN_LOAD_INTEGRATOR BIT(17) +#define VC4_HDMI_RM_CONTROL_FREE_RUN BIT(4) + +#define VC4_HDMI_RM_OFFSET_ONLY BIT(31) +#define VC4_HDMI_RM_OFFSET_OFFSET_SHIFT 0 +#define VC4_HDMI_RM_OFFSET_OFFSET_MASK VC4_MASK(30, 0) + +#define VC4_HDMI_RM_FORMAT_SHIFT_SHIFT 24 +#define VC4_HDMI_RM_FORMAT_SHIFT_MASK VC4_MASK(25, 24) + +#define OSCILLATOR_FREQUENCY 54000000 + +void vc4_hdmi_phy_init(struct vc4_hdmi *vc4_hdmi, struct drm_display_mode *mode) +{ + /* PHY should be in reset, like + * vc4_hdmi_encoder_disable() does. + */ + + HDMI_WRITE(HDMI_TX_PHY_RESET_CTL, 0xf << 16); + HDMI_WRITE(HDMI_TX_PHY_RESET_CTL, 0); +} + +void vc4_hdmi_phy_disable(struct vc4_hdmi *vc4_hdmi) +{ + HDMI_WRITE(HDMI_TX_PHY_RESET_CTL, 0xf << 16); +} + +void vc4_hdmi_phy_rng_enable(struct vc4_hdmi *vc4_hdmi) +{ + HDMI_WRITE(HDMI_TX_PHY_CTL_0, + HDMI_READ(HDMI_TX_PHY_CTL_0) & + ~VC4_HDMI_TX_PHY_RNG_PWRDN); +} + +void vc4_hdmi_phy_rng_disable(struct vc4_hdmi *vc4_hdmi) +{ + HDMI_WRITE(HDMI_TX_PHY_CTL_0, + HDMI_READ(HDMI_TX_PHY_CTL_0) | + VC4_HDMI_TX_PHY_RNG_PWRDN); +} + +static unsigned long long +phy_get_vco_freq(unsigned long long clock, u8 *vco_sel, u8 *vco_div) +{ + unsigned long long vco_freq = clock; + unsigned int _vco_div = 0; + unsigned int _vco_sel = 0; + + while (vco_freq < 3000000000ULL) { + _vco_div++; + vco_freq = clock * _vco_div * 10; + } + + if (vco_freq > 4500000000ULL) + _vco_sel = 1; + + *vco_sel = _vco_sel; + *vco_div = _vco_div; + + return vco_freq; +} + +static u8 phy_get_cp_current(unsigned long vco_freq) +{ + if (vco_freq < 3700000000ULL) + return 0x1c; + + return 0x18; +} + +static u32 phy_get_rm_offset(unsigned long long vco_freq) +{ + unsigned long long fref = OSCILLATOR_FREQUENCY; + u64 offset = 0; + + /* RM offset is stored as 9.22 format */ + offset = vco_freq * 2; + offset = offset << 22; + do_div(offset, fref); + offset >>= 2; + + return offset; +} + +static u8 phy_get_vco_gain(unsigned long long vco_freq) +{ + if (vco_freq < 3350000000ULL) + return 0xf; + + if (vco_freq < 3700000000ULL) + return 0xc; + + if (vco_freq < 4050000000ULL) + return 0x6; + + if (vco_freq < 4800000000ULL) + return 0x5; + + if (vco_freq < 5200000000ULL) + return 0x7; + + return 0x2; +} + +struct phy_lane_settings { + struct { + u8 preemphasis; + u8 main_driver; + } amplitude; + + u8 res_sel_data; + u8 term_res_sel_data; +}; + +struct phy_settings { + unsigned long long min_rate; + unsigned long long max_rate; + struct phy_lane_settings channel[3]; + struct phy_lane_settings clock; +}; + +static const struct phy_settings vc5_hdmi_phy_settings[] = { + { + 0, 50000000, + { + {{0x0, 0x0A}, 0x12, 0x0}, + {{0x0, 0x0A}, 0x12, 0x0}, + {{0x0, 0x0A}, 0x12, 0x0} + }, + {{0x0, 0x0A}, 0x18, 0x0}, + }, + { + 50000001, 75000000, + { + {{0x0, 0x09}, 0x12, 0x0}, + {{0x0, 0x09}, 0x12, 0x0}, + {{0x0, 0x09}, 0x12, 0x0} + }, + {{0x0, 0x0C}, 0x18, 0x3}, + }, + { + 75000001, 165000000, + { + {{0x0, 0x09}, 0x12, 0x0}, + {{0x0, 0x09}, 0x12, 0x0}, + {{0x0, 0x09}, 0x12, 0x0} + }, + {{0x0, 0x0C}, 0x18, 0x3}, + }, + { + 165000001, 250000000, + { + {{0x0, 0x0F}, 0x12, 0x1}, + {{0x0, 0x0F}, 0x12, 0x1}, + {{0x0, 0x0F}, 0x12, 0x1} + }, + {{0x0, 0x0C}, 0x18, 0x3}, + }, + { + 250000001, 340000000, + { + {{0x2, 0x0D}, 0x12, 0x1}, + {{0x2, 0x0D}, 0x12, 0x1}, + {{0x2, 0x0D}, 0x12, 0x1} + }, + {{0x0, 0x0C}, 0x18, 0xF}, + }, + { + 340000001, 450000000, + { + {{0x0, 0x1B}, 0x12, 0xF}, + {{0x0, 0x1B}, 0x12, 0xF}, + {{0x0, 0x1B}, 0x12, 0xF} + }, + {{0x0, 0x0A}, 0x12, 0xF}, + }, + { + 450000001, 600000000, + { + {{0x0, 0x1C}, 0x12, 0xF}, + {{0x0, 0x1C}, 0x12, 0xF}, + {{0x0, 0x1C}, 0x12, 0xF} + }, + {{0x0, 0x0B}, 0x13, 0xF}, + }, +}; + +static const struct phy_settings *phy_get_settings(unsigned long long tmds_rate) +{ + unsigned int count = ARRAY_SIZE(vc5_hdmi_phy_settings); + unsigned int i; + + for (i = 0; i < count; i++) { + const struct phy_settings *s = &vc5_hdmi_phy_settings[i]; + + if (tmds_rate >= s->min_rate && tmds_rate <= s->max_rate) + return s; + } + + /* + * If the pixel clock exceeds our max setting, try the max + * setting anyway. + */ + return &vc5_hdmi_phy_settings[count - 1]; +} + +static const struct phy_lane_settings * +phy_get_channel_settings(enum vc4_hdmi_phy_channel chan, + unsigned long long tmds_rate) +{ + const struct phy_settings *settings = phy_get_settings(tmds_rate); + + if (chan == PHY_LANE_CK) + return &settings->clock; + + return &settings->channel[chan]; +} + +static void vc5_hdmi_reset_phy(struct vc4_hdmi *vc4_hdmi) +{ + HDMI_WRITE(HDMI_TX_PHY_RESET_CTL, 0x0f); + HDMI_WRITE(HDMI_TX_PHY_POWERDOWN_CTL, BIT(10)); +} + +void vc5_hdmi_phy_init(struct vc4_hdmi *vc4_hdmi, struct drm_display_mode *mode) +{ + const struct phy_lane_settings *chan0_settings, *chan1_settings, *chan2_settings, *clock_settings; + const struct vc4_hdmi_variant *variant = vc4_hdmi->variant; + unsigned long long pixel_freq = mode->clock * 1000; + unsigned long long vco_freq; + unsigned char word_sel; + u8 vco_sel, vco_div; + + vco_freq = phy_get_vco_freq(pixel_freq, &vco_sel, &vco_div); + + vc5_hdmi_reset_phy(vc4_hdmi); + + HDMI_WRITE(HDMI_TX_PHY_POWERDOWN_CTL, + VC4_HDMI_TX_PHY_POWERDOWN_CTL_RNDGEN_PWRDN); + + HDMI_WRITE(HDMI_TX_PHY_RESET_CTL, + HDMI_READ(HDMI_TX_PHY_RESET_CTL) & + ~VC4_HDMI_TX_PHY_RESET_CTL_TX_0_RESET & + ~VC4_HDMI_TX_PHY_RESET_CTL_TX_1_RESET & + ~VC4_HDMI_TX_PHY_RESET_CTL_TX_2_RESET & + ~VC4_HDMI_TX_PHY_RESET_CTL_TX_CK_RESET); + + HDMI_WRITE(HDMI_RM_CONTROL, + HDMI_READ(HDMI_RM_CONTROL) | + VC4_HDMI_RM_CONTROL_EN_FREEZE_COUNTERS | + VC4_HDMI_RM_CONTROL_EN_LOAD_INTEGRATOR | + VC4_HDMI_RM_CONTROL_FREE_RUN); + + HDMI_WRITE(HDMI_TX_PHY_PLL_CALIBRATION_CONFIG_1, + (HDMI_READ(HDMI_TX_PHY_PLL_CALIBRATION_CONFIG_1) & + ~VC4_HDMI_TX_PHY_PLL_CALIBRATION_CONFIG_1_MIN_LIMIT_MASK) | + VC4_SET_FIELD(0, VC4_HDMI_TX_PHY_PLL_CALIBRATION_CONFIG_1_MIN_LIMIT)); + + HDMI_WRITE(HDMI_TX_PHY_PLL_CALIBRATION_CONFIG_2, + (HDMI_READ(HDMI_TX_PHY_PLL_CALIBRATION_CONFIG_2) & + ~VC4_HDMI_TX_PHY_PLL_CALIBRATION_CONFIG_2_MAX_LIMIT_MASK) | + VC4_SET_FIELD(0, VC4_HDMI_TX_PHY_PLL_CALIBRATION_CONFIG_2_MAX_LIMIT)); + + HDMI_WRITE(HDMI_RM_OFFSET, + VC4_SET_FIELD(phy_get_rm_offset(vco_freq), + VC4_HDMI_RM_OFFSET_OFFSET) | + VC4_HDMI_RM_OFFSET_ONLY); + + HDMI_WRITE(HDMI_TX_PHY_CLK_DIV, + VC4_SET_FIELD(vco_div, VC4_HDMI_TX_PHY_CLK_DIV_VCO)); + + HDMI_WRITE(HDMI_TX_PHY_PLL_CALIBRATION_CONFIG_4, + VC4_SET_FIELD(0xe147, VC4_HDMI_TX_PHY_PLL_CALIBRATION_CONFIG_4_HOLD_THRESHOLD) | + VC4_SET_FIELD(0xe14, VC4_HDMI_TX_PHY_PLL_CALIBRATION_CONFIG_4_STABLE_THRESHOLD)); + + HDMI_WRITE(HDMI_TX_PHY_PLL_CTL_0, + VC4_HDMI_TX_PHY_PLL_CTL_0_ENA_VCO_CLK | + VC4_HDMI_TX_PHY_PLL_CTL_0_VCO_CONT_EN | + VC4_HDMI_TX_PHY_PLL_CTL_0_MASH11_MODE | + VC4_SET_FIELD(vco_sel, VC4_HDMI_TX_PHY_PLL_CTL_0_VCO_SEL)); + + HDMI_WRITE(HDMI_TX_PHY_PLL_CTL_1, + HDMI_READ(HDMI_TX_PHY_PLL_CTL_1) | + VC4_HDMI_TX_PHY_PLL_CTL_1_FREQ_DOUBLER_ENABLE | + VC4_SET_FIELD(3, VC4_HDMI_TX_PHY_PLL_CTL_1_POST_RST_SEL) | + VC4_SET_FIELD(1, VC4_HDMI_TX_PHY_PLL_CTL_1_FREQ_DOUBLER_DELAY) | + VC4_SET_FIELD(0x8a, VC4_HDMI_TX_PHY_PLL_CTL_1_CPP)); + + HDMI_WRITE(HDMI_RM_FORMAT, + HDMI_READ(HDMI_RM_FORMAT) | + VC4_SET_FIELD(2, VC4_HDMI_RM_FORMAT_SHIFT)); + + HDMI_WRITE(HDMI_TX_PHY_PLL_CFG, + HDMI_READ(HDMI_TX_PHY_PLL_CFG) | + VC4_SET_FIELD(1, VC4_HDMI_TX_PHY_PLL_CFG_PDIV)); + + if (pixel_freq >= 340000000) + word_sel = 3; + else + word_sel = 0; + HDMI_WRITE(HDMI_TX_PHY_TMDS_CLK_WORD_SEL, word_sel); + + HDMI_WRITE(HDMI_TX_PHY_CTL_3, + VC4_SET_FIELD(phy_get_cp_current(vco_freq), + VC4_HDMI_TX_PHY_CTL_3_ICP) | + VC4_SET_FIELD(1, VC4_HDMI_TX_PHY_CTL_3_CP) | + VC4_SET_FIELD(1, VC4_HDMI_TX_PHY_CTL_3_CP1) | + VC4_SET_FIELD(3, VC4_HDMI_TX_PHY_CTL_3_CZ) | + VC4_SET_FIELD(4, VC4_HDMI_TX_PHY_CTL_3_RP) | + VC4_SET_FIELD(6, VC4_HDMI_TX_PHY_CTL_3_RZ)); + + chan0_settings = + phy_get_channel_settings(variant->phy_lane_mapping[PHY_LANE_0], + pixel_freq); + chan1_settings = + phy_get_channel_settings(variant->phy_lane_mapping[PHY_LANE_1], + pixel_freq); + chan2_settings = + phy_get_channel_settings(variant->phy_lane_mapping[PHY_LANE_2], + pixel_freq); + clock_settings = + phy_get_channel_settings(variant->phy_lane_mapping[PHY_LANE_CK], + pixel_freq); + + HDMI_WRITE(HDMI_TX_PHY_CTL_0, + VC4_SET_FIELD(chan0_settings->amplitude.preemphasis, + VC4_HDMI_TX_PHY_CTL_0_PREEMP_0_PREEMP) | + VC4_SET_FIELD(chan0_settings->amplitude.main_driver, + VC4_HDMI_TX_PHY_CTL_0_PREEMP_0_MAINDRV) | + VC4_SET_FIELD(chan1_settings->amplitude.preemphasis, + VC4_HDMI_TX_PHY_CTL_0_PREEMP_1_PREEMP) | + VC4_SET_FIELD(chan1_settings->amplitude.main_driver, + VC4_HDMI_TX_PHY_CTL_0_PREEMP_1_MAINDRV) | + VC4_SET_FIELD(chan2_settings->amplitude.preemphasis, + VC4_HDMI_TX_PHY_CTL_0_PREEMP_2_PREEMP) | + VC4_SET_FIELD(chan2_settings->amplitude.main_driver, + VC4_HDMI_TX_PHY_CTL_0_PREEMP_2_MAINDRV) | + VC4_SET_FIELD(clock_settings->amplitude.preemphasis, + VC4_HDMI_TX_PHY_CTL_0_PREEMP_CK_PREEMP) | + VC4_SET_FIELD(clock_settings->amplitude.main_driver, + VC4_HDMI_TX_PHY_CTL_0_PREEMP_CK_MAINDRV)); + + HDMI_WRITE(HDMI_TX_PHY_CTL_1, + HDMI_READ(HDMI_TX_PHY_CTL_1) | + VC4_SET_FIELD(chan0_settings->res_sel_data, + VC4_HDMI_TX_PHY_CTL_1_RES_SEL_DATA0) | + VC4_SET_FIELD(chan1_settings->res_sel_data, + VC4_HDMI_TX_PHY_CTL_1_RES_SEL_DATA1) | + VC4_SET_FIELD(chan2_settings->res_sel_data, + VC4_HDMI_TX_PHY_CTL_1_RES_SEL_DATA2) | + VC4_SET_FIELD(clock_settings->res_sel_data, + VC4_HDMI_TX_PHY_CTL_1_RES_SEL_CK)); + + HDMI_WRITE(HDMI_TX_PHY_CTL_2, + VC4_SET_FIELD(chan0_settings->term_res_sel_data, + VC4_HDMI_TX_PHY_CTL_2_TERM_RES_SELDATA0) | + VC4_SET_FIELD(chan1_settings->term_res_sel_data, + VC4_HDMI_TX_PHY_CTL_2_TERM_RES_SELDATA1) | + VC4_SET_FIELD(chan2_settings->term_res_sel_data, + VC4_HDMI_TX_PHY_CTL_2_TERM_RES_SELDATA2) | + VC4_SET_FIELD(clock_settings->term_res_sel_data, + VC4_HDMI_TX_PHY_CTL_2_TERM_RES_SELCK) | + VC4_SET_FIELD(phy_get_vco_gain(vco_freq), + VC4_HDMI_TX_PHY_CTL_2_VCO_GAIN)); + + HDMI_WRITE(HDMI_TX_PHY_CHANNEL_SWAP, + VC4_SET_FIELD(variant->phy_lane_mapping[PHY_LANE_0], + VC4_HDMI_TX_PHY_CHANNEL_SWAP_TX0_OUT_SEL) | + VC4_SET_FIELD(variant->phy_lane_mapping[PHY_LANE_1], + VC4_HDMI_TX_PHY_CHANNEL_SWAP_TX1_OUT_SEL) | + VC4_SET_FIELD(variant->phy_lane_mapping[PHY_LANE_2], + VC4_HDMI_TX_PHY_CHANNEL_SWAP_TX2_OUT_SEL) | + VC4_SET_FIELD(variant->phy_lane_mapping[PHY_LANE_CK], + VC4_HDMI_TX_PHY_CHANNEL_SWAP_TXCK_OUT_SEL)); + + HDMI_WRITE(HDMI_TX_PHY_RESET_CTL, + HDMI_READ(HDMI_TX_PHY_RESET_CTL) & + ~(VC4_HDMI_TX_PHY_RESET_CTL_PLL_RESETB | + VC4_HDMI_TX_PHY_RESET_CTL_PLLDIV_RESETB)); + + HDMI_WRITE(HDMI_TX_PHY_RESET_CTL, + HDMI_READ(HDMI_TX_PHY_RESET_CTL) | + VC4_HDMI_TX_PHY_RESET_CTL_PLL_RESETB | + VC4_HDMI_TX_PHY_RESET_CTL_PLLDIV_RESETB); +} + +void vc5_hdmi_phy_disable(struct vc4_hdmi *vc4_hdmi) +{ + vc5_hdmi_reset_phy(vc4_hdmi); +} + +void vc5_hdmi_phy_rng_enable(struct vc4_hdmi *vc4_hdmi) +{ + HDMI_WRITE(HDMI_TX_PHY_POWERDOWN_CTL, + HDMI_READ(HDMI_TX_PHY_POWERDOWN_CTL) & + ~VC4_HDMI_TX_PHY_POWERDOWN_CTL_RNDGEN_PWRDN); +} + +void vc5_hdmi_phy_rng_disable(struct vc4_hdmi *vc4_hdmi) +{ + HDMI_WRITE(HDMI_TX_PHY_POWERDOWN_CTL, + HDMI_READ(HDMI_TX_PHY_POWERDOWN_CTL) | + VC4_HDMI_TX_PHY_POWERDOWN_CTL_RNDGEN_PWRDN); +} diff --git a/drivers/gpu/drm/vc4/vc4_hdmi_regs.h b/drivers/gpu/drm/vc4/vc4_hdmi_regs.h new file mode 100644 index 000000000000..7c6b4818f245 --- /dev/null +++ b/drivers/gpu/drm/vc4/vc4_hdmi_regs.h @@ -0,0 +1,442 @@ +#ifndef _VC4_HDMI_REGS_H_ +#define _VC4_HDMI_REGS_H_ + +#include "vc4_hdmi.h" + +#define VC4_HDMI_PACKET_STRIDE 0x24 + +enum vc4_hdmi_regs { + VC4_INVALID = 0, + VC4_HDMI, + VC4_HD, + VC5_CEC, + VC5_CSC, + VC5_DVP, + VC5_PHY, + VC5_RAM, + VC5_RM, +}; + +enum vc4_hdmi_field { + HDMI_AUDIO_PACKET_CONFIG, + HDMI_CEC_CNTRL_1, + HDMI_CEC_CNTRL_2, + HDMI_CEC_CNTRL_3, + HDMI_CEC_CNTRL_4, + HDMI_CEC_CNTRL_5, + HDMI_CEC_CPU_CLEAR, + HDMI_CEC_CPU_MASK_CLEAR, + HDMI_CEC_CPU_MASK_SET, + HDMI_CEC_CPU_MASK_STATUS, + HDMI_CEC_CPU_STATUS, + + /* + * Transmit data, first byte is low byte of the 32-bit reg. + * MSB of each byte transmitted first. + */ + HDMI_CEC_RX_DATA_1, + HDMI_CEC_RX_DATA_2, + HDMI_CEC_RX_DATA_3, + HDMI_CEC_RX_DATA_4, + HDMI_CEC_TX_DATA_1, + HDMI_CEC_TX_DATA_2, + HDMI_CEC_TX_DATA_3, + HDMI_CEC_TX_DATA_4, + HDMI_CLOCK_STOP, + HDMI_CORE_REV, + HDMI_CRP_CFG, + HDMI_CSC_12_11, + HDMI_CSC_14_13, + HDMI_CSC_22_21, + HDMI_CSC_24_23, + HDMI_CSC_32_31, + HDMI_CSC_34_33, + HDMI_CSC_CTL, + + /* + * 20-bit fields containing CTS values to be transmitted if + * !EXTERNAL_CTS_EN + */ + HDMI_CTS_0, + HDMI_CTS_1, + HDMI_DVP_CTL, + HDMI_FIFO_CTL, + HDMI_FRAME_COUNT, + HDMI_HORZA, + HDMI_HORZB, + HDMI_HOTPLUG, + HDMI_HOTPLUG_INT, + + /* + * 3 bits per field, where each field maps from that + * corresponding MAI bus channel to the given HDMI channel. + */ + HDMI_MAI_CHANNEL_MAP, + HDMI_MAI_CONFIG, + HDMI_MAI_CTL, + + /* + * Register for DMAing in audio data to be transported over + * the MAI bus to the Falcon core. + */ + HDMI_MAI_DATA, + + /* Format header to be placed on the MAI data. Unused. */ + HDMI_MAI_FMT, + + /* Last received format word on the MAI bus. */ + HDMI_MAI_FORMAT, + HDMI_MAI_SMP, + HDMI_MAI_THR, + HDMI_M_CTL, + HDMI_RAM_PACKET_CONFIG, + HDMI_RAM_PACKET_START, + HDMI_RAM_PACKET_STATUS, + HDMI_RM_CONTROL, + HDMI_RM_FORMAT, + HDMI_RM_OFFSET, + HDMI_SCHEDULER_CONTROL, + HDMI_SW_RESET_CONTROL, + HDMI_TX_PHY_CHANNEL_SWAP, + HDMI_TX_PHY_CLK_DIV, + HDMI_TX_PHY_CTL_0, + HDMI_TX_PHY_CTL_1, + HDMI_TX_PHY_CTL_2, + HDMI_TX_PHY_CTL_3, + HDMI_TX_PHY_PLL_CALIBRATION_CONFIG_1, + HDMI_TX_PHY_PLL_CALIBRATION_CONFIG_2, + HDMI_TX_PHY_PLL_CALIBRATION_CONFIG_4, + HDMI_TX_PHY_PLL_CFG, + HDMI_TX_PHY_PLL_CTL_0, + HDMI_TX_PHY_PLL_CTL_1, + HDMI_TX_PHY_POWERDOWN_CTL, + HDMI_TX_PHY_RESET_CTL, + HDMI_TX_PHY_TMDS_CLK_WORD_SEL, + HDMI_VEC_INTERFACE_XBAR, + HDMI_VERTA0, + HDMI_VERTA1, + HDMI_VERTB0, + HDMI_VERTB1, + HDMI_VID_CTL, +}; + +struct vc4_hdmi_register { + char *name; + enum vc4_hdmi_regs reg; + unsigned int offset; +}; + +#define _VC4_REG(_base, _reg, _offset) \ + [_reg] = { \ + .name = #_reg, \ + .reg = _base, \ + .offset = _offset, \ + } + +#define VC4_HD_REG(reg, offset) _VC4_REG(VC4_HD, reg, offset) +#define VC4_HDMI_REG(reg, offset) _VC4_REG(VC4_HDMI, reg, offset) +#define VC5_CEC_REG(reg, offset) _VC4_REG(VC5_CEC, reg, offset) +#define VC5_CSC_REG(reg, offset) _VC4_REG(VC5_CSC, reg, offset) +#define VC5_DVP_REG(reg, offset) _VC4_REG(VC5_DVP, reg, offset) +#define VC5_PHY_REG(reg, offset) _VC4_REG(VC5_PHY, reg, offset) +#define VC5_RAM_REG(reg, offset) _VC4_REG(VC5_RAM, reg, offset) +#define VC5_RM_REG(reg, offset) _VC4_REG(VC5_RM, reg, offset) + +static const struct vc4_hdmi_register vc4_hdmi_fields[] = { + VC4_HD_REG(HDMI_M_CTL, 0x000c), + VC4_HD_REG(HDMI_MAI_CTL, 0x0014), + VC4_HD_REG(HDMI_MAI_THR, 0x0018), + VC4_HD_REG(HDMI_MAI_FMT, 0x001c), + VC4_HD_REG(HDMI_MAI_DATA, 0x0020), + VC4_HD_REG(HDMI_MAI_SMP, 0x002c), + VC4_HD_REG(HDMI_VID_CTL, 0x0038), + VC4_HD_REG(HDMI_CSC_CTL, 0x0040), + VC4_HD_REG(HDMI_CSC_12_11, 0x0044), + VC4_HD_REG(HDMI_CSC_14_13, 0x0048), + VC4_HD_REG(HDMI_CSC_22_21, 0x004c), + VC4_HD_REG(HDMI_CSC_24_23, 0x0050), + VC4_HD_REG(HDMI_CSC_32_31, 0x0054), + VC4_HD_REG(HDMI_CSC_34_33, 0x0058), + VC4_HD_REG(HDMI_FRAME_COUNT, 0x0068), + + VC4_HDMI_REG(HDMI_CORE_REV, 0x0000), + VC4_HDMI_REG(HDMI_SW_RESET_CONTROL, 0x0004), + VC4_HDMI_REG(HDMI_HOTPLUG_INT, 0x0008), + VC4_HDMI_REG(HDMI_HOTPLUG, 0x000c), + VC4_HDMI_REG(HDMI_FIFO_CTL, 0x005c), + VC4_HDMI_REG(HDMI_MAI_CHANNEL_MAP, 0x0090), + VC4_HDMI_REG(HDMI_MAI_CONFIG, 0x0094), + VC4_HDMI_REG(HDMI_MAI_FORMAT, 0x0098), + VC4_HDMI_REG(HDMI_AUDIO_PACKET_CONFIG, 0x009c), + VC4_HDMI_REG(HDMI_RAM_PACKET_CONFIG, 0x00a0), + VC4_HDMI_REG(HDMI_RAM_PACKET_STATUS, 0x00a4), + VC4_HDMI_REG(HDMI_CRP_CFG, 0x00a8), + VC4_HDMI_REG(HDMI_CTS_0, 0x00ac), + VC4_HDMI_REG(HDMI_CTS_1, 0x00b0), + VC4_HDMI_REG(HDMI_SCHEDULER_CONTROL, 0x00c0), + VC4_HDMI_REG(HDMI_HORZA, 0x00c4), + VC4_HDMI_REG(HDMI_HORZB, 0x00c8), + VC4_HDMI_REG(HDMI_VERTA0, 0x00cc), + VC4_HDMI_REG(HDMI_VERTB0, 0x00d0), + VC4_HDMI_REG(HDMI_VERTA1, 0x00d4), + VC4_HDMI_REG(HDMI_VERTB1, 0x00d8), + VC4_HDMI_REG(HDMI_CEC_CNTRL_1, 0x00e8), + VC4_HDMI_REG(HDMI_CEC_CNTRL_2, 0x00ec), + VC4_HDMI_REG(HDMI_CEC_CNTRL_3, 0x00f0), + VC4_HDMI_REG(HDMI_CEC_CNTRL_4, 0x00f4), + VC4_HDMI_REG(HDMI_CEC_CNTRL_5, 0x00f8), + VC4_HDMI_REG(HDMI_CEC_TX_DATA_1, 0x00fc), + VC4_HDMI_REG(HDMI_CEC_TX_DATA_2, 0x0100), + VC4_HDMI_REG(HDMI_CEC_TX_DATA_3, 0x0104), + VC4_HDMI_REG(HDMI_CEC_TX_DATA_4, 0x0108), + VC4_HDMI_REG(HDMI_CEC_RX_DATA_1, 0x010c), + VC4_HDMI_REG(HDMI_CEC_RX_DATA_2, 0x0110), + VC4_HDMI_REG(HDMI_CEC_RX_DATA_3, 0x0114), + VC4_HDMI_REG(HDMI_CEC_RX_DATA_4, 0x0118), + VC4_HDMI_REG(HDMI_TX_PHY_RESET_CTL, 0x02c0), + VC4_HDMI_REG(HDMI_TX_PHY_CTL_0, 0x02c4), + VC4_HDMI_REG(HDMI_CEC_CPU_STATUS, 0x0340), + VC4_HDMI_REG(HDMI_CEC_CPU_CLEAR, 0x0348), + VC4_HDMI_REG(HDMI_CEC_CPU_MASK_STATUS, 0x034c), + VC4_HDMI_REG(HDMI_CEC_CPU_MASK_SET, 0x034c), + VC4_HDMI_REG(HDMI_CEC_CPU_MASK_CLEAR, 0x0354), + VC4_HDMI_REG(HDMI_RAM_PACKET_START, 0x0400), +}; + +static const struct vc4_hdmi_register vc5_hdmi_hdmi0_fields[] = { + VC4_HD_REG(HDMI_DVP_CTL, 0x0000), + VC4_HD_REG(HDMI_MAI_CTL, 0x0010), + VC4_HD_REG(HDMI_MAI_THR, 0x0014), + VC4_HD_REG(HDMI_MAI_FMT, 0x0018), + VC4_HD_REG(HDMI_MAI_DATA, 0x001c), + VC4_HD_REG(HDMI_MAI_SMP, 0x0020), + VC4_HD_REG(HDMI_VID_CTL, 0x0044), + VC4_HD_REG(HDMI_FRAME_COUNT, 0x0060), + + VC4_HDMI_REG(HDMI_FIFO_CTL, 0x074), + VC4_HDMI_REG(HDMI_AUDIO_PACKET_CONFIG, 0x0b8), + VC4_HDMI_REG(HDMI_RAM_PACKET_CONFIG, 0x0bc), + VC4_HDMI_REG(HDMI_RAM_PACKET_STATUS, 0x0c4), + VC4_HDMI_REG(HDMI_CRP_CFG, 0x0c8), + VC4_HDMI_REG(HDMI_CTS_0, 0x0cc), + VC4_HDMI_REG(HDMI_CTS_1, 0x0d0), + VC4_HDMI_REG(HDMI_SCHEDULER_CONTROL, 0x0e0), + VC4_HDMI_REG(HDMI_HORZA, 0x0e4), + VC4_HDMI_REG(HDMI_HORZB, 0x0e8), + VC4_HDMI_REG(HDMI_VERTA0, 0x0ec), + VC4_HDMI_REG(HDMI_VERTB0, 0x0f0), + VC4_HDMI_REG(HDMI_VERTA1, 0x0f4), + VC4_HDMI_REG(HDMI_VERTB1, 0x0f8), + VC4_HDMI_REG(HDMI_MAI_CHANNEL_MAP, 0x09c), + VC4_HDMI_REG(HDMI_MAI_CONFIG, 0x0a0), + VC4_HDMI_REG(HDMI_HOTPLUG, 0x1a8), + + VC5_DVP_REG(HDMI_CLOCK_STOP, 0x0bc), + VC5_DVP_REG(HDMI_VEC_INTERFACE_XBAR, 0x0f0), + + VC5_PHY_REG(HDMI_TX_PHY_RESET_CTL, 0x000), + VC5_PHY_REG(HDMI_TX_PHY_POWERDOWN_CTL, 0x004), + VC5_PHY_REG(HDMI_TX_PHY_CTL_0, 0x008), + VC5_PHY_REG(HDMI_TX_PHY_CTL_1, 0x00c), + VC5_PHY_REG(HDMI_TX_PHY_CTL_2, 0x010), + VC5_PHY_REG(HDMI_TX_PHY_CTL_3, 0x014), + VC5_PHY_REG(HDMI_TX_PHY_PLL_CTL_0, 0x01c), + VC5_PHY_REG(HDMI_TX_PHY_PLL_CTL_1, 0x020), + VC5_PHY_REG(HDMI_TX_PHY_CLK_DIV, 0x028), + VC5_PHY_REG(HDMI_TX_PHY_PLL_CFG, 0x034), + VC5_PHY_REG(HDMI_TX_PHY_TMDS_CLK_WORD_SEL, 0x044), + VC5_PHY_REG(HDMI_TX_PHY_CHANNEL_SWAP, 0x04c), + VC5_PHY_REG(HDMI_TX_PHY_PLL_CALIBRATION_CONFIG_1, 0x050), + VC5_PHY_REG(HDMI_TX_PHY_PLL_CALIBRATION_CONFIG_2, 0x054), + VC5_PHY_REG(HDMI_TX_PHY_PLL_CALIBRATION_CONFIG_4, 0x05c), + + VC5_RM_REG(HDMI_RM_CONTROL, 0x000), + VC5_RM_REG(HDMI_RM_OFFSET, 0x018), + VC5_RM_REG(HDMI_RM_FORMAT, 0x01c), + + VC5_RAM_REG(HDMI_RAM_PACKET_START, 0x000), + + VC5_CEC_REG(HDMI_CEC_CNTRL_1, 0x010), + VC5_CEC_REG(HDMI_CEC_CNTRL_2, 0x014), + VC5_CEC_REG(HDMI_CEC_CNTRL_3, 0x018), + VC5_CEC_REG(HDMI_CEC_CNTRL_4, 0x01c), + VC5_CEC_REG(HDMI_CEC_CNTRL_5, 0x020), + VC5_CEC_REG(HDMI_CEC_TX_DATA_1, 0x028), + VC5_CEC_REG(HDMI_CEC_TX_DATA_2, 0x02c), + VC5_CEC_REG(HDMI_CEC_TX_DATA_3, 0x030), + VC5_CEC_REG(HDMI_CEC_TX_DATA_4, 0x034), + VC5_CEC_REG(HDMI_CEC_RX_DATA_1, 0x038), + VC5_CEC_REG(HDMI_CEC_RX_DATA_2, 0x03c), + VC5_CEC_REG(HDMI_CEC_RX_DATA_3, 0x040), + VC5_CEC_REG(HDMI_CEC_RX_DATA_4, 0x044), + + VC5_CSC_REG(HDMI_CSC_CTL, 0x000), + VC5_CSC_REG(HDMI_CSC_12_11, 0x004), + VC5_CSC_REG(HDMI_CSC_14_13, 0x008), + VC5_CSC_REG(HDMI_CSC_22_21, 0x00c), + VC5_CSC_REG(HDMI_CSC_24_23, 0x010), + VC5_CSC_REG(HDMI_CSC_32_31, 0x014), + VC5_CSC_REG(HDMI_CSC_34_33, 0x018), +}; + +static const struct vc4_hdmi_register vc5_hdmi_hdmi1_fields[] = { + VC4_HD_REG(HDMI_DVP_CTL, 0x0000), + VC4_HD_REG(HDMI_MAI_CTL, 0x0030), + VC4_HD_REG(HDMI_MAI_THR, 0x0034), + VC4_HD_REG(HDMI_MAI_FMT, 0x0038), + VC4_HD_REG(HDMI_MAI_DATA, 0x003c), + VC4_HD_REG(HDMI_MAI_SMP, 0x0040), + VC4_HD_REG(HDMI_VID_CTL, 0x0048), + VC4_HD_REG(HDMI_FRAME_COUNT, 0x0064), + + VC4_HDMI_REG(HDMI_FIFO_CTL, 0x074), + VC4_HDMI_REG(HDMI_AUDIO_PACKET_CONFIG, 0x0b8), + VC4_HDMI_REG(HDMI_RAM_PACKET_CONFIG, 0x0bc), + VC4_HDMI_REG(HDMI_RAM_PACKET_STATUS, 0x0c4), + VC4_HDMI_REG(HDMI_CRP_CFG, 0x0c8), + VC4_HDMI_REG(HDMI_CTS_0, 0x0cc), + VC4_HDMI_REG(HDMI_CTS_1, 0x0d0), + VC4_HDMI_REG(HDMI_SCHEDULER_CONTROL, 0x0e0), + VC4_HDMI_REG(HDMI_HORZA, 0x0e4), + VC4_HDMI_REG(HDMI_HORZB, 0x0e8), + VC4_HDMI_REG(HDMI_VERTA0, 0x0ec), + VC4_HDMI_REG(HDMI_VERTB0, 0x0f0), + VC4_HDMI_REG(HDMI_VERTA1, 0x0f4), + VC4_HDMI_REG(HDMI_VERTB1, 0x0f8), + VC4_HDMI_REG(HDMI_MAI_CHANNEL_MAP, 0x09c), + VC4_HDMI_REG(HDMI_MAI_CONFIG, 0x0a0), + VC4_HDMI_REG(HDMI_HOTPLUG, 0x1a8), + + VC5_DVP_REG(HDMI_CLOCK_STOP, 0x0bc), + VC5_DVP_REG(HDMI_VEC_INTERFACE_XBAR, 0x0f0), + + VC5_PHY_REG(HDMI_TX_PHY_RESET_CTL, 0x000), + VC5_PHY_REG(HDMI_TX_PHY_POWERDOWN_CTL, 0x004), + VC5_PHY_REG(HDMI_TX_PHY_CTL_0, 0x008), + VC5_PHY_REG(HDMI_TX_PHY_CTL_1, 0x00c), + VC5_PHY_REG(HDMI_TX_PHY_CTL_2, 0x010), + VC5_PHY_REG(HDMI_TX_PHY_CTL_3, 0x014), + VC5_PHY_REG(HDMI_TX_PHY_PLL_CTL_0, 0x01c), + VC5_PHY_REG(HDMI_TX_PHY_PLL_CTL_1, 0x020), + VC5_PHY_REG(HDMI_TX_PHY_CLK_DIV, 0x028), + VC5_PHY_REG(HDMI_TX_PHY_PLL_CFG, 0x034), + VC5_PHY_REG(HDMI_TX_PHY_CHANNEL_SWAP, 0x04c), + VC5_PHY_REG(HDMI_TX_PHY_TMDS_CLK_WORD_SEL, 0x044), + VC5_PHY_REG(HDMI_TX_PHY_PLL_CALIBRATION_CONFIG_1, 0x050), + VC5_PHY_REG(HDMI_TX_PHY_PLL_CALIBRATION_CONFIG_2, 0x054), + VC5_PHY_REG(HDMI_TX_PHY_PLL_CALIBRATION_CONFIG_4, 0x05c), + + VC5_RM_REG(HDMI_RM_CONTROL, 0x000), + VC5_RM_REG(HDMI_RM_OFFSET, 0x018), + VC5_RM_REG(HDMI_RM_FORMAT, 0x01c), + + VC5_RAM_REG(HDMI_RAM_PACKET_START, 0x000), + + VC5_CEC_REG(HDMI_CEC_CNTRL_1, 0x010), + VC5_CEC_REG(HDMI_CEC_CNTRL_2, 0x014), + VC5_CEC_REG(HDMI_CEC_CNTRL_3, 0x018), + VC5_CEC_REG(HDMI_CEC_CNTRL_4, 0x01c), + VC5_CEC_REG(HDMI_CEC_CNTRL_5, 0x020), + VC5_CEC_REG(HDMI_CEC_TX_DATA_1, 0x028), + VC5_CEC_REG(HDMI_CEC_TX_DATA_2, 0x02c), + VC5_CEC_REG(HDMI_CEC_TX_DATA_3, 0x030), + VC5_CEC_REG(HDMI_CEC_TX_DATA_4, 0x034), + VC5_CEC_REG(HDMI_CEC_RX_DATA_1, 0x038), + VC5_CEC_REG(HDMI_CEC_RX_DATA_2, 0x03c), + VC5_CEC_REG(HDMI_CEC_RX_DATA_3, 0x040), + VC5_CEC_REG(HDMI_CEC_RX_DATA_4, 0x044), + + VC5_CSC_REG(HDMI_CSC_CTL, 0x000), + VC5_CSC_REG(HDMI_CSC_12_11, 0x004), + VC5_CSC_REG(HDMI_CSC_14_13, 0x008), + VC5_CSC_REG(HDMI_CSC_22_21, 0x00c), + VC5_CSC_REG(HDMI_CSC_24_23, 0x010), + VC5_CSC_REG(HDMI_CSC_32_31, 0x014), + VC5_CSC_REG(HDMI_CSC_34_33, 0x018), +}; + +static inline +void __iomem *__vc4_hdmi_get_field_base(struct vc4_hdmi *hdmi, + enum vc4_hdmi_regs reg) +{ + switch (reg) { + case VC4_HD: + return hdmi->hd_regs; + + case VC4_HDMI: + return hdmi->hdmicore_regs; + + case VC5_CSC: + return hdmi->csc_regs; + + case VC5_CEC: + return hdmi->cec_regs; + + case VC5_DVP: + return hdmi->dvp_regs; + + case VC5_PHY: + return hdmi->phy_regs; + + case VC5_RAM: + return hdmi->ram_regs; + + case VC5_RM: + return hdmi->rm_regs; + + default: + return NULL; + } + + return NULL; +} + +static inline u32 vc4_hdmi_read(struct vc4_hdmi *hdmi, + enum vc4_hdmi_field reg) +{ + const struct vc4_hdmi_register *field; + const struct vc4_hdmi_variant *variant = hdmi->variant; + void __iomem *base; + + if (reg >= variant->num_registers) { + dev_warn(&hdmi->pdev->dev, + "Invalid register ID %u\n", reg); + return 0; + } + + field = &variant->registers[reg]; + base = __vc4_hdmi_get_field_base(hdmi, field->reg); + if (!base) { + dev_warn(&hdmi->pdev->dev, + "Unknown register ID %u\n", reg); + return 0; + } + + return readl(base + field->offset); +} +#define HDMI_READ(reg) vc4_hdmi_read(vc4_hdmi, reg) + +static inline void vc4_hdmi_write(struct vc4_hdmi *hdmi, + enum vc4_hdmi_field reg, + u32 value) +{ + const struct vc4_hdmi_register *field; + const struct vc4_hdmi_variant *variant = hdmi->variant; + void __iomem *base; + + if (reg >= variant->num_registers) { + dev_warn(&hdmi->pdev->dev, + "Invalid register ID %u\n", reg); + return; + } + + field = &variant->registers[reg]; + base = __vc4_hdmi_get_field_base(hdmi, field->reg); + if (!base) + return; + + writel(value, base + field->offset); +} +#define HDMI_WRITE(reg, val) vc4_hdmi_write(vc4_hdmi, reg, val) + +#endif /* _VC4_HDMI_REGS_H_ */ diff --git a/drivers/gpu/drm/vc4/vc4_hvs.c b/drivers/gpu/drm/vc4/vc4_hvs.c index 2d2bf59c0503..4d0a833366ce 100644 --- a/drivers/gpu/drm/vc4/vc4_hvs.c +++ b/drivers/gpu/drm/vc4/vc4_hvs.c @@ -19,6 +19,8 @@ * each CRTC. */ +#include <linux/bitfield.h> +#include <linux/clk.h> #include <linux/component.h> #include <linux/platform_device.h> @@ -160,6 +162,7 @@ static void vc4_hvs_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_state = to_vc4_crtc_state(crtc->state); u32 i; /* The LUT memory is laid out with each HVS channel in order, @@ -168,7 +171,7 @@ static void vc4_hvs_lut_load(struct drm_crtc *crtc) */ HVS_WRITE(SCALER_GAMADDR, SCALER_GAMADDR_AUTOINC | - (vc4_crtc->channel * 3 * crtc->gamma_size)); + (vc4_state->assigned_channel * 3 * crtc->gamma_size)); for (i = 0; i < crtc->gamma_size; i++) HVS_WRITE(SCALER_GAMDATA, vc4_crtc->lut_r[i]); @@ -194,6 +197,135 @@ static void vc4_hvs_update_gamma_lut(struct drm_crtc *crtc) vc4_hvs_lut_load(crtc); } +int vc4_hvs_get_fifo_from_output(struct drm_device *dev, unsigned int output) +{ + struct vc4_dev *vc4 = to_vc4_dev(dev); + u32 reg; + int ret; + + if (!vc4->hvs->hvs5) + return output; + + switch (output) { + case 0: + return 0; + + case 1: + return 1; + + case 2: + reg = HVS_READ(SCALER_DISPECTRL); + ret = FIELD_GET(SCALER_DISPECTRL_DSP2_MUX_MASK, reg); + if (ret == 0) + return 2; + + return 0; + + case 3: + reg = HVS_READ(SCALER_DISPCTRL); + ret = FIELD_GET(SCALER_DISPCTRL_DSP3_MUX_MASK, reg); + if (ret == 3) + return -EPIPE; + + return ret; + + case 4: + reg = HVS_READ(SCALER_DISPEOLN); + ret = FIELD_GET(SCALER_DISPEOLN_DSP4_MUX_MASK, reg); + if (ret == 3) + return -EPIPE; + + return ret; + + case 5: + reg = HVS_READ(SCALER_DISPDITHER); + ret = FIELD_GET(SCALER_DISPDITHER_DSP5_MUX_MASK, reg); + if (ret == 3) + return -EPIPE; + + return ret; + + default: + return -EPIPE; + } +} + +static int vc4_hvs_init_channel(struct vc4_dev *vc4, struct drm_crtc *crtc, + struct drm_display_mode *mode, bool oneshot) +{ + struct vc4_crtc_state *vc4_crtc_state = to_vc4_crtc_state(crtc->state); + unsigned int chan = vc4_crtc_state->assigned_channel; + bool interlace = mode->flags & DRM_MODE_FLAG_INTERLACE; + u32 dispbkgndx; + u32 dispctrl; + + HVS_WRITE(SCALER_DISPCTRLX(chan), 0); + HVS_WRITE(SCALER_DISPCTRLX(chan), SCALER_DISPCTRLX_RESET); + HVS_WRITE(SCALER_DISPCTRLX(chan), 0); + + /* Turn on the scaler, which will wait for vstart to start + * compositing. + * When feeding the transposer, we should operate in oneshot + * mode. + */ + dispctrl = SCALER_DISPCTRLX_ENABLE; + + if (!vc4->hvs->hvs5) + dispctrl |= VC4_SET_FIELD(mode->hdisplay, + SCALER_DISPCTRLX_WIDTH) | + VC4_SET_FIELD(mode->vdisplay, + SCALER_DISPCTRLX_HEIGHT) | + (oneshot ? SCALER_DISPCTRLX_ONESHOT : 0); + else + dispctrl |= VC4_SET_FIELD(mode->hdisplay, + SCALER5_DISPCTRLX_WIDTH) | + VC4_SET_FIELD(mode->vdisplay, + SCALER5_DISPCTRLX_HEIGHT) | + (oneshot ? SCALER5_DISPCTRLX_ONESHOT : 0); + + HVS_WRITE(SCALER_DISPCTRLX(chan), dispctrl); + + dispbkgndx = HVS_READ(SCALER_DISPBKGNDX(chan)); + dispbkgndx &= ~SCALER_DISPBKGND_GAMMA; + dispbkgndx &= ~SCALER_DISPBKGND_INTERLACE; + + HVS_WRITE(SCALER_DISPBKGNDX(chan), dispbkgndx | + SCALER_DISPBKGND_AUTOHS | + ((!vc4->hvs->hvs5) ? SCALER_DISPBKGND_GAMMA : 0) | + (interlace ? SCALER_DISPBKGND_INTERLACE : 0)); + + /* Reload the LUT, since the SRAMs would have been disabled if + * all CRTCs had SCALER_DISPBKGND_GAMMA unset at once. + */ + vc4_hvs_lut_load(crtc); + + return 0; +} + +void vc4_hvs_stop_channel(struct drm_device *dev, unsigned int chan) +{ + struct vc4_dev *vc4 = to_vc4_dev(dev); + + if (HVS_READ(SCALER_DISPCTRLX(chan)) & SCALER_DISPCTRLX_ENABLE) + return; + + HVS_WRITE(SCALER_DISPCTRLX(chan), + HVS_READ(SCALER_DISPCTRLX(chan)) | SCALER_DISPCTRLX_RESET); + HVS_WRITE(SCALER_DISPCTRLX(chan), + HVS_READ(SCALER_DISPCTRLX(chan)) & ~SCALER_DISPCTRLX_ENABLE); + + /* Once we leave, the scaler should be disabled and its fifo empty. */ + WARN_ON_ONCE(HVS_READ(SCALER_DISPCTRLX(chan)) & SCALER_DISPCTRLX_RESET); + + WARN_ON_ONCE(VC4_GET_FIELD(HVS_READ(SCALER_DISPSTATX(chan)), + SCALER_DISPSTATX_MODE) != + SCALER_DISPSTATX_MODE_DISABLED); + + WARN_ON_ONCE((HVS_READ(SCALER_DISPSTATX(chan)) & + (SCALER_DISPSTATX_FULL | SCALER_DISPSTATX_EMPTY)) != + SCALER_DISPSTATX_EMPTY); +} + int vc4_hvs_atomic_check(struct drm_crtc *crtc, struct drm_crtc_state *state) { @@ -248,12 +380,12 @@ static void vc4_hvs_update_dlist(struct drm_crtc *crtc) 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); } } @@ -263,59 +395,22 @@ void vc4_hvs_atomic_enable(struct drm_crtc *crtc, { struct drm_device *dev = crtc->dev; struct vc4_dev *vc4 = to_vc4_dev(dev); - struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc); struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc->state); struct drm_display_mode *mode = &crtc->state->adjusted_mode; bool oneshot = vc4_state->feed_txp; - u32 dispctrl; vc4_hvs_update_dlist(crtc); - - /* Turn on the scaler, which will wait for vstart to start - * compositing. - * When feeding the transposer, we should operate in oneshot - * mode. - */ - dispctrl = SCALER_DISPCTRLX_ENABLE; - dispctrl |= VC4_SET_FIELD(mode->hdisplay, - SCALER_DISPCTRLX_WIDTH) | - VC4_SET_FIELD(mode->vdisplay, - SCALER_DISPCTRLX_HEIGHT) | - (oneshot ? SCALER_DISPCTRLX_ONESHOT : 0); - - HVS_WRITE(SCALER_DISPCTRLX(vc4_crtc->channel), dispctrl); + vc4_hvs_init_channel(vc4, crtc, mode, oneshot); } void vc4_hvs_atomic_disable(struct drm_crtc *crtc, struct drm_crtc_state *old_state) { 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_state = to_vc4_crtc_state(old_state); + unsigned int chan = vc4_state->assigned_channel; - if (HVS_READ(SCALER_DISPCTRLX(chan)) & - SCALER_DISPCTRLX_ENABLE) { - HVS_WRITE(SCALER_DISPCTRLX(chan), - SCALER_DISPCTRLX_RESET); - - /* While the docs say that reset is self-clearing, it - * seems it doesn't actually. - */ - HVS_WRITE(SCALER_DISPCTRLX(chan), 0); - } - - /* Once we leave, the scaler should be disabled and its fifo empty. */ - - WARN_ON_ONCE(HVS_READ(SCALER_DISPCTRLX(chan)) & SCALER_DISPCTRLX_RESET); - - WARN_ON_ONCE(VC4_GET_FIELD(HVS_READ(SCALER_DISPSTATX(chan)), - SCALER_DISPSTATX_MODE) != - SCALER_DISPSTATX_MODE_DISABLED); - - WARN_ON_ONCE((HVS_READ(SCALER_DISPSTATX(chan)) & - (SCALER_DISPSTATX_FULL | SCALER_DISPSTATX_EMPTY)) != - SCALER_DISPSTATX_EMPTY); + vc4_hvs_stop_channel(dev, chan); } void vc4_hvs_atomic_flush(struct drm_crtc *crtc, @@ -323,7 +418,6 @@ void vc4_hvs_atomic_flush(struct drm_crtc *crtc, { struct drm_device *dev = crtc->dev; struct vc4_dev *vc4 = to_vc4_dev(dev); - struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc); struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc->state); struct drm_plane *plane; struct vc4_plane_state *vc4_plane_state; @@ -365,8 +459,8 @@ void vc4_hvs_atomic_flush(struct drm_crtc *crtc, /* 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 @@ -380,7 +474,7 @@ void vc4_hvs_atomic_flush(struct drm_crtc *crtc, vc4_hvs_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_hvs_update_gamma_lut(crtc); @@ -392,7 +486,7 @@ void vc4_hvs_atomic_flush(struct drm_crtc *crtc, */ 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) { @@ -401,50 +495,6 @@ void vc4_hvs_atomic_flush(struct drm_crtc *crtc, } } -void vc4_hvs_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 vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc->state); - struct drm_display_mode *mode = &crtc->state->adjusted_mode; - bool interlace = mode->flags & DRM_MODE_FLAG_INTERLACE; - - if (vc4_crtc->data->hvs_channel == 2) { - u32 dispctrl; - u32 dsp3_mux; - - /* - * SCALER_DISPCTRL_DSP3 = X, where X < 2 means 'connect DSP3 to - * FIFO X'. - * SCALER_DISPCTRL_DSP3 = 3 means 'disable DSP 3'. - * - * DSP3 is connected to FIFO2 unless the transposer is - * enabled. In this case, FIFO 2 is directly accessed by the - * TXP IP, and we need to disable the FIFO2 -> pixelvalve1 - * route. - */ - if (vc4_state->feed_txp) - dsp3_mux = VC4_SET_FIELD(3, SCALER_DISPCTRL_DSP3_MUX); - else - dsp3_mux = VC4_SET_FIELD(2, SCALER_DISPCTRL_DSP3_MUX); - - dispctrl = HVS_READ(SCALER_DISPCTRL) & - ~SCALER_DISPCTRL_DSP3_MUX_MASK; - HVS_WRITE(SCALER_DISPCTRL, dispctrl | dsp3_mux); - } - - HVS_WRITE(SCALER_DISPBKGNDX(vc4_crtc->channel), - SCALER_DISPBKGND_AUTOHS | - SCALER_DISPBKGND_GAMMA | - (interlace ? SCALER_DISPBKGND_INTERLACE : 0)); - - /* Reload the LUT, since the SRAMs would have been disabled if - * all CRTCs had SCALER_DISPBKGND_GAMMA unset at once. - */ - vc4_hvs_lut_load(crtc); -} - void vc4_hvs_mask_underrun(struct drm_device *dev, int channel) { struct vc4_dev *vc4 = to_vc4_dev(dev); @@ -521,6 +571,9 @@ static int vc4_hvs_bind(struct device *dev, struct device *master, void *data) hvs->pdev = pdev; + if (of_device_is_compatible(pdev->dev.of_node, "brcm,bcm2711-hvs")) + hvs->hvs5 = true; + hvs->regs = vc4_ioremap_regs(pdev, 0); if (IS_ERR(hvs->regs)) return PTR_ERR(hvs->regs); @@ -529,7 +582,24 @@ static int vc4_hvs_bind(struct device *dev, struct device *master, void *data) hvs->regset.regs = hvs_regs; hvs->regset.nregs = ARRAY_SIZE(hvs_regs); - hvs->dlist = hvs->regs + SCALER_DLIST_START; + if (hvs->hvs5) { + hvs->core_clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(hvs->core_clk)) { + dev_err(&pdev->dev, "Couldn't get core clock\n"); + return PTR_ERR(hvs->core_clk); + } + + ret = clk_prepare_enable(hvs->core_clk); + if (ret) { + dev_err(&pdev->dev, "Couldn't enable the core clock\n"); + return ret; + } + } + + if (!hvs->hvs5) + hvs->dlist = hvs->regs + SCALER_DLIST_START; + else + hvs->dlist = hvs->regs + SCALER5_DLIST_START; spin_lock_init(&hvs->mm_lock); @@ -547,7 +617,12 @@ static int vc4_hvs_bind(struct device *dev, struct device *master, void *data) * between planes when they don't overlap on the screen, but * for now we just allocate globally. */ - drm_mm_init(&hvs->lbm_mm, 0, 96 * 1024); + if (!hvs->hvs5) + /* 96kB */ + drm_mm_init(&hvs->lbm_mm, 0, 96 * 1024); + else + /* 70k words */ + drm_mm_init(&hvs->lbm_mm, 0, 70 * 2 * 1024); /* Upload filter kernels. We only have the one for now, so we * keep it around for the lifetime of the driver. @@ -605,6 +680,7 @@ static void vc4_hvs_unbind(struct device *dev, struct device *master, { struct drm_device *drm = dev_get_drvdata(master); struct vc4_dev *vc4 = drm->dev_private; + struct vc4_hvs *hvs = vc4->hvs; if (drm_mm_node_allocated(&vc4->hvs->mitchell_netravali_filter)) drm_mm_remove_node(&vc4->hvs->mitchell_netravali_filter); @@ -612,6 +688,8 @@ static void vc4_hvs_unbind(struct device *dev, struct device *master, drm_mm_takedown(&vc4->hvs->dlist_mm); drm_mm_takedown(&vc4->hvs->lbm_mm); + clk_disable_unprepare(hvs->core_clk); + vc4->hvs = NULL; } @@ -632,6 +710,7 @@ static int vc4_hvs_dev_remove(struct platform_device *pdev) } static const struct of_device_id vc4_hvs_dt_match[] = { + { .compatible = "brcm,bcm2711-hvs" }, { .compatible = "brcm,bcm2835-hvs" }, {} }; diff --git a/drivers/gpu/drm/vc4/vc4_kms.c b/drivers/gpu/drm/vc4/vc4_kms.c index 08318e69061b..af3ee3dcdab6 100644 --- a/drivers/gpu/drm/vc4/vc4_kms.c +++ b/drivers/gpu/drm/vc4/vc4_kms.c @@ -11,6 +11,8 @@ * crtc, HDMI encoder). */ +#include <linux/clk.h> + #include <drm/drm_atomic.h> #include <drm/drm_atomic_helper.h> #include <drm/drm_crtc.h> @@ -144,22 +146,130 @@ vc4_ctm_commit(struct vc4_dev *vc4, struct drm_atomic_state *state) 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 int i; + + for_each_new_crtc_in_state(state, crtc, crtc_state, i) { + struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc_state); + u32 dispctrl; + u32 dsp3_mux; + + if (!crtc_state->active) + continue; + + if (vc4_state->assigned_channel != 2) + continue; + + /* + * SCALER_DISPCTRL_DSP3 = X, where X < 2 means 'connect DSP3 to + * FIFO X'. + * SCALER_DISPCTRL_DSP3 = 3 means 'disable DSP 3'. + * + * DSP3 is connected to FIFO2 unless the transposer is + * enabled. In this case, FIFO 2 is directly accessed by the + * TXP IP, and we need to disable the FIFO2 -> pixelvalve1 + * route. + */ + if (vc4_state->feed_txp) + dsp3_mux = VC4_SET_FIELD(3, SCALER_DISPCTRL_DSP3_MUX); + else + dsp3_mux = VC4_SET_FIELD(2, SCALER_DISPCTRL_DSP3_MUX); + + dispctrl = HVS_READ(SCALER_DISPCTRL) & + ~SCALER_DISPCTRL_DSP3_MUX_MASK; + HVS_WRITE(SCALER_DISPCTRL, dispctrl | dsp3_mux); + } +} + +static void vc5_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) ? 0 : 1; + 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); + 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 vc4_atomic_complete_commit(struct drm_atomic_state *state) { struct drm_device *dev = state->dev; struct vc4_dev *vc4 = to_vc4_dev(dev); - struct vc4_crtc *vc4_crtc; + struct vc4_hvs *hvs = vc4->hvs; + struct drm_crtc_state *new_crtc_state; + struct drm_crtc *crtc; int i; - for (i = 0; i < dev->mode_config.num_crtc; i++) { - if (!state->crtcs[i].ptr || !state->crtcs[i].commit) + for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) { + struct vc4_crtc_state *vc4_crtc_state; + + if (!new_crtc_state->commit) continue; - vc4_crtc = to_vc4_crtc(state->crtcs[i].ptr); - vc4_hvs_mask_underrun(dev, vc4_crtc->channel); + vc4_crtc_state = to_vc4_crtc_state(new_crtc_state); + vc4_hvs_mask_underrun(dev, vc4_crtc_state->assigned_channel); } + if (vc4->hvs->hvs5) + clk_set_min_rate(hvs->core_clk, 500000000); + drm_atomic_helper_wait_for_fences(dev, state, false); drm_atomic_helper_wait_for_dependencies(state); @@ -168,6 +278,11 @@ vc4_atomic_complete_commit(struct drm_atomic_state *state) vc4_ctm_commit(vc4, state); + if (vc4->hvs->hvs5) + vc5_hvs_pv_muxing_commit(vc4, state); + else + vc4_hvs_pv_muxing_commit(vc4, state); + drm_atomic_helper_commit_planes(dev, state, 0); drm_atomic_helper_commit_modeset_enables(dev, state); @@ -182,6 +297,9 @@ vc4_atomic_complete_commit(struct drm_atomic_state *state) drm_atomic_helper_commit_cleanup_done(state); + if (vc4->hvs->hvs5) + clk_set_min_rate(hvs->core_clk, 0); + drm_atomic_state_put(state); up(&vc4->async_modeset); @@ -374,8 +492,11 @@ vc4_ctm_atomic_check(struct drm_device *dev, struct drm_atomic_state *state) /* 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. @@ -415,6 +536,9 @@ static int vc4_load_tracker_atomic_check(struct drm_atomic_state *state) struct drm_plane *plane; int i; + if (!vc4->load_tracker_available) + return 0; + priv_state = drm_atomic_get_private_obj_state(state, &vc4->load_tracker); if (IS_ERR(priv_state)) @@ -485,10 +609,77 @@ 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 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; + + /* + * 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) { + if (!crtc->state->enable) + continue; + + crtc_state = drm_atomic_get_crtc_state(state, crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + } + + 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); + unsigned int matching_channels; + + 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. + */ + matching_channels = unassigned_channels & vc4_crtc->data->hvs_available_channels; + if (matching_channels) { + unsigned int channel = ffs(matching_channels) - 1; + + vc4_crtc_state->assigned_channel = channel; + unassigned_channels &= ~BIT(channel); + } else { + return -EINVAL; + } + } ret = vc4_ctm_atomic_check(dev, state); if (ret < 0) @@ -512,12 +703,18 @@ 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; - /* Start with the load tracker enabled. Can be disabled through the - * debugfs load_tracker file. - */ - vc4->load_tracker_enabled = true; + if (!is_vc5) { + vc4->load_tracker_available = true; + + /* Start with the load tracker enabled. Can be + * disabled through the debugfs load_tracker file. + */ + vc4->load_tracker_enabled = true; + } sema_init(&vc4->async_modeset, 1); @@ -531,8 +728,14 @@ int vc4_kms_load(struct drm_device *dev) return ret; } - dev->mode_config.max_width = 2048; - dev->mode_config.max_height = 2048; + if (is_vc5) { + dev->mode_config.max_width = 7680; + dev->mode_config.max_height = 7680; + } else { + dev->mode_config.max_width = 2048; + dev->mode_config.max_height = 2048; + } + dev->mode_config.funcs = &vc4_mode_funcs; dev->mode_config.preferred_depth = 24; dev->mode_config.async_page_flip = true; @@ -547,14 +750,17 @@ int vc4_kms_load(struct drm_device *dev) drm_atomic_private_obj_init(dev, &vc4->ctm_manager, &ctm_state->base, &vc4_ctm_state_funcs); - load_state = kzalloc(sizeof(*load_state), GFP_KERNEL); - if (!load_state) { - drm_atomic_private_obj_fini(&vc4->ctm_manager); - return -ENOMEM; - } + 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; + } - drm_atomic_private_obj_init(dev, &vc4->load_tracker, &load_state->base, - &vc4_load_tracker_state_funcs); + drm_atomic_private_obj_init(dev, &vc4->load_tracker, + &load_state->base, + &vc4_load_tracker_state_funcs); + } drm_mode_config_reset(dev); diff --git a/drivers/gpu/drm/vc4/vc4_plane.c b/drivers/gpu/drm/vc4/vc4_plane.c index d040d9f12c6d..6b39cc2ca18d 100644 --- a/drivers/gpu/drm/vc4/vc4_plane.c +++ b/drivers/gpu/drm/vc4/vc4_plane.c @@ -32,45 +32,60 @@ static const struct hvs_format { u32 drm; /* DRM_FORMAT_* */ u32 hvs; /* HVS_FORMAT_* */ u32 pixel_order; + u32 pixel_order_hvs5; } hvs_formats[] = { { - .drm = DRM_FORMAT_XRGB8888, .hvs = HVS_PIXEL_FORMAT_RGBA8888, + .drm = DRM_FORMAT_XRGB8888, + .hvs = HVS_PIXEL_FORMAT_RGBA8888, .pixel_order = HVS_PIXEL_ORDER_ABGR, + .pixel_order_hvs5 = HVS_PIXEL_ORDER_ARGB, }, { - .drm = DRM_FORMAT_ARGB8888, .hvs = HVS_PIXEL_FORMAT_RGBA8888, + .drm = DRM_FORMAT_ARGB8888, + .hvs = HVS_PIXEL_FORMAT_RGBA8888, .pixel_order = HVS_PIXEL_ORDER_ABGR, + .pixel_order_hvs5 = HVS_PIXEL_ORDER_ARGB, }, { - .drm = DRM_FORMAT_ABGR8888, .hvs = HVS_PIXEL_FORMAT_RGBA8888, + .drm = DRM_FORMAT_ABGR8888, + .hvs = HVS_PIXEL_FORMAT_RGBA8888, .pixel_order = HVS_PIXEL_ORDER_ARGB, + .pixel_order_hvs5 = HVS_PIXEL_ORDER_ABGR, }, { - .drm = DRM_FORMAT_XBGR8888, .hvs = HVS_PIXEL_FORMAT_RGBA8888, + .drm = DRM_FORMAT_XBGR8888, + .hvs = HVS_PIXEL_FORMAT_RGBA8888, .pixel_order = HVS_PIXEL_ORDER_ARGB, + .pixel_order_hvs5 = HVS_PIXEL_ORDER_ABGR, }, { - .drm = DRM_FORMAT_RGB565, .hvs = HVS_PIXEL_FORMAT_RGB565, + .drm = DRM_FORMAT_RGB565, + .hvs = HVS_PIXEL_FORMAT_RGB565, .pixel_order = HVS_PIXEL_ORDER_XRGB, }, { - .drm = DRM_FORMAT_BGR565, .hvs = HVS_PIXEL_FORMAT_RGB565, + .drm = DRM_FORMAT_BGR565, + .hvs = HVS_PIXEL_FORMAT_RGB565, .pixel_order = HVS_PIXEL_ORDER_XBGR, }, { - .drm = DRM_FORMAT_ARGB1555, .hvs = HVS_PIXEL_FORMAT_RGBA5551, + .drm = DRM_FORMAT_ARGB1555, + .hvs = HVS_PIXEL_FORMAT_RGBA5551, .pixel_order = HVS_PIXEL_ORDER_ABGR, }, { - .drm = DRM_FORMAT_XRGB1555, .hvs = HVS_PIXEL_FORMAT_RGBA5551, + .drm = DRM_FORMAT_XRGB1555, + .hvs = HVS_PIXEL_FORMAT_RGBA5551, .pixel_order = HVS_PIXEL_ORDER_ABGR, }, { - .drm = DRM_FORMAT_RGB888, .hvs = HVS_PIXEL_FORMAT_RGB888, + .drm = DRM_FORMAT_RGB888, + .hvs = HVS_PIXEL_FORMAT_RGB888, .pixel_order = HVS_PIXEL_ORDER_XRGB, }, { - .drm = DRM_FORMAT_BGR888, .hvs = HVS_PIXEL_FORMAT_RGB888, + .drm = DRM_FORMAT_BGR888, + .hvs = HVS_PIXEL_FORMAT_RGB888, .pixel_order = HVS_PIXEL_ORDER_XBGR, }, { @@ -422,10 +437,7 @@ static void vc4_write_ppf(struct vc4_plane_state *vc4_state, u32 src, u32 dst) static u32 vc4_lbm_size(struct drm_plane_state *state) { struct vc4_plane_state *vc4_state = to_vc4_plane_state(state); - /* This is the worst case number. One of the two sizes will - * be used depending on the scaling configuration. - */ - u32 pix_per_line = max(vc4_state->src_w[0], (u32)vc4_state->crtc_w); + u32 pix_per_line; u32 lbm; /* LBM is not needed when there's no vertical scaling. */ @@ -433,6 +445,18 @@ static u32 vc4_lbm_size(struct drm_plane_state *state) vc4_state->y_scaling[1] == VC4_SCALING_NONE) return 0; + /* + * This can be further optimized in the RGB/YUV444 case if the PPF + * decimation factor is between 0.5 and 1.0 by using crtc_w. + * + * It's not an issue though, since in that case since src_w[0] is going + * to be greater than or equal to crtc_w. + */ + if (vc4_state->x_scaling[0] == VC4_SCALING_TPZ) + pix_per_line = vc4_state->crtc_w; + else + pix_per_line = vc4_state->src_w[0]; + if (!vc4_state->is_yuv) { if (vc4_state->y_scaling[0] == VC4_SCALING_TPZ) lbm = pix_per_line * 8; @@ -492,6 +516,11 @@ static void vc4_plane_calc_load(struct drm_plane_state *state) struct vc4_plane_state *vc4_state; struct drm_crtc_state *crtc_state; unsigned int vscale_factor; + struct vc4_dev *vc4; + + vc4 = to_vc4_dev(state->plane->dev); + if (!vc4->load_tracker_available) + return; vc4_state = to_vc4_plane_state(state); crtc_state = drm_atomic_get_existing_crtc_state(state->state, @@ -563,7 +592,9 @@ static int vc4_plane_allocate_lbm(struct drm_plane_state *state) spin_lock_irqsave(&vc4->hvs->mm_lock, irqflags); ret = drm_mm_insert_node_generic(&vc4->hvs->lbm_mm, &vc4_state->lbm, - lbm_size, 32, 0, 0); + lbm_size, + vc4->hvs->hvs5 ? 64 : 32, + 0, 0); spin_unlock_irqrestore(&vc4->hvs->mm_lock, irqflags); if (ret) @@ -776,35 +807,6 @@ static int vc4_plane_mode_set(struct drm_plane *plane, return -EINVAL; } - /* Control word */ - vc4_dlist_write(vc4_state, - SCALER_CTL0_VALID | - (rotation & DRM_MODE_REFLECT_X ? SCALER_CTL0_HFLIP : 0) | - (rotation & DRM_MODE_REFLECT_Y ? SCALER_CTL0_VFLIP : 0) | - VC4_SET_FIELD(SCALER_CTL0_RGBA_EXPAND_ROUND, SCALER_CTL0_RGBA_EXPAND) | - (format->pixel_order << SCALER_CTL0_ORDER_SHIFT) | - (hvs_format << SCALER_CTL0_PIXEL_FORMAT_SHIFT) | - VC4_SET_FIELD(tiling, SCALER_CTL0_TILING) | - (vc4_state->is_unity ? SCALER_CTL0_UNITY : 0) | - VC4_SET_FIELD(scl0, SCALER_CTL0_SCL0) | - VC4_SET_FIELD(scl1, SCALER_CTL0_SCL1)); - - /* Position Word 0: Image Positions and Alpha Value */ - vc4_state->pos0_offset = vc4_state->dlist_count; - vc4_dlist_write(vc4_state, - VC4_SET_FIELD(state->alpha >> 8, SCALER_POS0_FIXED_ALPHA) | - VC4_SET_FIELD(vc4_state->crtc_x, SCALER_POS0_START_X) | - VC4_SET_FIELD(vc4_state->crtc_y, SCALER_POS0_START_Y)); - - /* Position Word 1: Scaled Image Dimensions. */ - if (!vc4_state->is_unity) { - vc4_dlist_write(vc4_state, - VC4_SET_FIELD(vc4_state->crtc_w, - SCALER_POS1_SCL_WIDTH) | - VC4_SET_FIELD(vc4_state->crtc_h, - SCALER_POS1_SCL_HEIGHT)); - } - /* Don't waste cycles mixing with plane alpha if the set alpha * is opaque or there is no per-pixel alpha information. * In any case we use the alpha property value as the fixed alpha. @@ -812,20 +814,120 @@ static int vc4_plane_mode_set(struct drm_plane *plane, mix_plane_alpha = state->alpha != DRM_BLEND_ALPHA_OPAQUE && fb->format->has_alpha; - /* Position Word 2: Source Image Size, Alpha */ - vc4_state->pos2_offset = vc4_state->dlist_count; - vc4_dlist_write(vc4_state, - VC4_SET_FIELD(fb->format->has_alpha ? - SCALER_POS2_ALPHA_MODE_PIPELINE : - SCALER_POS2_ALPHA_MODE_FIXED, - SCALER_POS2_ALPHA_MODE) | - (mix_plane_alpha ? SCALER_POS2_ALPHA_MIX : 0) | - (fb->format->has_alpha ? SCALER_POS2_ALPHA_PREMULT : 0) | - VC4_SET_FIELD(vc4_state->src_w[0], SCALER_POS2_WIDTH) | - VC4_SET_FIELD(vc4_state->src_h[0], SCALER_POS2_HEIGHT)); + if (!vc4->hvs->hvs5) { + /* Control word */ + vc4_dlist_write(vc4_state, + SCALER_CTL0_VALID | + (rotation & DRM_MODE_REFLECT_X ? SCALER_CTL0_HFLIP : 0) | + (rotation & DRM_MODE_REFLECT_Y ? SCALER_CTL0_VFLIP : 0) | + VC4_SET_FIELD(SCALER_CTL0_RGBA_EXPAND_ROUND, SCALER_CTL0_RGBA_EXPAND) | + (format->pixel_order << SCALER_CTL0_ORDER_SHIFT) | + (hvs_format << SCALER_CTL0_PIXEL_FORMAT_SHIFT) | + VC4_SET_FIELD(tiling, SCALER_CTL0_TILING) | + (vc4_state->is_unity ? SCALER_CTL0_UNITY : 0) | + VC4_SET_FIELD(scl0, SCALER_CTL0_SCL0) | + VC4_SET_FIELD(scl1, SCALER_CTL0_SCL1)); + + /* Position Word 0: Image Positions and Alpha Value */ + vc4_state->pos0_offset = vc4_state->dlist_count; + vc4_dlist_write(vc4_state, + VC4_SET_FIELD(state->alpha >> 8, SCALER_POS0_FIXED_ALPHA) | + VC4_SET_FIELD(vc4_state->crtc_x, SCALER_POS0_START_X) | + VC4_SET_FIELD(vc4_state->crtc_y, SCALER_POS0_START_Y)); + + /* Position Word 1: Scaled Image Dimensions. */ + if (!vc4_state->is_unity) { + vc4_dlist_write(vc4_state, + VC4_SET_FIELD(vc4_state->crtc_w, + SCALER_POS1_SCL_WIDTH) | + VC4_SET_FIELD(vc4_state->crtc_h, + SCALER_POS1_SCL_HEIGHT)); + } + + /* Position Word 2: Source Image Size, Alpha */ + vc4_state->pos2_offset = vc4_state->dlist_count; + vc4_dlist_write(vc4_state, + VC4_SET_FIELD(fb->format->has_alpha ? + SCALER_POS2_ALPHA_MODE_PIPELINE : + SCALER_POS2_ALPHA_MODE_FIXED, + SCALER_POS2_ALPHA_MODE) | + (mix_plane_alpha ? SCALER_POS2_ALPHA_MIX : 0) | + (fb->format->has_alpha ? + SCALER_POS2_ALPHA_PREMULT : 0) | + VC4_SET_FIELD(vc4_state->src_w[0], + SCALER_POS2_WIDTH) | + VC4_SET_FIELD(vc4_state->src_h[0], + SCALER_POS2_HEIGHT)); + + /* Position Word 3: Context. Written by the HVS. */ + vc4_dlist_write(vc4_state, 0xc0c0c0c0); - /* Position Word 3: Context. Written by the HVS. */ - vc4_dlist_write(vc4_state, 0xc0c0c0c0); + } else { + u32 hvs_pixel_order = format->pixel_order; + + if (format->pixel_order_hvs5) + hvs_pixel_order = format->pixel_order_hvs5; + + /* Control word */ + vc4_dlist_write(vc4_state, + SCALER_CTL0_VALID | + (hvs_pixel_order << SCALER_CTL0_ORDER_SHIFT) | + (hvs_format << SCALER_CTL0_PIXEL_FORMAT_SHIFT) | + VC4_SET_FIELD(tiling, SCALER_CTL0_TILING) | + (vc4_state->is_unity ? + SCALER5_CTL0_UNITY : 0) | + VC4_SET_FIELD(scl0, SCALER_CTL0_SCL0) | + VC4_SET_FIELD(scl1, SCALER_CTL0_SCL1) | + SCALER5_CTL0_ALPHA_EXPAND | + SCALER5_CTL0_RGB_EXPAND); + + /* Position Word 0: Image Positions and Alpha Value */ + vc4_state->pos0_offset = vc4_state->dlist_count; + vc4_dlist_write(vc4_state, + (rotation & DRM_MODE_REFLECT_Y ? + SCALER5_POS0_VFLIP : 0) | + VC4_SET_FIELD(vc4_state->crtc_x, + SCALER_POS0_START_X) | + (rotation & DRM_MODE_REFLECT_X ? + SCALER5_POS0_HFLIP : 0) | + VC4_SET_FIELD(vc4_state->crtc_y, + SCALER5_POS0_START_Y) + ); + + /* Control Word 2 */ + vc4_dlist_write(vc4_state, + VC4_SET_FIELD(state->alpha >> 4, + SCALER5_CTL2_ALPHA) | + (fb->format->has_alpha ? + SCALER5_CTL2_ALPHA_PREMULT : 0) | + (mix_plane_alpha ? + SCALER5_CTL2_ALPHA_MIX : 0) | + VC4_SET_FIELD(fb->format->has_alpha ? + SCALER5_CTL2_ALPHA_MODE_PIPELINE : + SCALER5_CTL2_ALPHA_MODE_FIXED, + SCALER5_CTL2_ALPHA_MODE) + ); + + /* Position Word 1: Scaled Image Dimensions. */ + if (!vc4_state->is_unity) { + vc4_dlist_write(vc4_state, + VC4_SET_FIELD(vc4_state->crtc_w, + SCALER_POS1_SCL_WIDTH) | + VC4_SET_FIELD(vc4_state->crtc_h, + SCALER_POS1_SCL_HEIGHT)); + } + + /* Position Word 2: Source Image Size */ + vc4_state->pos2_offset = vc4_state->dlist_count; + vc4_dlist_write(vc4_state, + VC4_SET_FIELD(vc4_state->src_w[0], + SCALER5_POS2_WIDTH) | + VC4_SET_FIELD(vc4_state->src_h[0], + SCALER5_POS2_HEIGHT)); + + /* Position Word 3: Context. Written by the HVS. */ + vc4_dlist_write(vc4_state, 0xc0c0c0c0); + } /* Pointer Word 0/1/2: RGB / Y / Cb / Cr Pointers @@ -1203,6 +1305,10 @@ static bool vc4_format_mod_supported(struct drm_plane *plane, default: return false; } + case DRM_FORMAT_RGBX1010102: + case DRM_FORMAT_BGRX1010102: + case DRM_FORMAT_RGBA1010102: + case DRM_FORMAT_BGRA1010102: case DRM_FORMAT_YUV422: case DRM_FORMAT_YVU422: case DRM_FORMAT_YUV420: @@ -1255,6 +1361,8 @@ struct drm_plane *vc4_plane_init(struct drm_device *dev, &vc4_plane_funcs, formats, ARRAY_SIZE(formats), modifiers, type, NULL); + if (ret) + return ERR_PTR(ret); drm_plane_helper_add(plane, &vc4_plane_helper_funcs); @@ -1283,7 +1391,7 @@ int vc4_plane_create_additional_planes(struct drm_device *drm) * modest number of planes to expose, that should hopefully * still cover any sane usecase. */ - for (i = 0; i < 8; i++) { + for (i = 0; i < 16; i++) { struct drm_plane *plane = vc4_plane_init(drm, DRM_PLANE_TYPE_OVERLAY); diff --git a/drivers/gpu/drm/vc4/vc4_regs.h b/drivers/gpu/drm/vc4/vc4_regs.h index 324462cc9cd4..be2c32a519b3 100644 --- a/drivers/gpu/drm/vc4/vc4_regs.h +++ b/drivers/gpu/drm/vc4/vc4_regs.h @@ -129,6 +129,8 @@ #define V3D_ERRSTAT 0x00f20 #define PV_CONTROL 0x00 +# define PV5_CONTROL_FIFO_LEVEL_HIGH_MASK VC4_MASK(26, 25) +# define PV5_CONTROL_FIFO_LEVEL_HIGH_SHIFT 25 # define PV_CONTROL_FORMAT_MASK VC4_MASK(23, 21) # define PV_CONTROL_FORMAT_SHIFT 21 # define PV_CONTROL_FORMAT_24 0 @@ -208,6 +210,11 @@ #define PV_HACT_ACT 0x30 +#define PV_MUX_CFG 0x34 +# define PV_MUX_CFG_RGB_PIXEL_MUX_MODE_MASK VC4_MASK(5, 2) +# define PV_MUX_CFG_RGB_PIXEL_MUX_MODE_SHIFT 2 +# define PV_MUX_CFG_RGB_PIXEL_MUX_MODE_NO_SWAP 8 + #define SCALER_CHANNELS_COUNT 3 #define SCALER_DISPCTRL 0x00000000 @@ -286,9 +293,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 @@ -327,6 +344,20 @@ # define SCALER_DISPCTRLX_HEIGHT_MASK VC4_MASK(11, 0) # define SCALER_DISPCTRLX_HEIGHT_SHIFT 0 +# define SCALER5_DISPCTRLX_WIDTH_MASK VC4_MASK(28, 16) +# define SCALER5_DISPCTRLX_WIDTH_SHIFT 16 +/* Generates a single frame when VSTART is seen and stops at the last + * pixel read from the FIFO. + */ +# define SCALER5_DISPCTRLX_ONESHOT BIT(15) +/* Processes a single context in the dlist and then task switch, + * instead of an entire line. + */ +# define SCALER5_DISPCTRLX_ONECTX_MASK VC4_MASK(14, 13) +# define SCALER5_DISPCTRLX_ONECTX_SHIFT 13 +# define SCALER5_DISPCTRLX_HEIGHT_MASK VC4_MASK(12, 0) +# define SCALER5_DISPCTRLX_HEIGHT_SHIFT 0 + #define SCALER_DISPBKGND0 0x00000044 # define SCALER_DISPBKGND_AUTOHS BIT(31) # define SCALER_DISPBKGND_INTERLACE BIT(30) @@ -460,32 +491,18 @@ #define SCALER_DLIST_START 0x00002000 #define SCALER_DLIST_SIZE 0x00004000 -#define VC4_HDMI_CORE_REV 0x000 +#define SCALER5_DLIST_START 0x00004000 -#define VC4_HDMI_SW_RESET_CONTROL 0x004 # define VC4_HDMI_SW_RESET_FORMAT_DETECT BIT(1) # define VC4_HDMI_SW_RESET_HDMI BIT(0) -#define VC4_HDMI_HOTPLUG_INT 0x008 - -#define VC4_HDMI_HOTPLUG 0x00c # define VC4_HDMI_HOTPLUG_CONNECTED BIT(0) -/* 3 bits per field, where each field maps from that corresponding MAI - * bus channel to the given HDMI channel. - */ -#define VC4_HDMI_MAI_CHANNEL_MAP 0x090 - -#define VC4_HDMI_MAI_CONFIG 0x094 # define VC4_HDMI_MAI_CONFIG_FORMAT_REVERSE BIT(27) # define VC4_HDMI_MAI_CONFIG_BIT_REVERSE BIT(26) # define VC4_HDMI_MAI_CHANNEL_MASK_MASK VC4_MASK(15, 0) # define VC4_HDMI_MAI_CHANNEL_MASK_SHIFT 0 -/* Last received format word on the MAI bus. */ -#define VC4_HDMI_MAI_FORMAT 0x098 - -#define VC4_HDMI_AUDIO_PACKET_CONFIG 0x09c # define VC4_HDMI_AUDIO_PACKET_ZERO_DATA_ON_SAMPLE_FLAT BIT(29) # define VC4_HDMI_AUDIO_PACKET_ZERO_DATA_ON_INACTIVE_CHANNELS BIT(24) # define VC4_HDMI_AUDIO_PACKET_FORCE_SAMPLE_PRESENT BIT(19) @@ -499,12 +516,8 @@ # define VC4_HDMI_AUDIO_PACKET_CEA_MASK_MASK VC4_MASK(7, 0) # define VC4_HDMI_AUDIO_PACKET_CEA_MASK_SHIFT 0 -#define VC4_HDMI_RAM_PACKET_CONFIG 0x0a0 # define VC4_HDMI_RAM_PACKET_ENABLE BIT(16) -#define VC4_HDMI_RAM_PACKET_STATUS 0x0a4 - -#define VC4_HDMI_CRP_CFG 0x0a8 /* When set, the CTS_PERIOD counts based on MAI bus sync pulse instead * of pixel clock. */ @@ -518,23 +531,12 @@ # define VC4_HDMI_CRP_CFG_N_MASK VC4_MASK(19, 0) # define VC4_HDMI_CRP_CFG_N_SHIFT 0 -/* 20-bit fields containing CTS values to be transmitted if !EXTERNAL_CTS_EN */ -#define VC4_HDMI_CTS_0 0x0ac -#define VC4_HDMI_CTS_1 0x0b0 -/* 20-bit fields containing number of clocks to send CTS0/1 before - * switching to the other one. - */ -#define VC4_HDMI_CTS_PERIOD_0 0x0b4 -#define VC4_HDMI_CTS_PERIOD_1 0x0b8 - -#define VC4_HDMI_HORZA 0x0c4 # define VC4_HDMI_HORZA_VPOS BIT(14) # define VC4_HDMI_HORZA_HPOS BIT(13) /* Horizontal active pixels (hdisplay). */ # define VC4_HDMI_HORZA_HAP_MASK VC4_MASK(12, 0) # define VC4_HDMI_HORZA_HAP_SHIFT 0 -#define VC4_HDMI_HORZB 0x0c8 /* Horizontal pack porch (htotal - hsync_end). */ # define VC4_HDMI_HORZB_HBP_MASK VC4_MASK(29, 20) # define VC4_HDMI_HORZB_HBP_SHIFT 20 @@ -545,7 +547,6 @@ # define VC4_HDMI_HORZB_HFP_MASK VC4_MASK(9, 0) # define VC4_HDMI_HORZB_HFP_SHIFT 0 -#define VC4_HDMI_FIFO_CTL 0x05c # define VC4_HDMI_FIFO_CTL_RECENTER_DONE BIT(14) # define VC4_HDMI_FIFO_CTL_USE_EMPTY BIT(13) # define VC4_HDMI_FIFO_CTL_ON_VB BIT(7) @@ -558,15 +559,12 @@ # define VC4_HDMI_FIFO_CTL_MASTER_SLAVE_N BIT(0) # define VC4_HDMI_FIFO_VALID_WRITE_MASK 0xefff -#define VC4_HDMI_SCHEDULER_CONTROL 0x0c0 # define VC4_HDMI_SCHEDULER_CONTROL_MANUAL_FORMAT BIT(15) # define VC4_HDMI_SCHEDULER_CONTROL_IGNORE_VSYNC_PREDICTS BIT(5) # define VC4_HDMI_SCHEDULER_CONTROL_VERT_ALWAYS_KEEPOUT BIT(3) # define VC4_HDMI_SCHEDULER_CONTROL_HDMI_ACTIVE BIT(1) # define VC4_HDMI_SCHEDULER_CONTROL_MODE_HDMI BIT(0) -#define VC4_HDMI_VERTA0 0x0cc -#define VC4_HDMI_VERTA1 0x0d4 /* Vertical sync pulse (vsync_end - vsync_start). */ # define VC4_HDMI_VERTA_VSP_MASK VC4_MASK(24, 20) # define VC4_HDMI_VERTA_VSP_SHIFT 20 @@ -577,8 +575,6 @@ # define VC4_HDMI_VERTA_VAL_MASK VC4_MASK(12, 0) # define VC4_HDMI_VERTA_VAL_SHIFT 0 -#define VC4_HDMI_VERTB0 0x0d0 -#define VC4_HDMI_VERTB1 0x0d8 /* Vertical sync pulse offset (for interlaced) */ # define VC4_HDMI_VERTB_VSPO_MASK VC4_MASK(21, 9) # define VC4_HDMI_VERTB_VSPO_SHIFT 9 @@ -586,7 +582,6 @@ # define VC4_HDMI_VERTB_VBP_MASK VC4_MASK(8, 0) # define VC4_HDMI_VERTB_VBP_SHIFT 0 -#define VC4_HDMI_CEC_CNTRL_1 0x0e8 /* Set when the transmission has ended. */ # define VC4_HDMI_CEC_TX_EOM BIT(31) /* If set, transmission was acked on the 1st or 2nd attempt (only one @@ -627,7 +622,6 @@ /* Set these fields to how many bit clock cycles get to that many * microseconds. */ -#define VC4_HDMI_CEC_CNTRL_2 0x0ec # define VC4_HDMI_CEC_CNT_TO_1500_US_MASK VC4_MASK(30, 24) # define VC4_HDMI_CEC_CNT_TO_1500_US_SHIFT 24 # define VC4_HDMI_CEC_CNT_TO_1300_US_MASK VC4_MASK(23, 17) @@ -639,7 +633,6 @@ # define VC4_HDMI_CEC_CNT_TO_400_US_MASK VC4_MASK(4, 0) # define VC4_HDMI_CEC_CNT_TO_400_US_SHIFT 0 -#define VC4_HDMI_CEC_CNTRL_3 0x0f0 # define VC4_HDMI_CEC_CNT_TO_2750_US_MASK VC4_MASK(31, 24) # define VC4_HDMI_CEC_CNT_TO_2750_US_SHIFT 24 # define VC4_HDMI_CEC_CNT_TO_2400_US_MASK VC4_MASK(23, 16) @@ -649,7 +642,6 @@ # define VC4_HDMI_CEC_CNT_TO_1700_US_MASK VC4_MASK(7, 0) # define VC4_HDMI_CEC_CNT_TO_1700_US_SHIFT 0 -#define VC4_HDMI_CEC_CNTRL_4 0x0f4 # define VC4_HDMI_CEC_CNT_TO_4300_US_MASK VC4_MASK(31, 24) # define VC4_HDMI_CEC_CNT_TO_4300_US_SHIFT 24 # define VC4_HDMI_CEC_CNT_TO_3900_US_MASK VC4_MASK(23, 16) @@ -659,7 +651,6 @@ # define VC4_HDMI_CEC_CNT_TO_3500_US_MASK VC4_MASK(7, 0) # define VC4_HDMI_CEC_CNT_TO_3500_US_SHIFT 0 -#define VC4_HDMI_CEC_CNTRL_5 0x0f8 # define VC4_HDMI_CEC_TX_SW_RESET BIT(27) # define VC4_HDMI_CEC_RX_SW_RESET BIT(26) # define VC4_HDMI_CEC_PAD_SW_RESET BIT(25) @@ -672,39 +663,11 @@ # define VC4_HDMI_CEC_CNT_TO_4500_US_MASK VC4_MASK(7, 0) # define VC4_HDMI_CEC_CNT_TO_4500_US_SHIFT 0 -/* Transmit data, first byte is low byte of the 32-bit reg. MSB of - * each byte transmitted first. - */ -#define VC4_HDMI_CEC_TX_DATA_1 0x0fc -#define VC4_HDMI_CEC_TX_DATA_2 0x100 -#define VC4_HDMI_CEC_TX_DATA_3 0x104 -#define VC4_HDMI_CEC_TX_DATA_4 0x108 -#define VC4_HDMI_CEC_RX_DATA_1 0x10c -#define VC4_HDMI_CEC_RX_DATA_2 0x110 -#define VC4_HDMI_CEC_RX_DATA_3 0x114 -#define VC4_HDMI_CEC_RX_DATA_4 0x118 - -#define VC4_HDMI_TX_PHY_RESET_CTL 0x2c0 - -#define VC4_HDMI_TX_PHY_CTL0 0x2c4 # define VC4_HDMI_TX_PHY_RNG_PWRDN BIT(25) -/* Interrupt status bits */ -#define VC4_HDMI_CPU_STATUS 0x340 -#define VC4_HDMI_CPU_SET 0x344 -#define VC4_HDMI_CPU_CLEAR 0x348 # define VC4_HDMI_CPU_CEC BIT(6) # define VC4_HDMI_CPU_HOTPLUG BIT(0) -#define VC4_HDMI_CPU_MASK_STATUS 0x34c -#define VC4_HDMI_CPU_MASK_SET 0x350 -#define VC4_HDMI_CPU_MASK_CLEAR 0x354 - -#define VC4_HDMI_GCP(x) (0x400 + ((x) * 0x4)) -#define VC4_HDMI_RAM_PACKET(x) (0x400 + ((x) * 0x24)) -#define VC4_HDMI_PACKET_STRIDE 0x24 - -#define VC4_HD_M_CTL 0x00c /* Debug: Current receive value on the CEC pad. */ # define VC4_HD_CECRXD BIT(9) /* Debug: Override CEC output to 0. */ @@ -714,7 +677,6 @@ # define VC4_HD_M_SW_RST BIT(2) # define VC4_HD_M_ENABLE BIT(0) -#define VC4_HD_MAI_CTL 0x014 /* Set when audio stream is received at a slower rate than the * sampling period, so MAI fifo goes empty. Write 1 to clear. */ @@ -739,7 +701,6 @@ /* Single-shot reset bit. Read value is undefined. */ # define VC4_HD_MAI_CTL_RESET BIT(0) -#define VC4_HD_MAI_THR 0x018 # define VC4_HD_MAI_THR_PANICHIGH_MASK VC4_MASK(29, 24) # define VC4_HD_MAI_THR_PANICHIGH_SHIFT 24 # define VC4_HD_MAI_THR_PANICLOW_MASK VC4_MASK(21, 16) @@ -749,31 +710,23 @@ # define VC4_HD_MAI_THR_DREQLOW_MASK VC4_MASK(5, 0) # define VC4_HD_MAI_THR_DREQLOW_SHIFT 0 -/* Format header to be placed on the MAI data. Unused. */ -#define VC4_HD_MAI_FMT 0x01c - -/* Register for DMAing in audio data to be transported over the MAI - * bus to the Falcon core. - */ -#define VC4_HD_MAI_DATA 0x020 - /* Divider from HDMI HSM clock to MAI serial clock. Sampling period * converges to N / (M + 1) cycles. */ -#define VC4_HD_MAI_SMP 0x02c # define VC4_HD_MAI_SMP_N_MASK VC4_MASK(31, 8) # define VC4_HD_MAI_SMP_N_SHIFT 8 # define VC4_HD_MAI_SMP_M_MASK VC4_MASK(7, 0) # define VC4_HD_MAI_SMP_M_SHIFT 0 -#define VC4_HD_VID_CTL 0x038 # define VC4_HD_VID_CTL_ENABLE BIT(31) # define VC4_HD_VID_CTL_UNDERFLOW_ENABLE BIT(30) # define VC4_HD_VID_CTL_FRAME_COUNTER_RESET BIT(29) # define VC4_HD_VID_CTL_VSYNC_LOW BIT(28) # define VC4_HD_VID_CTL_HSYNC_LOW BIT(27) +# define VC4_HD_VID_CTL_CLRSYNC BIT(24) +# define VC4_HD_VID_CTL_CLRRGB BIT(23) +# define VC4_HD_VID_CTL_BLANKPIX BIT(18) -#define VC4_HD_CSC_CTL 0x040 # define VC4_HD_CSC_CTL_ORDER_MASK VC4_MASK(7, 5) # define VC4_HD_CSC_CTL_ORDER_SHIFT 5 # define VC4_HD_CSC_CTL_ORDER_RGB 0 @@ -791,14 +744,7 @@ # define VC4_HD_CSC_CTL_RGB2YCC BIT(1) # define VC4_HD_CSC_CTL_ENABLE BIT(0) -#define VC4_HD_CSC_12_11 0x044 -#define VC4_HD_CSC_14_13 0x048 -#define VC4_HD_CSC_22_21 0x04c -#define VC4_HD_CSC_24_23 0x050 -#define VC4_HD_CSC_32_31 0x054 -#define VC4_HD_CSC_34_33 0x058 - -#define VC4_HD_FRAME_COUNT 0x068 +# define VC4_DVP_HT_CLOCK_STOP_PIXEL BIT(1) /* HVS display list information. */ #define HVS_BOOTLOADER_DLIST_END 32 @@ -825,6 +771,8 @@ enum hvs_pixel_format { HVS_PIXEL_FORMAT_PALETTE = 13, HVS_PIXEL_FORMAT_YUV444_RGB = 14, HVS_PIXEL_FORMAT_AYUV444_RGB = 15, + HVS_PIXEL_FORMAT_RGBA1010102 = 16, + HVS_PIXEL_FORMAT_YCBCR_10BIT = 17, }; /* Note: the LSB is the rightmost character shown. Only valid for @@ -879,6 +827,10 @@ enum hvs_pixel_format { #define SCALER_CTL0_RGBA_EXPAND_MSB 2 #define SCALER_CTL0_RGBA_EXPAND_ROUND 3 +#define SCALER5_CTL0_ALPHA_EXPAND BIT(12) + +#define SCALER5_CTL0_RGB_EXPAND BIT(11) + #define SCALER_CTL0_SCL1_MASK VC4_MASK(10, 8) #define SCALER_CTL0_SCL1_SHIFT 8 @@ -896,10 +848,13 @@ enum hvs_pixel_format { /* Set to indicate no scaling. */ #define SCALER_CTL0_UNITY BIT(4) +#define SCALER5_CTL0_UNITY BIT(15) #define SCALER_CTL0_PIXEL_FORMAT_MASK VC4_MASK(3, 0) #define SCALER_CTL0_PIXEL_FORMAT_SHIFT 0 +#define SCALER5_CTL0_PIXEL_FORMAT_MASK VC4_MASK(4, 0) + #define SCALER_POS0_FIXED_ALPHA_MASK VC4_MASK(31, 24) #define SCALER_POS0_FIXED_ALPHA_SHIFT 24 @@ -909,12 +864,48 @@ enum hvs_pixel_format { #define SCALER_POS0_START_X_MASK VC4_MASK(11, 0) #define SCALER_POS0_START_X_SHIFT 0 +#define SCALER5_POS0_START_Y_MASK VC4_MASK(27, 16) +#define SCALER5_POS0_START_Y_SHIFT 16 + +#define SCALER5_POS0_START_X_MASK VC4_MASK(13, 0) +#define SCALER5_POS0_START_X_SHIFT 0 + +#define SCALER5_POS0_VFLIP BIT(31) +#define SCALER5_POS0_HFLIP BIT(15) + +#define SCALER5_CTL2_ALPHA_MODE_MASK VC4_MASK(31, 30) +#define SCALER5_CTL2_ALPHA_MODE_SHIFT 30 +#define SCALER5_CTL2_ALPHA_MODE_PIPELINE 0 +#define SCALER5_CTL2_ALPHA_MODE_FIXED 1 +#define SCALER5_CTL2_ALPHA_MODE_FIXED_NONZERO 2 +#define SCALER5_CTL2_ALPHA_MODE_FIXED_OVER_0x07 3 + +#define SCALER5_CTL2_ALPHA_PREMULT BIT(29) + +#define SCALER5_CTL2_ALPHA_MIX BIT(28) + +#define SCALER5_CTL2_ALPHA_LOC BIT(25) + +#define SCALER5_CTL2_MAP_SEL_MASK VC4_MASK(18, 17) +#define SCALER5_CTL2_MAP_SEL_SHIFT 17 + +#define SCALER5_CTL2_GAMMA BIT(16) + +#define SCALER5_CTL2_ALPHA_MASK VC4_MASK(15, 4) +#define SCALER5_CTL2_ALPHA_SHIFT 4 + #define SCALER_POS1_SCL_HEIGHT_MASK VC4_MASK(27, 16) #define SCALER_POS1_SCL_HEIGHT_SHIFT 16 #define SCALER_POS1_SCL_WIDTH_MASK VC4_MASK(11, 0) #define SCALER_POS1_SCL_WIDTH_SHIFT 0 +#define SCALER5_POS1_SCL_HEIGHT_MASK VC4_MASK(28, 16) +#define SCALER5_POS1_SCL_HEIGHT_SHIFT 16 + +#define SCALER5_POS1_SCL_WIDTH_MASK VC4_MASK(12, 0) +#define SCALER5_POS1_SCL_WIDTH_SHIFT 0 + #define SCALER_POS2_ALPHA_MODE_MASK VC4_MASK(31, 30) #define SCALER_POS2_ALPHA_MODE_SHIFT 30 #define SCALER_POS2_ALPHA_MODE_PIPELINE 0 @@ -930,6 +921,12 @@ enum hvs_pixel_format { #define SCALER_POS2_WIDTH_MASK VC4_MASK(11, 0) #define SCALER_POS2_WIDTH_SHIFT 0 +#define SCALER5_POS2_HEIGHT_MASK VC4_MASK(28, 16) +#define SCALER5_POS2_HEIGHT_SHIFT 16 + +#define SCALER5_POS2_WIDTH_MASK VC4_MASK(12, 0) +#define SCALER5_POS2_WIDTH_SHIFT 0 + /* Color Space Conversion words. Some values are S2.8 signed * integers, except that the 2 integer bits map as {0x0: 0, 0x1: 1, * 0x2: 2, 0x3: -1} diff --git a/drivers/gpu/drm/vc4/vc4_txp.c b/drivers/gpu/drm/vc4/vc4_txp.c index a7c3af0005a0..849dcafbfff1 100644 --- a/drivers/gpu/drm/vc4/vc4_txp.c +++ b/drivers/gpu/drm/vc4/vc4_txp.c @@ -436,7 +436,6 @@ static const struct drm_crtc_helper_funcs vc4_txp_crtc_helper_funcs = { .atomic_flush = vc4_hvs_atomic_flush, .atomic_enable = vc4_txp_atomic_enable, .atomic_disable = vc4_txp_atomic_disable, - .mode_set_nofb = vc4_hvs_mode_set_nofb, }; static irqreturn_t vc4_txp_interrupt(int irq, void *data) @@ -452,7 +451,8 @@ static irqreturn_t vc4_txp_interrupt(int irq, void *data) } static const struct vc4_crtc_data vc4_txp_crtc_data = { - .hvs_channel = 2, + .hvs_available_channels = BIT(2), + .hvs_output = 2, }; static int vc4_txp_bind(struct device *dev, struct device *master, void *data) |