diff options
Diffstat (limited to 'drivers/gpu/drm/rockchip/rockchip_drm_vop.c')
-rw-r--r-- | drivers/gpu/drm/rockchip/rockchip_drm_vop.c | 169 |
1 files changed, 161 insertions, 8 deletions
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c index 613404f86668..d04b3492bdac 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c @@ -139,6 +139,7 @@ struct vop { uint32_t *regsbak; void __iomem *regs; + void __iomem *lut_regs; /* physical map length of vop register */ uint32_t len; @@ -1040,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) { @@ -1075,6 +1180,14 @@ static void vop_crtc_atomic_enable(struct drm_crtc *crtc, 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); @@ -1085,9 +1198,7 @@ static void vop_crtc_atomic_enable(struct drm_crtc *crtc, 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; @@ -1096,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; @@ -1191,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) { @@ -1243,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, @@ -1361,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) @@ -1518,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 @@ -1822,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; |