From 768f719ab7b045e30055be3b4ceb2fcf9ceb58f2 Mon Sep 17 00:00:00 2001 From: Russell King Date: Fri, 25 Jan 2019 09:47:05 +0000 Subject: [PATCH 01/18] drm/armada: fix crtc interlace We support interlace, but this was broken when we could no longer get a ref on the vblank interrupt. Arrange to get the ref on the vblank interrupt after we've re-enabled vblank, and put it before we disable the vblank. Signed-off-by: Russell King --- drivers/gpu/drm/armada/armada_crtc.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/armada/armada_crtc.c b/drivers/gpu/drm/armada/armada_crtc.c index ba4a3fab7745..09bade15f51c 100644 --- a/drivers/gpu/drm/armada/armada_crtc.c +++ b/drivers/gpu/drm/armada/armada_crtc.c @@ -278,16 +278,9 @@ static void armada_drm_crtc_mode_set_nofb(struct drm_crtc *crtc) armada_reg_queue_set(regs, i, sclk, LCD_CFG_SCLK_DIV); - if (interlaced ^ dcrtc->interlaced) { - if (adj->flags & DRM_MODE_FLAG_INTERLACE) - drm_crtc_vblank_get(&dcrtc->crtc); - else - drm_crtc_vblank_put(&dcrtc->crtc); - dcrtc->interlaced = interlaced; - } - spin_lock_irqsave(&dcrtc->irq_lock, flags); + dcrtc->interlaced = interlaced; /* Even interlaced/progressive frame */ dcrtc->v[1].spu_v_h_total = adj->crtc_vtotal << 16 | adj->crtc_htotal; @@ -390,6 +383,9 @@ static void armada_drm_crtc_atomic_disable(struct drm_crtc *crtc, DRM_DEBUG_KMS("[CRTC:%d:%s]\n", crtc->base.id, crtc->name); + if (old_state->adjusted_mode.flags & DRM_MODE_FLAG_INTERLACE) + drm_crtc_vblank_put(crtc); + drm_crtc_vblank_off(crtc); armada_drm_crtc_update(dcrtc, false); @@ -434,6 +430,9 @@ static void armada_drm_crtc_atomic_enable(struct drm_crtc *crtc, armada_drm_crtc_update(dcrtc, true); drm_crtc_vblank_on(crtc); + if (crtc->state->adjusted_mode.flags & DRM_MODE_FLAG_INTERLACE) + WARN_ON(drm_crtc_vblank_get(crtc)); + armada_drm_crtc_queue_state_event(crtc); } From ad52f53ff4e3167425b4b2195a23b9d2d0d243c6 Mon Sep 17 00:00:00 2001 From: Russell King Date: Fri, 25 Jan 2019 09:55:28 +0000 Subject: [PATCH 02/18] drm/armada: use __drm_atomic_helper_plane_reset in overlay reset Use the __drm_atomic_helper_plane_reset() helper in the overlay reset code to ensure that generic features are correctly reset in future. Signed-off-by: Russell King --- drivers/gpu/drm/armada/armada_overlay.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/armada/armada_overlay.c b/drivers/gpu/drm/armada/armada_overlay.c index 8d770641fcc4..f830f53d7adf 100644 --- a/drivers/gpu/drm/armada/armada_overlay.c +++ b/drivers/gpu/drm/armada/armada_overlay.c @@ -307,13 +307,10 @@ static void armada_overlay_reset(struct drm_plane *plane) if (plane->state) __drm_atomic_helper_plane_destroy_state(plane->state); kfree(plane->state); + plane->state = NULL; state = kzalloc(sizeof(*state), GFP_KERNEL); if (state) { - state->base.plane = plane; - state->base.color_encoding = DEFAULT_ENCODING; - state->base.color_range = DRM_COLOR_YCBCR_LIMITED_RANGE; - state->base.rotation = DRM_MODE_ROTATE_0; state->colorkey_yr = 0xfefefe00; state->colorkey_ug = 0x01010100; state->colorkey_vb = 0x01010100; @@ -323,8 +320,10 @@ static void armada_overlay_reset(struct drm_plane *plane) state->brightness = DEFAULT_BRIGHTNESS; state->contrast = DEFAULT_CONTRAST; state->saturation = DEFAULT_SATURATION; + __drm_atomic_helper_plane_reset(plane, &state->base); + state->base.color_encoding = DEFAULT_ENCODING; + state->base.color_range = DRM_COLOR_YCBCR_LIMITED_RANGE; } - plane->state = &state->base; } struct drm_plane_state * From 9184ae8db3d563438519cab295c2c9698255ca87 Mon Sep 17 00:00:00 2001 From: Russell King Date: Fri, 25 Jan 2019 10:28:20 +0000 Subject: [PATCH 03/18] drm/armada: add plane size/location accessors Add accessors for getting the register values for the plane from the plane state. This will allow us to generate the values when validating the plane rather than when programming, which allows us to fix the interlace handling without adding lots of additional handling in the update functions. Signed-off-by: Russell King --- drivers/gpu/drm/armada/armada_overlay.c | 12 ++++++------ drivers/gpu/drm/armada/armada_plane.c | 12 ++++++------ drivers/gpu/drm/armada/armada_plane.h | 4 ++++ 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/armada/armada_overlay.c b/drivers/gpu/drm/armada/armada_overlay.c index f830f53d7adf..8a3d2815cb58 100644 --- a/drivers/gpu/drm/armada/armada_overlay.c +++ b/drivers/gpu/drm/armada/armada_overlay.c @@ -94,14 +94,14 @@ static void armada_drm_overlay_plane_atomic_update(struct drm_plane *plane, armada_reg_queue_mod(regs, idx, 0, CFG_PDWN16x66 | CFG_PDWN32x66, LCD_SPU_SRAM_PARA1); - val = armada_rect_hw_fp(&state->src); - if (armada_rect_hw_fp(&old_state->src) != val) + val = armada_src_hw(state); + if (armada_src_hw(old_state) != val) armada_reg_queue_set(regs, idx, val, LCD_SPU_DMA_HPXL_VLN); - val = armada_rect_yx(&state->dst); - if (armada_rect_yx(&old_state->dst) != val) + val = armada_dst_yx(state); + if (armada_dst_yx(old_state) != val) armada_reg_queue_set(regs, idx, val, LCD_SPU_DMA_OVSA_HPXL_VLN); - val = armada_rect_hw(&state->dst); - if (armada_rect_hw(&old_state->dst) != val) + val = armada_dst_hw(state); + if (armada_dst_hw(old_state) != val) armada_reg_queue_set(regs, idx, val, LCD_SPU_DZM_HPXL_VLN); /* FIXME: overlay on an interlaced display */ if (old_state->src.x1 != state->src.x1 || diff --git a/drivers/gpu/drm/armada/armada_plane.c b/drivers/gpu/drm/armada/armada_plane.c index 9f36423dd394..6c098d379ae2 100644 --- a/drivers/gpu/drm/armada/armada_plane.c +++ b/drivers/gpu/drm/armada/armada_plane.c @@ -173,14 +173,14 @@ static void armada_drm_primary_plane_atomic_update(struct drm_plane *plane, val |= CFG_PDWN256x24; armada_reg_queue_mod(regs, idx, 0, val, LCD_SPU_SRAM_PARA1); } - val = armada_rect_hw_fp(&state->src); - if (armada_rect_hw_fp(&old_state->src) != val) + val = armada_src_hw(state); + if (armada_src_hw(old_state) != val) armada_reg_queue_set(regs, idx, val, LCD_SPU_GRA_HPXL_VLN); - val = armada_rect_yx(&state->dst); - if (armada_rect_yx(&old_state->dst) != val) + val = armada_dst_yx(state); + if (armada_dst_yx(old_state) != val) armada_reg_queue_set(regs, idx, val, LCD_SPU_GRA_OVSA_HPXL_VLN); - val = armada_rect_hw(&state->dst); - if (armada_rect_hw(&old_state->dst) != val) + val = armada_dst_hw(state); + if (armada_dst_hw(old_state) != val) armada_reg_queue_set(regs, idx, val, LCD_SPU_GZM_HPXL_VLN); if (old_state->src.x1 != state->src.x1 || old_state->src.y1 != state->src.y1 || diff --git a/drivers/gpu/drm/armada/armada_plane.h b/drivers/gpu/drm/armada/armada_plane.h index ff4281ba7fad..049c593342eb 100644 --- a/drivers/gpu/drm/armada/armada_plane.h +++ b/drivers/gpu/drm/armada/armada_plane.h @@ -1,6 +1,10 @@ #ifndef ARMADA_PLANE_H #define ARMADA_PLANE_H +#define armada_src_hw(state) armada_rect_hw_fp(&(state)->src) +#define armada_dst_yx(state) armada_rect_yx(&(state)->dst) +#define armada_dst_hw(state) armada_rect_hw(&(state)->dst) + void armada_drm_plane_calc(struct drm_plane_state *state, u32 addrs[2][3], u16 pitches[3], bool interlaced); int armada_drm_plane_prepare_fb(struct drm_plane *plane, From 1d1547ec12bc7d4d03c21704d4445aff8bcac8c1 Mon Sep 17 00:00:00 2001 From: Russell King Date: Fri, 25 Jan 2019 10:56:54 +0000 Subject: [PATCH 04/18] drm/armada: fix plane location and size for interlace When the CRTC is programmed for interlace, we have to halve the Y parameters for the plane. Rather than doing this in the update function (which would need the calculation repeated for the old state as well as the new state), arrange to do the calculation in atomic_check and save it in our private plane state structure. Signed-off-by: Russell King --- drivers/gpu/drm/armada/armada_hw.h | 15 ----- drivers/gpu/drm/armada/armada_overlay.c | 15 ++--- drivers/gpu/drm/armada/armada_plane.c | 78 ++++++++++++++++++++----- drivers/gpu/drm/armada/armada_plane.h | 20 ++++++- 4 files changed, 90 insertions(+), 38 deletions(-) diff --git a/drivers/gpu/drm/armada/armada_hw.h b/drivers/gpu/drm/armada/armada_hw.h index 277580b36758..345dc4d0851e 100644 --- a/drivers/gpu/drm/armada/armada_hw.h +++ b/drivers/gpu/drm/armada/armada_hw.h @@ -316,19 +316,4 @@ enum { PWRDN_IRQ_LEVEL = 1 << 0, }; -static inline u32 armada_rect_hw_fp(struct drm_rect *r) -{ - return (drm_rect_height(r) & 0xffff0000) | drm_rect_width(r) >> 16; -} - -static inline u32 armada_rect_hw(struct drm_rect *r) -{ - return drm_rect_height(r) << 16 | (drm_rect_width(r) & 0x0000ffff); -} - -static inline u32 armada_rect_yx(struct drm_rect *r) -{ - return (r)->y1 << 16 | ((r)->x1 & 0x0000ffff); -} - #endif diff --git a/drivers/gpu/drm/armada/armada_overlay.c b/drivers/gpu/drm/armada/armada_overlay.c index 8a3d2815cb58..589cdb6a9dae 100644 --- a/drivers/gpu/drm/armada/armada_overlay.c +++ b/drivers/gpu/drm/armada/armada_overlay.c @@ -27,7 +27,7 @@ #define DEFAULT_ENCODING DRM_COLOR_YCBCR_BT601 struct armada_overlay_state { - struct drm_plane_state base; + struct armada_plane_state base; u32 colorkey_yr; u32 colorkey_ug; u32 colorkey_vb; @@ -38,7 +38,7 @@ struct armada_overlay_state { u16 saturation; }; #define drm_to_overlay_state(s) \ - container_of(s, struct armada_overlay_state, base) + container_of(s, struct armada_overlay_state, base.base) static inline u32 armada_spu_contrast(struct drm_plane_state *state) { @@ -320,9 +320,9 @@ static void armada_overlay_reset(struct drm_plane *plane) state->brightness = DEFAULT_BRIGHTNESS; state->contrast = DEFAULT_CONTRAST; state->saturation = DEFAULT_SATURATION; - __drm_atomic_helper_plane_reset(plane, &state->base); - state->base.color_encoding = DEFAULT_ENCODING; - state->base.color_range = DRM_COLOR_YCBCR_LIMITED_RANGE; + __drm_atomic_helper_plane_reset(plane, &state->base.base); + state->base.base.color_encoding = DEFAULT_ENCODING; + state->base.base.color_range = DRM_COLOR_YCBCR_LIMITED_RANGE; } } @@ -336,8 +336,9 @@ armada_overlay_duplicate_state(struct drm_plane *plane) state = kmemdup(plane->state, sizeof(*state), GFP_KERNEL); if (state) - __drm_atomic_helper_plane_duplicate_state(plane, &state->base); - return &state->base; + __drm_atomic_helper_plane_duplicate_state(plane, + &state->base.base); + return &state->base.base; } static int armada_overlay_set_property(struct drm_plane *plane, diff --git a/drivers/gpu/drm/armada/armada_plane.c b/drivers/gpu/drm/armada/armada_plane.c index 6c098d379ae2..09218c1d8a4b 100644 --- a/drivers/gpu/drm/armada/armada_plane.c +++ b/drivers/gpu/drm/armada/armada_plane.c @@ -126,20 +126,47 @@ void armada_drm_plane_cleanup_fb(struct drm_plane *plane, int armada_drm_plane_atomic_check(struct drm_plane *plane, struct drm_plane_state *state) { - if (state->fb && !WARN_ON(!state->crtc)) { - struct drm_crtc *crtc = state->crtc; - struct drm_crtc_state *crtc_state; + struct armada_plane_state *st = to_armada_plane_state(state); + struct drm_crtc *crtc = state->crtc; + struct drm_crtc_state *crtc_state; + bool interlace; + int ret; - if (state->state) - crtc_state = drm_atomic_get_existing_crtc_state(state->state, crtc); - else - crtc_state = crtc->state; - return drm_atomic_helper_check_plane_state(state, crtc_state, - 0, INT_MAX, - true, false); - } else { + if (!state->fb || WARN_ON(!state->crtc)) { state->visible = false; + return 0; } + + if (state->state) + crtc_state = drm_atomic_get_existing_crtc_state(state->state, crtc); + else + crtc_state = crtc->state; + + ret = drm_atomic_helper_check_plane_state(state, crtc_state, 0, + INT_MAX, true, false); + if (ret) + return ret; + + interlace = crtc_state->adjusted_mode.flags & DRM_MODE_FLAG_INTERLACE; + if (interlace) { + if ((state->dst.y1 | state->dst.y2) & 1) + return -EINVAL; + st->src_hw = drm_rect_height(&state->src) >> 17; + st->dst_yx = state->dst.y1 >> 1; + st->dst_hw = drm_rect_height(&state->dst) >> 1; + } else { + st->src_hw = drm_rect_height(&state->src) >> 16; + st->dst_yx = state->dst.y1; + st->dst_hw = drm_rect_height(&state->dst); + } + + st->src_hw <<= 16; + st->src_hw |= drm_rect_width(&state->src) >> 16; + st->dst_yx <<= 16; + st->dst_yx |= state->dst.x1 & 0x0000ffff; + st->dst_hw <<= 16; + st->dst_hw |= drm_rect_width(&state->dst) & 0x0000ffff; + return 0; } @@ -262,12 +289,37 @@ static const struct drm_plane_helper_funcs armada_primary_plane_helper_funcs = { .atomic_disable = armada_drm_primary_plane_atomic_disable, }; +void armada_plane_reset(struct drm_plane *plane) +{ + struct armada_plane_state *st; + if (plane->state) + __drm_atomic_helper_plane_destroy_state(plane->state); + kfree(plane->state); + st = kzalloc(sizeof(*st), GFP_KERNEL); + if (st) + __drm_atomic_helper_plane_reset(plane, &st->base); +} + +struct drm_plane_state *armada_plane_duplicate_state(struct drm_plane *plane) +{ + struct armada_plane_state *st; + + if (WARN_ON(!plane->state)) + return NULL; + + st = kmemdup(plane->state, sizeof(*st), GFP_KERNEL); + if (st) + __drm_atomic_helper_plane_duplicate_state(plane, &st->base); + + return &st->base; +} + static const struct drm_plane_funcs armada_primary_plane_funcs = { .update_plane = drm_atomic_helper_update_plane, .disable_plane = drm_atomic_helper_disable_plane, .destroy = drm_primary_helper_destroy, - .reset = drm_atomic_helper_plane_reset, - .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, + .reset = armada_plane_reset, + .atomic_duplicate_state = armada_plane_duplicate_state, .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, }; diff --git a/drivers/gpu/drm/armada/armada_plane.h b/drivers/gpu/drm/armada/armada_plane.h index 049c593342eb..396c45a4ea5e 100644 --- a/drivers/gpu/drm/armada/armada_plane.h +++ b/drivers/gpu/drm/armada/armada_plane.h @@ -1,9 +1,18 @@ #ifndef ARMADA_PLANE_H #define ARMADA_PLANE_H -#define armada_src_hw(state) armada_rect_hw_fp(&(state)->src) -#define armada_dst_yx(state) armada_rect_yx(&(state)->dst) -#define armada_dst_hw(state) armada_rect_hw(&(state)->dst) +struct armada_plane_state { + struct drm_plane_state base; + u32 src_hw; + u32 dst_yx; + u32 dst_hw; +}; + +#define to_armada_plane_state(st) \ + container_of(st, struct armada_plane_state, base) +#define armada_src_hw(state) to_armada_plane_state(state)->src_hw +#define armada_dst_yx(state) to_armada_plane_state(state)->dst_yx +#define armada_dst_hw(state) to_armada_plane_state(state)->dst_hw void armada_drm_plane_calc(struct drm_plane_state *state, u32 addrs[2][3], u16 pitches[3], bool interlaced); @@ -13,6 +22,11 @@ void armada_drm_plane_cleanup_fb(struct drm_plane *plane, struct drm_plane_state *old_state); int armada_drm_plane_atomic_check(struct drm_plane *plane, struct drm_plane_state *state); +void armada_plane_reset(struct drm_plane *plane); +struct drm_plane_state *armada_plane_duplicate_state(struct drm_plane *plane); +void armada_plane_destroy_state(struct drm_plane *plane, + struct drm_plane_state *state); + int armada_drm_primary_plane_init(struct drm_device *drm, struct drm_plane *primary); From 89e0c53ccfbcca4a492d98735d2eda307368f742 Mon Sep 17 00:00:00 2001 From: Russell King Date: Sun, 1 Jul 2018 17:01:26 +0100 Subject: [PATCH 05/18] drm/armada: add missing interlaced support for overlay frame Interlaced support has been missing from the overlay frame, which is sub-optimal. Add support for this missing feature. Signed-off-by: Russell King --- drivers/gpu/drm/armada/armada_overlay.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/armada/armada_overlay.c b/drivers/gpu/drm/armada/armada_overlay.c index 589cdb6a9dae..d8d6e4645c1f 100644 --- a/drivers/gpu/drm/armada/armada_overlay.c +++ b/drivers/gpu/drm/armada/armada_overlay.c @@ -106,12 +106,13 @@ static void armada_drm_overlay_plane_atomic_update(struct drm_plane *plane, /* FIXME: overlay on an interlaced display */ if (old_state->src.x1 != state->src.x1 || old_state->src.y1 != state->src.y1 || - old_state->fb != state->fb) { + old_state->fb != state->fb || + state->crtc->state->mode_changed) { const struct drm_format_info *format; u16 src_x, pitches[3]; u32 addrs[2][3]; - armada_drm_plane_calc(state, addrs, pitches, false); + armada_drm_plane_calc(state, addrs, pitches, dcrtc->interlaced); armada_reg_queue_set(regs, idx, addrs[0][0], LCD_SPU_DMA_START_ADDR_Y0); @@ -146,6 +147,8 @@ static void armada_drm_overlay_plane_atomic_update(struct drm_plane *plane, src_x = state->src.x1 >> 16; if (format->num_planes == 1 && src_x & (format->hsub - 1)) cfg ^= CFG_DMA_MOD(CFG_SWAPUV); + if (dcrtc->interlaced) + cfg |= CFG_DMA_FTOGGLE; cfg_mask = CFG_CBSH_ENA | CFG_DMAFORMAT | CFG_DMA_MOD(CFG_SWAPRB | CFG_SWAPUV | CFG_SWAPYU | CFG_YUV2RGB) | From 7d62237da6ec072292c2f738ebffbf10f6a101cf Mon Sep 17 00:00:00 2001 From: Russell King Date: Fri, 25 Jan 2019 11:14:21 +0000 Subject: [PATCH 06/18] drm/armada: move plane address and pitch calculation to atomic_check Move the plane address and pitch calculations to atomic_check rather than the update function, so we don't have to probe the interlace setting for the CRTC while updating the plane. Signed-off-by: Russell King --- drivers/gpu/drm/armada/armada_overlay.c | 23 +++++++++---------- drivers/gpu/drm/armada/armada_plane.c | 30 +++++++++---------------- drivers/gpu/drm/armada/armada_plane.h | 5 +++++ 3 files changed, 25 insertions(+), 33 deletions(-) diff --git a/drivers/gpu/drm/armada/armada_overlay.c b/drivers/gpu/drm/armada/armada_overlay.c index d8d6e4645c1f..a79d6e940435 100644 --- a/drivers/gpu/drm/armada/armada_overlay.c +++ b/drivers/gpu/drm/armada/armada_overlay.c @@ -109,27 +109,24 @@ static void armada_drm_overlay_plane_atomic_update(struct drm_plane *plane, old_state->fb != state->fb || state->crtc->state->mode_changed) { const struct drm_format_info *format; - u16 src_x, pitches[3]; - u32 addrs[2][3]; + u16 src_x; - armada_drm_plane_calc(state, addrs, pitches, dcrtc->interlaced); - - armada_reg_queue_set(regs, idx, addrs[0][0], + armada_reg_queue_set(regs, idx, armada_addr(state, 0, 0), LCD_SPU_DMA_START_ADDR_Y0); - armada_reg_queue_set(regs, idx, addrs[0][1], + armada_reg_queue_set(regs, idx, armada_addr(state, 0, 1), LCD_SPU_DMA_START_ADDR_U0); - armada_reg_queue_set(regs, idx, addrs[0][2], + armada_reg_queue_set(regs, idx, armada_addr(state, 0, 2), LCD_SPU_DMA_START_ADDR_V0); - armada_reg_queue_set(regs, idx, addrs[1][0], + armada_reg_queue_set(regs, idx, armada_addr(state, 1, 0), LCD_SPU_DMA_START_ADDR_Y1); - armada_reg_queue_set(regs, idx, addrs[1][1], + armada_reg_queue_set(regs, idx, armada_addr(state, 1, 1), LCD_SPU_DMA_START_ADDR_U1); - armada_reg_queue_set(regs, idx, addrs[1][2], + armada_reg_queue_set(regs, idx, armada_addr(state, 1, 2), LCD_SPU_DMA_START_ADDR_V1); - val = pitches[0] << 16 | pitches[0]; + val = armada_pitch(state, 0) << 16 | armada_pitch(state, 0); armada_reg_queue_set(regs, idx, val, LCD_SPU_DMA_PITCH_YC); - val = pitches[1] << 16 | pitches[2]; + val = armada_pitch(state, 1) << 16 | armada_pitch(state, 2); armada_reg_queue_set(regs, idx, val, LCD_SPU_DMA_PITCH_UV); cfg = CFG_DMA_FMT(drm_fb_to_armada_fb(state->fb)->fmt) | @@ -147,7 +144,7 @@ static void armada_drm_overlay_plane_atomic_update(struct drm_plane *plane, src_x = state->src.x1 >> 16; if (format->num_planes == 1 && src_x & (format->hsub - 1)) cfg ^= CFG_DMA_MOD(CFG_SWAPUV); - if (dcrtc->interlaced) + if (to_armada_plane_state(state)->interlace) cfg |= CFG_DMA_FTOGGLE; cfg_mask = CFG_CBSH_ENA | CFG_DMAFORMAT | CFG_DMA_MOD(CFG_SWAPRB | CFG_SWAPUV | diff --git a/drivers/gpu/drm/armada/armada_plane.c b/drivers/gpu/drm/armada/armada_plane.c index 09218c1d8a4b..45092fc3598e 100644 --- a/drivers/gpu/drm/armada/armada_plane.c +++ b/drivers/gpu/drm/armada/armada_plane.c @@ -79,23 +79,6 @@ void armada_drm_plane_calc(struct drm_plane_state *state, u32 addrs[2][3], } } -static unsigned armada_drm_crtc_calc_fb(struct drm_plane_state *state, - struct armada_regs *regs, bool interlaced) -{ - u16 pitches[3]; - u32 addrs[2][3]; - unsigned i = 0; - - armada_drm_plane_calc(state, addrs, pitches, interlaced); - - /* write offset, base, and pitch */ - armada_reg_queue_set(regs, i, addrs[0][0], LCD_CFG_GRA_START_ADDR0); - armada_reg_queue_set(regs, i, addrs[1][0], LCD_CFG_GRA_START_ADDR1); - armada_reg_queue_mod(regs, i, pitches[0], 0xffff, LCD_CFG_GRA_PITCH); - - return i; -} - int armada_drm_plane_prepare_fb(struct drm_plane *plane, struct drm_plane_state *state) { @@ -167,6 +150,9 @@ int armada_drm_plane_atomic_check(struct drm_plane *plane, st->dst_hw <<= 16; st->dst_hw |= drm_rect_width(&state->dst) & 0x0000ffff; + armada_drm_plane_calc(state, st->addrs, st->pitches, interlace); + st->interlace = interlace; + return 0; } @@ -213,8 +199,12 @@ static void armada_drm_primary_plane_atomic_update(struct drm_plane *plane, old_state->src.y1 != state->src.y1 || old_state->fb != state->fb || state->crtc->state->mode_changed) { - idx += armada_drm_crtc_calc_fb(state, regs + idx, - dcrtc->interlaced); + armada_reg_queue_set(regs, idx, armada_addr(state, 0, 0), + LCD_CFG_GRA_START_ADDR0); + armada_reg_queue_set(regs, idx, armada_addr(state, 1, 0), + LCD_CFG_GRA_START_ADDR1); + armada_reg_queue_mod(regs, idx, armada_pitch(state, 0), 0xffff, + LCD_CFG_GRA_PITCH); } if (old_state->fb != state->fb || state->crtc->state->mode_changed) { @@ -224,7 +214,7 @@ static void armada_drm_primary_plane_atomic_update(struct drm_plane *plane, cfg |= CFG_PALETTE_ENA; if (state->visible) cfg |= CFG_GRA_ENA; - if (dcrtc->interlaced) + if (to_armada_plane_state(state)->interlace) cfg |= CFG_GRA_FTOGGLE; cfg_mask = CFG_GRAFORMAT | CFG_GRA_MOD(CFG_SWAPRB | CFG_SWAPUV | diff --git a/drivers/gpu/drm/armada/armada_plane.h b/drivers/gpu/drm/armada/armada_plane.h index 396c45a4ea5e..2707ec781941 100644 --- a/drivers/gpu/drm/armada/armada_plane.h +++ b/drivers/gpu/drm/armada/armada_plane.h @@ -6,6 +6,9 @@ struct armada_plane_state { u32 src_hw; u32 dst_yx; u32 dst_hw; + u32 addrs[2][3]; + u16 pitches[3]; + bool interlace; }; #define to_armada_plane_state(st) \ @@ -13,6 +16,8 @@ struct armada_plane_state { #define armada_src_hw(state) to_armada_plane_state(state)->src_hw #define armada_dst_yx(state) to_armada_plane_state(state)->dst_yx #define armada_dst_hw(state) to_armada_plane_state(state)->dst_hw +#define armada_addr(state, f, p) to_armada_plane_state(state)->addrs[f][p] +#define armada_pitch(state, n) to_armada_plane_state(state)->pitches[n] void armada_drm_plane_calc(struct drm_plane_state *state, u32 addrs[2][3], u16 pitches[3], bool interlaced); From d0d765dec6b7e6dc5d6c153784f1a80a1f5bf3c5 Mon Sep 17 00:00:00 2001 From: Russell King Date: Sun, 1 Jul 2018 15:46:26 +0100 Subject: [PATCH 07/18] drm/armada: add support for setting gamma Add support for setting gamma through both the legacy interfaces and the atomic interfaces. Signed-off-by: Russell King --- drivers/gpu/drm/armada/armada_crtc.c | 63 +++++++++++++++++++++++++++ drivers/gpu/drm/armada/armada_hw.h | 4 ++ drivers/gpu/drm/armada/armada_plane.c | 2 +- 3 files changed, 68 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/armada/armada_crtc.c b/drivers/gpu/drm/armada/armada_crtc.c index 09bade15f51c..5991feb1bcc9 100644 --- a/drivers/gpu/drm/armada/armada_crtc.c +++ b/drivers/gpu/drm/armada/armada_crtc.c @@ -130,6 +130,44 @@ static void armada_drm_crtc_queue_state_event(struct drm_crtc *crtc) } } +static void armada_drm_update_gamma(struct drm_crtc *crtc) +{ + struct drm_property_blob *blob = crtc->state->gamma_lut; + void __iomem *base = drm_to_armada_crtc(crtc)->base; + int i; + + if (blob) { + struct drm_color_lut *lut = blob->data; + + armada_updatel(CFG_CSB_256x8, CFG_CSB_256x8 | CFG_PDWN256x8, + base + LCD_SPU_SRAM_PARA1); + + for (i = 0; i < 256; i++) { + writel_relaxed(drm_color_lut_extract(lut[i].red, 8), + base + LCD_SPU_SRAM_WRDAT); + writel_relaxed(i | SRAM_WRITE | SRAM_GAMMA_YR, + base + LCD_SPU_SRAM_CTRL); + readl_relaxed(base + LCD_SPU_HWC_OVSA_HPXL_VLN); + writel_relaxed(drm_color_lut_extract(lut[i].green, 8), + base + LCD_SPU_SRAM_WRDAT); + writel_relaxed(i | SRAM_WRITE | SRAM_GAMMA_UG, + base + LCD_SPU_SRAM_CTRL); + readl_relaxed(base + LCD_SPU_HWC_OVSA_HPXL_VLN); + writel_relaxed(drm_color_lut_extract(lut[i].blue, 8), + base + LCD_SPU_SRAM_WRDAT); + writel_relaxed(i | SRAM_WRITE | SRAM_GAMMA_VB, + base + LCD_SPU_SRAM_CTRL); + readl_relaxed(base + LCD_SPU_HWC_OVSA_HPXL_VLN); + } + armada_updatel(CFG_GAMMA_ENA, CFG_GAMMA_ENA, + base + LCD_SPU_DMA_CTRL0); + } else { + armada_updatel(0, CFG_GAMMA_ENA, base + LCD_SPU_DMA_CTRL0); + armada_updatel(CFG_PDWN256x8, CFG_CSB_256x8 | CFG_PDWN256x8, + base + LCD_SPU_SRAM_PARA1); + } +} + /* The mode_config.mutex will be held for this call */ static bool armada_drm_crtc_mode_fixup(struct drm_crtc *crtc, const struct drm_display_mode *mode, struct drm_display_mode *adj) @@ -338,6 +376,20 @@ static void armada_drm_crtc_mode_set_nofb(struct drm_crtc *crtc) spin_unlock_irqrestore(&dcrtc->irq_lock, flags); } +static int armada_drm_crtc_atomic_check(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + DRM_DEBUG_KMS("[CRTC:%d:%s]\n", crtc->base.id, crtc->name); + + if (state->gamma_lut && drm_color_lut_size(state->gamma_lut) != 256) + return -EINVAL; + + if (state->color_mgmt_changed) + state->planes_changed = true; + + return 0; +} + static void armada_drm_crtc_atomic_begin(struct drm_crtc *crtc, struct drm_crtc_state *old_crtc_state) { @@ -345,6 +397,9 @@ static void armada_drm_crtc_atomic_begin(struct drm_crtc *crtc, DRM_DEBUG_KMS("[CRTC:%d:%s]\n", crtc->base.id, crtc->name); + if (crtc->state->color_mgmt_changed) + armada_drm_update_gamma(crtc); + dcrtc->regs_idx = 0; dcrtc->regs = dcrtc->atomic_regs; } @@ -439,6 +494,7 @@ static void armada_drm_crtc_atomic_enable(struct drm_crtc *crtc, static const struct drm_crtc_helper_funcs armada_crtc_helper_funcs = { .mode_fixup = armada_drm_crtc_mode_fixup, .mode_set_nofb = armada_drm_crtc_mode_set_nofb, + .atomic_check = armada_drm_crtc_atomic_check, .atomic_begin = armada_drm_crtc_atomic_begin, .atomic_flush = armada_drm_crtc_atomic_flush, .atomic_disable = armada_drm_crtc_atomic_disable, @@ -702,6 +758,7 @@ static const struct drm_crtc_funcs armada_crtc_funcs = { .cursor_set = armada_drm_crtc_cursor_set, .cursor_move = armada_drm_crtc_cursor_move, .destroy = armada_drm_crtc_destroy, + .gamma_set = drm_atomic_helper_legacy_gamma_set, .set_config = drm_atomic_helper_set_config, .page_flip = drm_atomic_helper_page_flip, .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, @@ -793,6 +850,12 @@ static int armada_drm_crtc_create(struct drm_device *drm, struct device *dev, drm_crtc_helper_add(&dcrtc->crtc, &armada_crtc_helper_funcs); + ret = drm_mode_crtc_set_gamma_size(&dcrtc->crtc, 256); + if (ret) + return ret; + + drm_crtc_enable_color_mgmt(&dcrtc->crtc, 0, false, 256); + return armada_overlay_plane_create(drm, 1 << dcrtc->num); err_crtc_init: diff --git a/drivers/gpu/drm/armada/armada_hw.h b/drivers/gpu/drm/armada/armada_hw.h index 345dc4d0851e..babfca71c4db 100644 --- a/drivers/gpu/drm/armada/armada_hw.h +++ b/drivers/gpu/drm/armada/armada_hw.h @@ -169,6 +169,10 @@ enum { SRAM_READ = 0 << 14, SRAM_WRITE = 2 << 14, SRAM_INIT = 3 << 14, + SRAM_GAMMA_YR = 0x0 << 8, + SRAM_GAMMA_UG = 0x1 << 8, + SRAM_GAMMA_VB = 0x2 << 8, + SRAM_PALETTE = 0x3 << 8, SRAM_HWC32_RAM1 = 0xc << 8, SRAM_HWC32_RAM2 = 0xd << 8, SRAM_HWC32_RAMR = SRAM_HWC32_RAM1, diff --git a/drivers/gpu/drm/armada/armada_plane.c b/drivers/gpu/drm/armada/armada_plane.c index 45092fc3598e..dbd4d52e8a32 100644 --- a/drivers/gpu/drm/armada/armada_plane.c +++ b/drivers/gpu/drm/armada/armada_plane.c @@ -265,7 +265,7 @@ static void armada_drm_primary_plane_atomic_disable(struct drm_plane *plane, /* Disable plane and power down most RAMs and FIFOs */ armada_reg_queue_mod(regs, idx, 0, CFG_GRA_ENA, LCD_SPU_DMA_CTRL0); armada_reg_queue_mod(regs, idx, CFG_PDWN256x32 | CFG_PDWN256x24 | - CFG_PDWN256x8 | CFG_PDWN32x32 | CFG_PDWN64x66, + CFG_PDWN32x32 | CFG_PDWN64x66, 0, LCD_SPU_SRAM_PARA1); dcrtc->regs_idx += idx; From 5d32b660c839706fd9a3d4b0e65cb648bc07032d Mon Sep 17 00:00:00 2001 From: Russell King Date: Sun, 1 Jul 2018 15:46:54 +0100 Subject: [PATCH 08/18] drm/armada: add comments about HWC32 cursor colour format Add some comments about the format of the HWC32 cursor colour format. Signed-off-by: Russell King --- drivers/gpu/drm/armada/armada_crtc.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/gpu/drm/armada/armada_crtc.c b/drivers/gpu/drm/armada/armada_crtc.c index 5991feb1bcc9..e81e57823b66 100644 --- a/drivers/gpu/drm/armada/armada_crtc.c +++ b/drivers/gpu/drm/armada/armada_crtc.c @@ -515,6 +515,13 @@ static void armada_load_cursor_argb(void __iomem *base, uint32_t *pix, for (x = 0; x < width; x++, p++) { uint32_t val = *p; + /* + * In "ARGB888" (HWC32) mode, writing to the SRAM + * requires these bits to contain: + * 31:24 = alpha 23:16 = blue 15:8 = green 7:0 = red + * So, it's actually ABGR8888. This is independent + * of the SWAPRB bits in DMA control register 0. + */ val = (val & 0xff00ff00) | (val & 0x000000ff) << 16 | (val & 0x00ff0000) >> 16; From f79d7c9543d2318aff29edd1bbcc142301eb75c2 Mon Sep 17 00:00:00 2001 From: Russell King Date: Sun, 1 Jul 2018 15:46:56 +0100 Subject: [PATCH 09/18] drm/armada: add drm_mode_set_crtcinfo() mode fixup Add a drm_mode_set_crtcinfo() call in our CRTC's mode_fixup callback to ensure that any adjustments to the mode made by connectors etc are properly accounted for by the CRTC. Signed-off-by: Russell King --- drivers/gpu/drm/armada/armada_crtc.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/gpu/drm/armada/armada_crtc.c b/drivers/gpu/drm/armada/armada_crtc.c index e81e57823b66..d802a6bc2d9f 100644 --- a/drivers/gpu/drm/armada/armada_crtc.c +++ b/drivers/gpu/drm/armada/armada_crtc.c @@ -180,6 +180,13 @@ static bool armada_drm_crtc_mode_fixup(struct drm_crtc *crtc, adj->flags & DRM_MODE_FLAG_INTERLACE) return false; + /* + * Set CRTC modesetting parameters for the adjusted mode. This is + * applied after the connectors, bridges, and encoders have fixed up + * this mode, as described above drm_atomic_helper_check_modeset(). + */ + drm_mode_set_crtcinfo(adj, CRTC_INTERLACE_HALVE_V); + /* Check whether the display mode is possible */ ret = dcrtc->variant->compute_clock(dcrtc, adj, NULL); if (ret) From 5a6cbce823bfa13f4ef47049b9ba861e432d5bd2 Mon Sep 17 00:00:00 2001 From: Russell King Date: Sun, 1 Jul 2018 22:11:27 +0100 Subject: [PATCH 10/18] drm/armada: add and use definitions for RDREG4F Add and use bit definitions for RDREG4F on Dove Armada 510. Signed-off-by: Russell King --- drivers/gpu/drm/armada/armada_510.c | 9 +++++++-- drivers/gpu/drm/armada/armada_hw.h | 10 ++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/armada/armada_510.c b/drivers/gpu/drm/armada/armada_510.c index 0e91d27921bd..370c422f64e3 100644 --- a/drivers/gpu/drm/armada/armada_510.c +++ b/drivers/gpu/drm/armada/armada_510.c @@ -24,8 +24,13 @@ static int armada510_crtc_init(struct armada_crtc *dcrtc, struct device *dev) dcrtc->extclk[0] = clk; - /* Lower the watermark so to eliminate jitter at higher bandwidths */ - armada_updatel(0x20, (1 << 11) | 0xff, dcrtc->base + LCD_CFG_RDREG4F); + /* + * Lower the watermark so to eliminate jitter at higher bandwidths. + * Disable SRAM read wait state to avoid system hang with external + * clock. + */ + armada_updatel(CFG_DMA_WM(0x20), CFG_SRAM_WAIT | CFG_DMA_WM_MASK, + dcrtc->base + LCD_CFG_RDREG4F); /* Initialise SPU register */ writel_relaxed(ADV_HWC32ENABLE | ADV_HWC32ARGB | ADV_HWC32BLEND, diff --git a/drivers/gpu/drm/armada/armada_hw.h b/drivers/gpu/drm/armada/armada_hw.h index babfca71c4db..5b1af0cc9f50 100644 --- a/drivers/gpu/drm/armada/armada_hw.h +++ b/drivers/gpu/drm/armada/armada_hw.h @@ -88,6 +88,16 @@ enum { ADV_VSYNC_H_OFF = 0xfff << 0, }; +/* LCD_CFG_RDREG4F - Armada 510 only */ +enum { + CFG_SRAM_WAIT = BIT(11), + CFG_SMPN_FASTTX = BIT(10), + CFG_DMA_ARB = BIT(9), + CFG_DMA_WM_EN = BIT(8), + CFG_DMA_WM_MASK = 0xff, +#define CFG_DMA_WM(x) ((x) & CFG_DMA_WM_MASK) +}; + enum { CFG_565 = 0, CFG_1555 = 1, From 0ab17499b18f4b990600be177058957ab1681808 Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 2 Jul 2018 17:27:46 +0100 Subject: [PATCH 11/18] drm/armada: add drm_atomic_helper_shutdown() call in tear-down Ensure that the hardware is disabled prior to tearing down the modeset support. Signed-off-by: Russell King --- drivers/gpu/drm/armada/armada_drv.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/armada/armada_drv.c b/drivers/gpu/drm/armada/armada_drv.c index e660c5ca52ae..e524bfff1c49 100644 --- a/drivers/gpu/drm/armada/armada_drv.c +++ b/drivers/gpu/drm/armada/armada_drv.c @@ -171,6 +171,8 @@ static void armada_drm_unbind(struct device *dev) drm_dev_unregister(&priv->drm); + drm_atomic_helper_shutdown(&priv->drm); + component_unbind_all(dev, &priv->drm); drm_mode_config_cleanup(&priv->drm); From 7f07ce0f04e25cd41a85ee780dbe85b40c9e3482 Mon Sep 17 00:00:00 2001 From: Russell King Date: Sat, 14 Jul 2018 11:17:45 +0100 Subject: [PATCH 12/18] drm/armada: add CRTC mode validation Add CRTC mode validation to reject modes that the CRTC does not support. Signed-off-by: Russell King --- drivers/gpu/drm/armada/armada_crtc.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/drivers/gpu/drm/armada/armada_crtc.c b/drivers/gpu/drm/armada/armada_crtc.c index d802a6bc2d9f..edce74f60198 100644 --- a/drivers/gpu/drm/armada/armada_crtc.c +++ b/drivers/gpu/drm/armada/armada_crtc.c @@ -168,6 +168,25 @@ static void armada_drm_update_gamma(struct drm_crtc *crtc) } } +static enum drm_mode_status armada_drm_crtc_mode_valid(struct drm_crtc *crtc, + const struct drm_display_mode *mode) +{ + if (mode->vscan > 1) + return MODE_NO_VSCAN; + + if (mode->flags & DRM_MODE_FLAG_DBLSCAN) + return MODE_NO_DBLESCAN; + + if (mode->flags & DRM_MODE_FLAG_HSKEW) + return MODE_H_ILLEGAL; + + if (mode->flags & (DRM_MODE_FLAG_BCAST | DRM_MODE_FLAG_PIXMUX | + DRM_MODE_FLAG_CLKDIV2)) + return MODE_BAD; + + return MODE_OK; +} + /* The mode_config.mutex will be held for this call */ static bool armada_drm_crtc_mode_fixup(struct drm_crtc *crtc, const struct drm_display_mode *mode, struct drm_display_mode *adj) @@ -499,6 +518,7 @@ static void armada_drm_crtc_atomic_enable(struct drm_crtc *crtc, } static const struct drm_crtc_helper_funcs armada_crtc_helper_funcs = { + .mode_valid = armada_drm_crtc_mode_valid, .mode_fixup = armada_drm_crtc_mode_fixup, .mode_set_nofb = armada_drm_crtc_mode_set_nofb, .atomic_check = armada_drm_crtc_atomic_check, From 1ba246f2fd2f6e6647b29e362a7d8c235e7fe9a7 Mon Sep 17 00:00:00 2001 From: Russell King Date: Sat, 14 Jul 2018 11:15:35 +0100 Subject: [PATCH 13/18] drm/armada: improve Dove clock selection Improve the Dove (Armada 510) LCD clock selection and divider calculation, limiting to the valid divisor values, and reporting an error if the clock is not achievable within the bounds of HDMI clocking requirements. Signed-off-by: Russell King --- drivers/gpu/drm/armada/armada_510.c | 117 +++++++++++++++++++-------- drivers/gpu/drm/armada/armada_crtc.c | 78 +++++++++++++++++- drivers/gpu/drm/armada/armada_crtc.h | 21 ++++- 3 files changed, 181 insertions(+), 35 deletions(-) diff --git a/drivers/gpu/drm/armada/armada_510.c b/drivers/gpu/drm/armada/armada_510.c index 370c422f64e3..6f8ad8fb19f1 100644 --- a/drivers/gpu/drm/armada/armada_510.c +++ b/drivers/gpu/drm/armada/armada_510.c @@ -14,15 +14,54 @@ #include "armada_drm.h" #include "armada_hw.h" +struct armada510_variant_data { + struct clk *clks[4]; + struct clk *sel_clk; +}; + static int armada510_crtc_init(struct armada_crtc *dcrtc, struct device *dev) { + struct armada510_variant_data *v; struct clk *clk; + int idx; - clk = devm_clk_get(dev, "ext_ref_clk1"); - if (IS_ERR(clk)) - return PTR_ERR(clk) == -ENOENT ? -EPROBE_DEFER : PTR_ERR(clk); + v = devm_kzalloc(dev, sizeof(*v), GFP_KERNEL); + if (!v) + return -ENOMEM; - dcrtc->extclk[0] = clk; + dcrtc->variant_data = v; + + if (dev->of_node) { + struct property *prop; + const char *s; + + of_property_for_each_string(dev->of_node, "clock-names", prop, + s) { + if (!strcmp(s, "ext_ref_clk0")) + idx = 0; + else if (!strcmp(s, "ext_ref_clk1")) + idx = 1; + else if (!strcmp(s, "plldivider")) + idx = 2; + else if (!strcmp(s, "axibus")) + idx = 3; + else + continue; + + clk = devm_clk_get(dev, s); + if (IS_ERR(clk)) + return PTR_ERR(clk) == -ENOENT ? -EPROBE_DEFER : + PTR_ERR(clk); + v->clks[idx] = clk; + } + } else { + clk = devm_clk_get(dev, "ext_ref_clk1"); + if (IS_ERR(clk)) + return PTR_ERR(clk) == -ENOENT ? -EPROBE_DEFER : + PTR_ERR(clk); + + v->clks[1] = clk; + } /* * Lower the watermark so to eliminate jitter at higher bandwidths. @@ -39,65 +78,77 @@ static int armada510_crtc_init(struct armada_crtc *dcrtc, struct device *dev) return 0; } +static const u32 armada510_clk_sels[] = { + SCLK_510_EXTCLK0, + SCLK_510_EXTCLK1, + SCLK_510_PLL, + SCLK_510_AXI, +}; + +static const struct armada_clocking_params armada510_clocking = { + /* HDMI requires -0.6%..+0.5% */ + .permillage_min = 994, + .permillage_max = 1005, + .settable = BIT(0) | BIT(1), + .div_max = SCLK_510_INT_DIV_MASK, +}; + /* * Armada510 specific SCLK register selection. * This gets called with sclk = NULL to test whether the mode is * supportable, and again with sclk != NULL to set the clocks up for * that. The former can return an error, but the latter is expected * not to. - * - * We currently are pretty rudimentary here, always selecting - * EXT_REF_CLK_1 for LCD0 and erroring LCD1. This needs improvement! */ static int armada510_crtc_compute_clock(struct armada_crtc *dcrtc, const struct drm_display_mode *mode, uint32_t *sclk) { - struct clk *clk = dcrtc->extclk[0]; - int ret; + struct armada510_variant_data *v = dcrtc->variant_data; + unsigned long desired_khz = mode->crtc_clock; + struct armada_clk_result res; + int ret, idx; - if (dcrtc->num == 1) - return -EINVAL; + idx = armada_crtc_select_clock(dcrtc, &res, &armada510_clocking, + v->clks, ARRAY_SIZE(v->clks), + desired_khz); + if (idx < 0) + return idx; - if (IS_ERR(clk)) - return PTR_ERR(clk); - - if (dcrtc->clk != clk) { - ret = clk_prepare_enable(clk); - if (ret) - return ret; - dcrtc->clk = clk; - } + ret = clk_prepare_enable(res.clk); + if (ret) + return ret; if (sclk) { - uint32_t rate, ref, div; + clk_set_rate(res.clk, res.desired_clk_hz); - rate = mode->clock * 1000; - ref = clk_round_rate(clk, rate); - div = DIV_ROUND_UP(ref, rate); - if (div < 1) - div = 1; + *sclk = res.div | armada510_clk_sels[idx]; - clk_set_rate(clk, ref); - *sclk = div | SCLK_510_EXTCLK1; + /* We are now using this clock */ + v->sel_clk = res.clk; + swap(dcrtc->clk, res.clk); } + clk_disable_unprepare(res.clk); + return 0; } static void armada510_crtc_disable(struct armada_crtc *dcrtc) { - if (!IS_ERR(dcrtc->clk)) { + if (dcrtc->clk) { clk_disable_unprepare(dcrtc->clk); - dcrtc->clk = ERR_PTR(-EINVAL); + dcrtc->clk = NULL; } } static void armada510_crtc_enable(struct armada_crtc *dcrtc, const struct drm_display_mode *mode) { - if (IS_ERR(dcrtc->clk)) { - dcrtc->clk = dcrtc->extclk[0]; - WARN_ON(clk_prepare_enable(dcrtc->clk)); + struct armada510_variant_data *v = dcrtc->variant_data; + + if (!dcrtc->clk && v->sel_clk) { + if (!WARN_ON(clk_prepare_enable(v->sel_clk))) + dcrtc->clk = v->sel_clk; } } diff --git a/drivers/gpu/drm/armada/armada_crtc.c b/drivers/gpu/drm/armada/armada_crtc.c index edce74f60198..cffb4c31e9d3 100644 --- a/drivers/gpu/drm/armada/armada_crtc.c +++ b/drivers/gpu/drm/armada/armada_crtc.c @@ -801,6 +801,83 @@ static const struct drm_crtc_funcs armada_crtc_funcs = { .disable_vblank = armada_drm_crtc_disable_vblank, }; +int armada_crtc_select_clock(struct armada_crtc *dcrtc, + struct armada_clk_result *res, + const struct armada_clocking_params *params, + struct clk *clks[], size_t num_clks, + unsigned long desired_khz) +{ + unsigned long desired_hz = desired_khz * 1000; + unsigned long desired_clk_hz; // requested clk input + unsigned long real_clk_hz; // actual clk input + unsigned long real_hz; // actual pixel clk + unsigned long permillage; + struct clk *clk; + u32 div; + int i; + + DRM_DEBUG_KMS("[CRTC:%u:%s] desired clock=%luHz\n", + dcrtc->crtc.base.id, dcrtc->crtc.name, desired_hz); + + for (i = 0; i < num_clks; i++) { + clk = clks[i]; + if (!clk) + continue; + + if (params->settable & BIT(i)) { + real_clk_hz = clk_round_rate(clk, desired_hz); + desired_clk_hz = desired_hz; + } else { + real_clk_hz = clk_get_rate(clk); + desired_clk_hz = real_clk_hz; + } + + /* If the clock can do exactly the desired rate, we're done */ + if (real_clk_hz == desired_hz) { + real_hz = real_clk_hz; + div = 1; + goto found; + } + + /* Calculate the divider - if invalid, we can't do this rate */ + div = DIV_ROUND_CLOSEST(real_clk_hz, desired_hz); + if (div == 0 || div > params->div_max) + continue; + + /* Calculate the actual rate - HDMI requires -0.6%..+0.5% */ + real_hz = DIV_ROUND_CLOSEST(real_clk_hz, div); + + DRM_DEBUG_KMS("[CRTC:%u:%s] clk=%u %luHz div=%u real=%luHz\n", + dcrtc->crtc.base.id, dcrtc->crtc.name, + i, real_clk_hz, div, real_hz); + + /* Avoid repeated division */ + if (real_hz < desired_hz) { + permillage = real_hz / desired_khz; + if (permillage < params->permillage_min) + continue; + } else { + permillage = DIV_ROUND_UP(real_hz, desired_khz); + if (permillage > params->permillage_max) + continue; + } + goto found; + } + + return -ERANGE; + +found: + DRM_DEBUG_KMS("[CRTC:%u:%s] selected clk=%u %luHz div=%u real=%luHz\n", + dcrtc->crtc.base.id, dcrtc->crtc.name, + i, real_clk_hz, div, real_hz); + + res->desired_clk_hz = desired_clk_hz; + res->clk = clk; + res->div = div; + + return i; +} + static int armada_drm_crtc_create(struct drm_device *drm, struct device *dev, struct resource *res, int irq, const struct armada_variant *variant, struct device_node *port) @@ -827,7 +904,6 @@ static int armada_drm_crtc_create(struct drm_device *drm, struct device *dev, dcrtc->variant = variant; dcrtc->base = base; dcrtc->num = drm->mode_config.num_crtc; - dcrtc->clk = ERR_PTR(-EINVAL); dcrtc->cfg_dumb_ctrl = DUMB24_RGB888_0; dcrtc->spu_iopad_ctrl = CFG_VSCALE_LN_EN | CFG_IOPAD_DUMB24; spin_lock_init(&dcrtc->irq_lock); diff --git a/drivers/gpu/drm/armada/armada_crtc.h b/drivers/gpu/drm/armada/armada_crtc.h index 08761ff01739..fb4aa48da60c 100644 --- a/drivers/gpu/drm/armada/armada_crtc.h +++ b/drivers/gpu/drm/armada/armada_crtc.h @@ -39,10 +39,10 @@ struct armada_variant; struct armada_crtc { struct drm_crtc crtc; const struct armada_variant *variant; + void *variant_data; unsigned num; void __iomem *base; struct clk *clk; - struct clk *extclk[2]; struct { uint32_t spu_v_h_total; uint32_t spu_v_porch; @@ -75,6 +75,25 @@ struct armada_crtc { void armada_drm_crtc_update_regs(struct armada_crtc *, struct armada_regs *); +struct armada_clocking_params { + unsigned long permillage_min; + unsigned long permillage_max; + u32 settable; + u32 div_max; +}; + +struct armada_clk_result { + unsigned long desired_clk_hz; + struct clk *clk; + u32 div; +}; + +int armada_crtc_select_clock(struct armada_crtc *dcrtc, + struct armada_clk_result *res, + const struct armada_clocking_params *params, + struct clk *clks[], size_t num_clks, + unsigned long desired_khz); + extern struct platform_driver armada_lcd_platform_driver; #endif From d880fa6627483822bee2ebf170901ad51623b9a2 Mon Sep 17 00:00:00 2001 From: Russell King Date: Sat, 14 Jul 2018 11:17:49 +0100 Subject: [PATCH 14/18] drm/armada: use mode_valid to validate the adjusted mode Validate the adjusted mode in the CRTC mode_fixup() call to ensure that any encoder or bridge doesn't supply us with a mode we can't support. Signed-off-by: Russell King --- drivers/gpu/drm/armada/armada_crtc.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/armada/armada_crtc.c b/drivers/gpu/drm/armada/armada_crtc.c index cffb4c31e9d3..5de7ae7c0d76 100644 --- a/drivers/gpu/drm/armada/armada_crtc.c +++ b/drivers/gpu/drm/armada/armada_crtc.c @@ -171,6 +171,8 @@ static void armada_drm_update_gamma(struct drm_crtc *crtc) static enum drm_mode_status armada_drm_crtc_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode *mode) { + struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc); + if (mode->vscan > 1) return MODE_NO_VSCAN; @@ -180,6 +182,11 @@ static enum drm_mode_status armada_drm_crtc_mode_valid(struct drm_crtc *crtc, if (mode->flags & DRM_MODE_FLAG_HSKEW) return MODE_H_ILLEGAL; + /* We can't do interlaced modes if we don't have the SPU_ADV_REG */ + if (!dcrtc->variant->has_spu_adv_reg && + mode->flags & DRM_MODE_FLAG_INTERLACE) + return MODE_NO_INTERLACE; + if (mode->flags & (DRM_MODE_FLAG_BCAST | DRM_MODE_FLAG_PIXMUX | DRM_MODE_FLAG_CLKDIV2)) return MODE_BAD; @@ -194,11 +201,6 @@ static bool armada_drm_crtc_mode_fixup(struct drm_crtc *crtc, struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc); int ret; - /* We can't do interlaced modes if we don't have the SPU_ADV_REG */ - if (!dcrtc->variant->has_spu_adv_reg && - adj->flags & DRM_MODE_FLAG_INTERLACE) - return false; - /* * Set CRTC modesetting parameters for the adjusted mode. This is * applied after the connectors, bridges, and encoders have fixed up @@ -206,6 +208,13 @@ static bool armada_drm_crtc_mode_fixup(struct drm_crtc *crtc, */ drm_mode_set_crtcinfo(adj, CRTC_INTERLACE_HALVE_V); + /* + * Validate the adjusted mode in case an encoder/bridge has set + * something we don't support. + */ + if (armada_drm_crtc_mode_valid(crtc, adj) != MODE_OK) + return false; + /* Check whether the display mode is possible */ ret = dcrtc->variant->compute_clock(dcrtc, adj, NULL); if (ret) From 06734cb0ab007d0b6810b98458688c3acdd9c962 Mon Sep 17 00:00:00 2001 From: Russell King Date: Sat, 7 Jul 2018 16:53:39 +0100 Subject: [PATCH 15/18] drm/armada: redo CRTC debugfs files Move the CRTC debugfs files into the CRTC specific directory. Signed-off-by: Russell King --- drivers/gpu/drm/armada/armada_crtc.c | 9 +++ drivers/gpu/drm/armada/armada_debugfs.c | 96 ++++++++++--------------- drivers/gpu/drm/armada/armada_drm.h | 1 + 3 files changed, 47 insertions(+), 59 deletions(-) diff --git a/drivers/gpu/drm/armada/armada_crtc.c b/drivers/gpu/drm/armada/armada_crtc.c index 5de7ae7c0d76..2e17d7e72eeb 100644 --- a/drivers/gpu/drm/armada/armada_crtc.c +++ b/drivers/gpu/drm/armada/armada_crtc.c @@ -774,6 +774,14 @@ static void armada_drm_crtc_destroy(struct drm_crtc *crtc) kfree(dcrtc); } +static int armada_drm_crtc_late_register(struct drm_crtc *crtc) +{ + if (IS_ENABLED(CONFIG_DEBUG_FS)) + armada_drm_crtc_debugfs_init(drm_to_armada_crtc(crtc)); + + return 0; +} + /* These are called under the vbl_lock. */ static int armada_drm_crtc_enable_vblank(struct drm_crtc *crtc) { @@ -806,6 +814,7 @@ static const struct drm_crtc_funcs armada_crtc_funcs = { .page_flip = drm_atomic_helper_page_flip, .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, + .late_register = armada_drm_crtc_late_register, .enable_vblank = armada_drm_crtc_enable_vblank, .disable_vblank = armada_drm_crtc_disable_vblank, }; diff --git a/drivers/gpu/drm/armada/armada_debugfs.c b/drivers/gpu/drm/armada/armada_debugfs.c index 6758c3a83de2..4dcce002ea2a 100644 --- a/drivers/gpu/drm/armada/armada_debugfs.c +++ b/drivers/gpu/drm/armada/armada_debugfs.c @@ -28,50 +28,33 @@ static int armada_debugfs_gem_linear_show(struct seq_file *m, void *data) return 0; } -static int armada_debugfs_reg_show(struct seq_file *m, void *data) +static int armada_debugfs_crtc_reg_show(struct seq_file *m, void *data) { - struct drm_device *dev = m->private; - struct armada_private *priv = dev->dev_private; - int n, i; + struct armada_crtc *dcrtc = m->private; + int i; - if (priv) { - for (n = 0; n < ARRAY_SIZE(priv->dcrtc); n++) { - struct armada_crtc *dcrtc = priv->dcrtc[n]; - if (!dcrtc) - continue; - - for (i = 0x84; i <= 0x1c4; i += 4) { - uint32_t v = readl_relaxed(dcrtc->base + i); - seq_printf(m, "%u: 0x%04x: 0x%08x\n", n, i, v); - } - } + for (i = 0x84; i <= 0x1c4; i += 4) { + u32 v = readl_relaxed(dcrtc->base + i); + seq_printf(m, "0x%04x: 0x%08x\n", i, v); } return 0; } -static int armada_debugfs_reg_r_open(struct inode *inode, struct file *file) +static int armada_debugfs_crtc_reg_open(struct inode *inode, struct file *file) { - return single_open(file, armada_debugfs_reg_show, inode->i_private); + return single_open(file, armada_debugfs_crtc_reg_show, + inode->i_private); } -static const struct file_operations fops_reg_r = { - .owner = THIS_MODULE, - .open = armada_debugfs_reg_r_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; - -static int armada_debugfs_write(struct file *file, const char __user *ptr, - size_t len, loff_t *off) +static int armada_debugfs_crtc_reg_write(struct file *file, + const char __user *ptr, size_t len, loff_t *off) { - struct drm_device *dev = file->private_data; - struct armada_private *priv = dev->dev_private; - struct armada_crtc *dcrtc = priv->dcrtc[0]; - char buf[32], *p; - uint32_t reg, val; + struct armada_crtc *dcrtc; + unsigned long reg, mask, val; + char buf[32]; int ret; + u32 v; if (*off != 0) return 0; @@ -84,24 +67,35 @@ static int armada_debugfs_write(struct file *file, const char __user *ptr, return ret; buf[len] = '\0'; - reg = simple_strtoul(buf, &p, 16); - if (!isspace(*p)) + if (sscanf(buf, "%lx %lx %lx", ®, &mask, &val) != 3) return -EINVAL; - val = simple_strtoul(p + 1, NULL, 16); + if (reg < 0x84 || reg > 0x1c4 || reg & 3) + return -ERANGE; - if (reg >= 0x84 && reg <= 0x1c4) - writel(val, dcrtc->base + reg); + dcrtc = ((struct seq_file *)file->private_data)->private; + v = readl(dcrtc->base + reg); + v &= ~mask; + v |= val & mask; + writel(v, dcrtc->base + reg); return len; } -static const struct file_operations fops_reg_w = { +static const struct file_operations armada_debugfs_crtc_reg_fops = { .owner = THIS_MODULE, - .open = simple_open, - .write = armada_debugfs_write, - .llseek = noop_llseek, + .open = armada_debugfs_crtc_reg_open, + .read = seq_read, + .write = armada_debugfs_crtc_reg_write, + .llseek = seq_lseek, + .release = single_release, }; +void armada_drm_crtc_debugfs_init(struct armada_crtc *dcrtc) +{ + debugfs_create_file("armada-regs", 0600, dcrtc->crtc.debugfs_entry, + dcrtc, &armada_debugfs_crtc_reg_fops); +} + static struct drm_info_list armada_debugfs_list[] = { { "gem_linear", armada_debugfs_gem_linear_show, 0 }, }; @@ -109,24 +103,8 @@ static struct drm_info_list armada_debugfs_list[] = { int armada_drm_debugfs_init(struct drm_minor *minor) { - struct dentry *de; - int ret; - - ret = drm_debugfs_create_files(armada_debugfs_list, - ARMADA_DEBUGFS_ENTRIES, - minor->debugfs_root, minor); - if (ret) - return ret; - - de = debugfs_create_file("reg", S_IFREG | S_IRUSR, - minor->debugfs_root, minor->dev, &fops_reg_r); - if (!de) - return -ENOMEM; - - de = debugfs_create_file("reg_wr", S_IFREG | S_IWUSR, - minor->debugfs_root, minor->dev, &fops_reg_w); - if (!de) - return -ENOMEM; + drm_debugfs_create_files(armada_debugfs_list, ARMADA_DEBUGFS_ENTRIES, + minor->debugfs_root, minor); return 0; } diff --git a/drivers/gpu/drm/armada/armada_drm.h b/drivers/gpu/drm/armada/armada_drm.h index f09083ff15d3..b6307235f320 100644 --- a/drivers/gpu/drm/armada/armada_drm.h +++ b/drivers/gpu/drm/armada/armada_drm.h @@ -78,6 +78,7 @@ void armada_fbdev_fini(struct drm_device *); int armada_overlay_plane_create(struct drm_device *, unsigned long); +void armada_drm_crtc_debugfs_init(struct armada_crtc *dcrtc); int armada_drm_debugfs_init(struct drm_minor *); #endif From f54a5990c32978dcb84ef1d38d22457bf3129797 Mon Sep 17 00:00:00 2001 From: Lubomir Rintel Date: Mon, 21 Jan 2019 07:01:57 +0100 Subject: [PATCH 16/18] drm/armada: replace the simple-framebuffer If there's a simple-framebuffer carried over from boot firmware, it's going to stop working once we setup the LCDC for use via DRM. Kick it off from the hardware. Signed-off-by: Lubomir Rintel Signed-off-by: Russell King --- drivers/gpu/drm/armada/armada_drv.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/gpu/drm/armada/armada_drv.c b/drivers/gpu/drm/armada/armada_drv.c index e524bfff1c49..05fab5cb5c2d 100644 --- a/drivers/gpu/drm/armada/armada_drv.c +++ b/drivers/gpu/drm/armada/armada_drv.c @@ -100,6 +100,17 @@ static int armada_drm_bind(struct device *dev) return ret; } + /* Remove early framebuffers */ + ret = drm_fb_helper_remove_conflicting_framebuffers(NULL, + "armada-drm-fb", + false); + if (ret) { + dev_err(dev, "[" DRM_NAME ":%s] can't kick out simple-fb: %d\n", + __func__, ret); + kfree(priv); + return ret; + } + priv->drm.dev_private = priv; dev_set_drvdata(dev, &priv->drm); From 989b9a7dd4ab40eb63026754b10527bf1d03bf86 Mon Sep 17 00:00:00 2001 From: Russell King Date: Thu, 24 Jan 2019 09:56:46 +0000 Subject: [PATCH 17/18] drm/armada: use for_each_endpoint_of_node() to walk crtc endpoints Rather than having a nested set of for_each_child_of_node() walkers, use the graph walker to iterate through the endpoints for CRTCs. Signed-off-by: Russell King --- drivers/gpu/drm/armada/armada_drv.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/armada/armada_drv.c b/drivers/gpu/drm/armada/armada_drv.c index 05fab5cb5c2d..1cfabcd6a629 100644 --- a/drivers/gpu/drm/armada/armada_drv.c +++ b/drivers/gpu/drm/armada/armada_drv.c @@ -204,11 +204,11 @@ static int compare_dev_name(struct device *dev, void *data) } static void armada_add_endpoints(struct device *dev, - struct component_match **match, struct device_node *port) + struct component_match **match, struct device_node *dev_node) { struct device_node *ep, *remote; - for_each_child_of_node(port, ep) { + for_each_endpoint_of_node(dev_node, ep) { remote = of_graph_get_remote_port_parent(ep); if (!remote || !of_device_is_available(remote)) { of_node_put(remote); @@ -242,7 +242,6 @@ static int armada_drm_probe(struct platform_device *pdev) if (dev->platform_data) { char **devices = dev->platform_data; - struct device_node *port; struct device *d; int i; @@ -258,10 +257,8 @@ static int armada_drm_probe(struct platform_device *pdev) for (i = 0; devices[i]; i++) { d = bus_find_device_by_name(&platform_bus_type, NULL, devices[i]); - if (d && d->of_node) { - for_each_child_of_node(d->of_node, port) - armada_add_endpoints(dev, &match, port); - } + if (d && d->of_node) + armada_add_endpoints(dev, &match, d->of_node); put_device(d); } } From 837567c1e9d587c0b438263c9cfd32de46640e16 Mon Sep 17 00:00:00 2001 From: Russell King Date: Thu, 24 Jan 2019 14:18:17 +0000 Subject: [PATCH 18/18] drm/armada: no need to check parent of remote There's no need to check the parent of the remote device to check whether it is available or not, the remote is the device itself. Signed-off-by: Russell King --- drivers/gpu/drm/armada/armada_drv.c | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/armada/armada_drv.c b/drivers/gpu/drm/armada/armada_drv.c index 1cfabcd6a629..f1729398b1bd 100644 --- a/drivers/gpu/drm/armada/armada_drv.c +++ b/drivers/gpu/drm/armada/armada_drv.c @@ -210,17 +210,9 @@ static void armada_add_endpoints(struct device *dev, for_each_endpoint_of_node(dev_node, ep) { remote = of_graph_get_remote_port_parent(ep); - if (!remote || !of_device_is_available(remote)) { - of_node_put(remote); - continue; - } else if (!of_device_is_available(remote->parent)) { - dev_warn(dev, "parent device of %pOF is not available\n", - remote); - of_node_put(remote); - continue; - } - - drm_of_component_match_add(dev, match, compare_of, remote); + if (remote && of_device_is_available(remote)) + drm_of_component_match_add(dev, match, compare_of, + remote); of_node_put(remote); } }