diff options
Diffstat (limited to 'drivers/gpu/drm/rockchip/rockchip_drm_vop.c')
-rw-r--r-- | drivers/gpu/drm/rockchip/rockchip_drm_vop.c | 283 |
1 files changed, 243 insertions, 40 deletions
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c index 09a790c2f3a1..d04b3492bdac 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c @@ -4,37 +4,38 @@ * Author:Mark Yao <mark.yao@rock-chips.com> */ +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/delay.h> +#include <linux/iopoll.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/overflow.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/reset.h> + #include <drm/drm.h> -#include <drm/drmP.h> #include <drm/drm_atomic.h> #include <drm/drm_atomic_uapi.h> #include <drm/drm_crtc.h> #include <drm/drm_flip_work.h> +#include <drm/drm_fourcc.h> #include <drm/drm_gem_framebuffer_helper.h> #include <drm/drm_plane_helper.h> #include <drm/drm_probe_helper.h> +#include <drm/drm_self_refresh_helper.h> +#include <drm/drm_vblank.h> + #ifdef CONFIG_DRM_ANALOGIX_DP #include <drm/bridge/analogix_dp.h> #endif -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/platform_device.h> -#include <linux/clk.h> -#include <linux/iopoll.h> -#include <linux/of.h> -#include <linux/of_device.h> -#include <linux/pm_runtime.h> -#include <linux/component.h> -#include <linux/overflow.h> - -#include <linux/reset.h> -#include <linux/delay.h> - #include "rockchip_drm_drv.h" #include "rockchip_drm_gem.h" #include "rockchip_drm_fb.h" -#include "rockchip_drm_psr.h" #include "rockchip_drm_vop.h" #include "rockchip_rgb.h" @@ -79,7 +80,7 @@ vop_get_intr_type(vop, &vop->data->intr->name, type) #define VOP_WIN_GET(vop, win, name) \ - vop_read_reg(vop, win->offset, win->phy->name) + vop_read_reg(vop, win->base, &win->phy->name) #define VOP_WIN_HAS_REG(win, name) \ (!!(win->phy->name.mask)) @@ -124,6 +125,7 @@ struct vop { bool is_enabled; struct completion dsp_hold_completion; + unsigned int win_enabled; /* protected by dev->event_lock */ struct drm_pending_vblank_event *event; @@ -137,6 +139,7 @@ struct vop { uint32_t *regsbak; void __iomem *regs; + void __iomem *lut_regs; /* physical map length of vop register */ uint32_t len; @@ -528,8 +531,10 @@ static void vop_core_clks_disable(struct vop *vop) clk_disable(vop->hclk); } -static void vop_win_disable(struct vop *vop, const struct vop_win_data *win) +static void vop_win_disable(struct vop *vop, const struct vop_win *vop_win) { + const struct vop_win_data *win = vop_win->data; + if (win->phy->scl && win->phy->scl->ext) { VOP_SCL_SET_EXT(vop, win, yrgb_hor_scl_mode, SCALE_NONE); VOP_SCL_SET_EXT(vop, win, yrgb_ver_scl_mode, SCALE_NONE); @@ -538,9 +543,10 @@ static void vop_win_disable(struct vop *vop, const struct vop_win_data *win) } VOP_WIN_SET(vop, win, enable, 0); + vop->win_enabled &= ~BIT(VOP_WIN_TO_INDEX(vop_win)); } -static int vop_enable(struct drm_crtc *crtc) +static int vop_enable(struct drm_crtc *crtc, struct drm_crtc_state *old_state) { struct vop *vop = to_vop(crtc); int ret, i; @@ -580,12 +586,17 @@ static int vop_enable(struct drm_crtc *crtc) * We need to make sure that all windows are disabled before we * enable the crtc. Otherwise we might try to scan from a destroyed * buffer later. + * + * In the case of enable-after-PSR, we don't need to worry about this + * case since the buffer is guaranteed to be valid and disabling the + * window will result in screen glitches on PSR exit. */ - for (i = 0; i < vop->data->win_size; i++) { - struct vop_win *vop_win = &vop->win[i]; - const struct vop_win_data *win = vop_win->data; + if (!old_state || !old_state->self_refresh_active) { + for (i = 0; i < vop->data->win_size; i++) { + struct vop_win *vop_win = &vop->win[i]; - vop_win_disable(vop, win); + vop_win_disable(vop, vop_win); + } } spin_unlock(&vop->reg_lock); @@ -615,6 +626,25 @@ err_put_pm_runtime: return ret; } +static void rockchip_drm_set_win_enabled(struct drm_crtc *crtc, bool enabled) +{ + struct vop *vop = to_vop(crtc); + int i; + + spin_lock(&vop->reg_lock); + + for (i = 0; i < vop->data->win_size; i++) { + struct vop_win *vop_win = &vop->win[i]; + const struct vop_win_data *win = vop_win->data; + + VOP_WIN_SET(vop, win, enable, + enabled && (vop->win_enabled & BIT(i))); + } + vop_cfg_done(vop); + + spin_unlock(&vop->reg_lock); +} + static void vop_crtc_atomic_disable(struct drm_crtc *crtc, struct drm_crtc_state *old_state) { @@ -622,9 +652,16 @@ static void vop_crtc_atomic_disable(struct drm_crtc *crtc, WARN_ON(vop->event); + if (crtc->state->self_refresh_active) + rockchip_drm_set_win_enabled(crtc, false); + mutex_lock(&vop->vop_lock); + drm_crtc_vblank_off(crtc); + if (crtc->state->self_refresh_active) + goto out; + /* * Vop standby will take effect at end of current frame, * if dsp hold valid irq happen, it means standby complete. @@ -655,6 +692,8 @@ static void vop_crtc_atomic_disable(struct drm_crtc *crtc, clk_disable(vop->dclk); vop_core_clks_disable(vop); pm_runtime_put(vop->dev); + +out: mutex_unlock(&vop->vop_lock); if (crtc->state->event && !crtc->state->active) { @@ -726,7 +765,6 @@ static void vop_plane_atomic_disable(struct drm_plane *plane, struct drm_plane_state *old_state) { struct vop_win *vop_win = to_vop_win(plane); - const struct vop_win_data *win = vop_win->data; struct vop *vop = to_vop(old_state->crtc); if (!old_state->crtc) @@ -734,7 +772,7 @@ static void vop_plane_atomic_disable(struct drm_plane *plane, spin_lock(&vop->reg_lock); - vop_win_disable(vop, win); + vop_win_disable(vop, vop_win); spin_unlock(&vop->reg_lock); } @@ -873,6 +911,7 @@ static void vop_plane_atomic_update(struct drm_plane *plane, } VOP_WIN_SET(vop, win, enable, 1); + vop->win_enabled |= BIT(win_index); spin_unlock(&vop->reg_lock); } @@ -924,12 +963,10 @@ static void vop_plane_atomic_async_update(struct drm_plane *plane, swap(plane->state->fb, new_state->fb); if (vop->is_enabled) { - rockchip_drm_psr_inhibit_get_state(new_state->state); vop_plane_atomic_update(plane, plane->state); spin_lock(&vop->reg_lock); vop_cfg_done(vop); spin_unlock(&vop->reg_lock); - rockchip_drm_psr_inhibit_put_state(new_state->state); /* * A scanout can still be occurring, so we can't drop the @@ -1004,14 +1041,118 @@ static bool vop_crtc_mode_fixup(struct drm_crtc *crtc, struct drm_display_mode *adjusted_mode) { struct vop *vop = to_vop(crtc); + unsigned long rate; - adjusted_mode->clock = - DIV_ROUND_UP(clk_round_rate(vop->dclk, - adjusted_mode->clock * 1000), 1000); + /* + * Clock craziness. + * + * Key points: + * + * - DRM works in in kHz. + * - Clock framework works in Hz. + * - Rockchip's clock driver picks the clock rate that is the + * same _OR LOWER_ than the one requested. + * + * Action plan: + * + * 1. When DRM gives us a mode, we should add 999 Hz to it. That way + * if the clock we need is 60000001 Hz (~60 MHz) and DRM tells us to + * make 60000 kHz then the clock framework will actually give us + * the right clock. + * + * NOTE: if the PLL (maybe through a divider) could actually make + * a clock rate 999 Hz higher instead of the one we want then this + * could be a problem. Unfortunately there's not much we can do + * since it's baked into DRM to use kHz. It shouldn't matter in + * practice since Rockchip PLLs are controlled by tables and + * even if there is a divider in the middle I wouldn't expect PLL + * rates in the table that are just a few kHz different. + * + * 2. Get the clock framework to round the rate for us to tell us + * what it will actually make. + * + * 3. Store the rounded up rate so that we don't need to worry about + * this in the actual clk_set_rate(). + */ + rate = clk_round_rate(vop->dclk, adjusted_mode->clock * 1000 + 999); + adjusted_mode->clock = DIV_ROUND_UP(rate, 1000); return true; } +static bool vop_dsp_lut_is_enabled(struct vop *vop) +{ + return vop_read_reg(vop, 0, &vop->data->common->dsp_lut_en); +} + +static void vop_crtc_write_gamma_lut(struct vop *vop, struct drm_crtc *crtc) +{ + struct drm_color_lut *lut = crtc->state->gamma_lut->data; + unsigned int i; + + for (i = 0; i < crtc->gamma_size; i++) { + u32 word; + + word = (drm_color_lut_extract(lut[i].red, 10) << 20) | + (drm_color_lut_extract(lut[i].green, 10) << 10) | + drm_color_lut_extract(lut[i].blue, 10); + writel(word, vop->lut_regs + i * 4); + } +} + +static void vop_crtc_gamma_set(struct vop *vop, struct drm_crtc *crtc, + struct drm_crtc_state *old_state) +{ + struct drm_crtc_state *state = crtc->state; + unsigned int idle; + int ret; + + if (!vop->lut_regs) + return; + /* + * To disable gamma (gamma_lut is null) or to write + * an update to the LUT, clear dsp_lut_en. + */ + spin_lock(&vop->reg_lock); + VOP_REG_SET(vop, common, dsp_lut_en, 0); + vop_cfg_done(vop); + spin_unlock(&vop->reg_lock); + + /* + * In order to write the LUT to the internal memory, + * we need to first make sure the dsp_lut_en bit is cleared. + */ + ret = readx_poll_timeout(vop_dsp_lut_is_enabled, vop, + idle, !idle, 5, 30 * 1000); + if (ret) { + DRM_DEV_ERROR(vop->dev, "display LUT RAM enable timeout!\n"); + return; + } + + if (!state->gamma_lut) + return; + + spin_lock(&vop->reg_lock); + vop_crtc_write_gamma_lut(vop, crtc); + VOP_REG_SET(vop, common, dsp_lut_en, 1); + vop_cfg_done(vop); + spin_unlock(&vop->reg_lock); +} + +static void vop_crtc_atomic_begin(struct drm_crtc *crtc, + struct drm_crtc_state *old_crtc_state) +{ + struct vop *vop = to_vop(crtc); + + /* + * Only update GAMMA if the 'active' flag is not changed, + * otherwise it's updated by .atomic_enable. + */ + if (crtc->state->color_mgmt_changed && + !crtc->state->active_changed) + vop_crtc_gamma_set(vop, crtc, old_crtc_state); +} + static void vop_crtc_atomic_enable(struct drm_crtc *crtc, struct drm_crtc_state *old_state) { @@ -1033,19 +1174,31 @@ static void vop_crtc_atomic_enable(struct drm_crtc *crtc, int dither_bpc = s->output_bpc ? s->output_bpc : 10; int ret; + if (old_state && old_state->self_refresh_active) { + drm_crtc_vblank_on(crtc); + rockchip_drm_set_win_enabled(crtc, true); + return; + } + + /* + * If we have a GAMMA LUT in the state, then let's make sure + * it's updated. We might be coming out of suspend, + * which means the LUT internal memory needs to be re-written. + */ + if (crtc->state->gamma_lut) + vop_crtc_gamma_set(vop, crtc, old_state); + mutex_lock(&vop->vop_lock); WARN_ON(vop->event); - ret = vop_enable(crtc); + ret = vop_enable(crtc, old_state); if (ret) { mutex_unlock(&vop->vop_lock); DRM_DEV_ERROR(vop->dev, "Failed to enable vop (%d)\n", ret); return; } - - pin_pol = BIT(DCLK_INVERT); - pin_pol |= (adjusted_mode->flags & DRM_MODE_FLAG_PHSYNC) ? + pin_pol = (adjusted_mode->flags & DRM_MODE_FLAG_PHSYNC) ? BIT(HSYNC_POSITIVE) : 0; pin_pol |= (adjusted_mode->flags & DRM_MODE_FLAG_PVSYNC) ? BIT(VSYNC_POSITIVE) : 0; @@ -1054,25 +1207,29 @@ static void vop_crtc_atomic_enable(struct drm_crtc *crtc, switch (s->output_type) { case DRM_MODE_CONNECTOR_LVDS: - VOP_REG_SET(vop, output, rgb_en, 1); + VOP_REG_SET(vop, output, rgb_dclk_pol, 1); VOP_REG_SET(vop, output, rgb_pin_pol, pin_pol); + VOP_REG_SET(vop, output, rgb_en, 1); break; case DRM_MODE_CONNECTOR_eDP: + VOP_REG_SET(vop, output, edp_dclk_pol, 1); VOP_REG_SET(vop, output, edp_pin_pol, pin_pol); VOP_REG_SET(vop, output, edp_en, 1); break; case DRM_MODE_CONNECTOR_HDMIA: + VOP_REG_SET(vop, output, hdmi_dclk_pol, 1); VOP_REG_SET(vop, output, hdmi_pin_pol, pin_pol); VOP_REG_SET(vop, output, hdmi_en, 1); break; case DRM_MODE_CONNECTOR_DSI: + VOP_REG_SET(vop, output, mipi_dclk_pol, 1); VOP_REG_SET(vop, output, mipi_pin_pol, pin_pol); VOP_REG_SET(vop, output, mipi_en, 1); VOP_REG_SET(vop, output, mipi_dual_channel_en, !!(s->output_flags & ROCKCHIP_OUTPUT_DSI_DUAL)); break; case DRM_MODE_CONNECTOR_DisplayPort: - pin_pol &= ~BIT(DCLK_INVERT); + VOP_REG_SET(vop, output, dp_dclk_pol, 0); VOP_REG_SET(vop, output, dp_pin_pol, pin_pol); VOP_REG_SET(vop, output, dp_en, 1); break; @@ -1149,6 +1306,26 @@ static void vop_wait_for_irq_handler(struct vop *vop) synchronize_irq(vop->irq); } +static int vop_crtc_atomic_check(struct drm_crtc *crtc, + struct drm_crtc_state *crtc_state) +{ + struct vop *vop = to_vop(crtc); + + if (vop->lut_regs && crtc_state->color_mgmt_changed && + crtc_state->gamma_lut) { + unsigned int len; + + len = drm_color_lut_size(crtc_state->gamma_lut); + if (len != crtc->gamma_size) { + DRM_DEBUG_KMS("Invalid LUT size; got %d, expected %d\n", + len, crtc->gamma_size); + return -EINVAL; + } + } + + return 0; +} + static void vop_crtc_atomic_flush(struct drm_crtc *crtc, struct drm_crtc_state *old_crtc_state) { @@ -1201,6 +1378,8 @@ static void vop_crtc_atomic_flush(struct drm_crtc *crtc, static const struct drm_crtc_helper_funcs vop_crtc_helper_funcs = { .mode_fixup = vop_crtc_mode_fixup, + .atomic_check = vop_crtc_atomic_check, + .atomic_begin = vop_crtc_atomic_begin, .atomic_flush = vop_crtc_atomic_flush, .atomic_enable = vop_crtc_atomic_enable, .atomic_disable = vop_crtc_atomic_disable, @@ -1319,6 +1498,7 @@ static const struct drm_crtc_funcs vop_crtc_funcs = { .disable_vblank = vop_crtc_disable_vblank, .set_crc_source = vop_crtc_set_crc_source, .verify_crc_source = vop_crtc_verify_crc_source, + .gamma_set = drm_atomic_helper_legacy_gamma_set, }; static void vop_fb_unref_worker(struct drm_flip_work *work, void *val) @@ -1476,6 +1656,10 @@ static int vop_create_crtc(struct vop *vop) goto err_cleanup_planes; drm_crtc_helper_add(crtc, &vop_crtc_helper_funcs); + if (vop->lut_regs) { + drm_mode_crtc_set_gamma_size(crtc, vop_data->lut_size); + drm_crtc_enable_color_mgmt(crtc, 0, false, vop_data->lut_size); + } /* * Create drm_planes for overlay windows with possible_crtcs restricted @@ -1519,6 +1703,12 @@ static int vop_create_crtc(struct vop *vop) init_completion(&vop->line_flag_completion); crtc->port = port; + ret = drm_self_refresh_helper_init(crtc); + if (ret) + DRM_DEV_DEBUG_KMS(vop->dev, + "Failed to init %s with SR helpers %d, ignoring\n", + crtc->name, ret); + return 0; err_cleanup_crtc: @@ -1536,6 +1726,8 @@ static void vop_destroy_crtc(struct vop *vop) struct drm_device *drm_dev = vop->drm_dev; struct drm_plane *plane, *tmp; + drm_self_refresh_helper_cleanup(crtc); + of_node_put(crtc->port); /* @@ -1560,7 +1752,6 @@ static void vop_destroy_crtc(struct vop *vop) static int vop_initial(struct vop *vop) { - const struct vop_data *vop_data = vop->data; struct reset_control *ahb_rst; int i, ret; @@ -1627,12 +1818,13 @@ static int vop_initial(struct vop *vop) VOP_REG_SET(vop, misc, global_regdone_en, 1); VOP_REG_SET(vop, common, dsp_blank, 0); - for (i = 0; i < vop_data->win_size; i++) { - const struct vop_win_data *win = &vop_data->win[i]; + for (i = 0; i < vop->data->win_size; i++) { + struct vop_win *vop_win = &vop->win[i]; + const struct vop_win_data *win = vop_win->data; int channel = i * 2 + 1; VOP_WIN_SET(vop, win, channel, (channel + 1) << 4 | channel); - vop_win_disable(vop, win); + vop_win_disable(vop, vop_win); VOP_WIN_SET(vop, win, gate, 1); } @@ -1772,6 +1964,17 @@ static int vop_bind(struct device *dev, struct device *master, void *data) if (IS_ERR(vop->regs)) return PTR_ERR(vop->regs); + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (res) { + if (!vop_data->lut_size) { + DRM_DEV_ERROR(dev, "no gamma LUT size defined\n"); + return -EINVAL; + } + vop->lut_regs = devm_ioremap_resource(dev, res); + if (IS_ERR(vop->lut_regs)) + return PTR_ERR(vop->lut_regs); + } + vop->regsbak = devm_kzalloc(dev, vop->len, GFP_KERNEL); if (!vop->regsbak) return -ENOMEM; |