X-Git-Url: http://demsky.eecs.uci.edu/git/?a=blobdiff_plain;f=drivers%2Fgpu%2Fdrm%2Frockchip%2Frockchip_drm_vop.c;h=7608a02d72f0255456476713f613c947b5840219;hb=c2e4d4e9ca60b2a67cd5a96e5d198d9d6827df28;hp=1e1832248ef4c12d490d920de2017795628bf3ee;hpb=7fd55b9ff442c5fafe1796bf960ccfec353d642e;p=firefly-linux-kernel-4.4.55.git diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c index 1e1832248ef4..7608a02d72f0 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c @@ -17,13 +17,16 @@ #include #include #include +#include #include +#include #include #include #include #include #include +#include #include #include #include @@ -38,6 +41,7 @@ #include "rockchip_drm_gem.h" #include "rockchip_drm_fb.h" #include "rockchip_drm_vop.h" +#include "rockchip_drm_backlight.h" #define VOP_REG_SUPPORT(vop, reg) \ (!reg.major || (reg.major == VOP_MAJOR(vop->data->version) && \ @@ -128,10 +132,15 @@ struct vop_zpos { int zpos; }; +enum vop_pending { + VOP_PENDING_FB_UNREF, +}; + struct vop_plane_state { struct drm_plane_state base; int format; int zpos; + unsigned int logo_ymirror; struct drm_rect src; struct drm_rect dest; dma_addr_t yrgb_mst; @@ -176,9 +185,13 @@ struct vop { bool vsync_work_pending; bool loader_protect; struct completion dsp_hold_completion; - struct completion wait_update_complete; + + /* protected by dev->event_lock */ struct drm_pending_vblank_event *event; + struct drm_flip_work fb_unref_work; + unsigned long pending; + struct completion line_flag_completion; const struct vop_data *data; @@ -190,10 +203,19 @@ struct vop { /* physical map length of vop register */ uint32_t len; + void __iomem *lut_regs; + u32 *lut; + u32 lut_len; + bool lut_active; + void __iomem *cabc_lut_regs; + u32 cabc_lut_len; + /* one time only one process allowed to config the register */ spinlock_t reg_lock; /* lock vop irq reg */ spinlock_t irq_lock; + /* mutex vop enable and disable */ + struct mutex vop_lock; unsigned int irq; @@ -203,13 +225,20 @@ struct vop { struct clk *dclk; /* vop share memory frequency */ struct clk *aclk; + /* vop source handling, optional */ + struct clk *dclk_source; /* vop dclk reset */ struct reset_control *dclk_rst; + struct devfreq *devfreq; + struct notifier_block dmc_nb; + struct vop_win win[]; }; +struct vop *dmc_vop; + static inline void vop_writel(struct vop *vop, uint32_t offset, uint32_t v) { writel(v, vop->regs + offset); @@ -317,6 +346,21 @@ static bool vop_line_flag_is_active(struct vop *vop) return VOP_INTR_GET_TYPE(vop, status, LINE_FLAG_INTR); } +static inline void vop_write_lut(struct vop *vop, uint32_t offset, uint32_t v) +{ + writel(v, vop->lut_regs + offset); +} + +static inline uint32_t vop_read_lut(struct vop *vop, uint32_t offset) +{ + return readl(vop->lut_regs + offset); +} + +static inline void vop_write_cabc_lut(struct vop *vop, uint32_t offset, uint32_t v) +{ + writel(v, vop->cabc_lut_regs + offset); +} + static bool has_rb_swapped(uint32_t format) { switch (format) { @@ -364,6 +408,8 @@ static bool is_yuv_output(uint32_t bus_format) switch (bus_format) { case MEDIA_BUS_FMT_YUV8_1X24: case MEDIA_BUS_FMT_YUV10_1X30: + case MEDIA_BUS_FMT_UYYVYY8_0_5X24: + case MEDIA_BUS_FMT_UYYVYY10_0_5X30: return true; default: return false; @@ -767,6 +813,88 @@ static void vop_line_flag_irq_disable(struct vop *vop) spin_unlock_irqrestore(&vop->irq_lock, flags); } +static void vop_crtc_load_lut(struct drm_crtc *crtc) +{ + struct vop *vop = to_vop(crtc); + int i, dle, lut_idx; + + if (!vop->is_enabled || !vop->lut || !vop->lut_regs) + return; + + if (WARN_ON(!drm_modeset_is_locked(&crtc->mutex))) + return; + + if (!VOP_CTRL_SUPPORT(vop, update_gamma_lut)) { + spin_lock(&vop->reg_lock); + VOP_CTRL_SET(vop, dsp_lut_en, 0); + vop_cfg_done(vop); + spin_unlock(&vop->reg_lock); + +#define CTRL_GET(name) VOP_CTRL_GET(vop, name) + readx_poll_timeout(CTRL_GET, dsp_lut_en, + dle, !dle, 5, 33333); + } else { + lut_idx = CTRL_GET(lut_buffer_index); + } + + for (i = 0; i < vop->lut_len; i++) + vop_write_lut(vop, i << 2, vop->lut[i]); + + spin_lock(&vop->reg_lock); + + VOP_CTRL_SET(vop, dsp_lut_en, 1); + VOP_CTRL_SET(vop, update_gamma_lut, 1); + vop_cfg_done(vop); + vop->lut_active = true; + + spin_unlock(&vop->reg_lock); + + if (VOP_CTRL_SUPPORT(vop, update_gamma_lut)) { + readx_poll_timeout(CTRL_GET, lut_buffer_index, + dle, dle != lut_idx, 5, 33333); + /* FIXME: + * update_gamma value auto clean to 0 by HW, should not + * bakeup it. + */ + VOP_CTRL_SET(vop, update_gamma_lut, 0); + } +#undef CTRL_GET +} + +void rockchip_vop_crtc_fb_gamma_set(struct drm_crtc *crtc, u16 red, u16 green, + u16 blue, int regno) +{ + struct vop *vop = to_vop(crtc); + u32 lut_len = vop->lut_len; + u32 r, g, b; + + if (regno >= lut_len || !vop->lut) + return; + + r = red * (lut_len - 1) / 0xffff; + g = green * (lut_len - 1) / 0xffff; + b = blue * (lut_len - 1) / 0xffff; + vop->lut[regno] = r * lut_len * lut_len + g * lut_len + b; +} + +void rockchip_vop_crtc_fb_gamma_get(struct drm_crtc *crtc, u16 *red, u16 *green, + u16 *blue, int regno) +{ + struct vop *vop = to_vop(crtc); + u32 lut_len = vop->lut_len; + u32 r, g, b; + + if (regno >= lut_len || !vop->lut) + return; + + r = (vop->lut[regno] / lut_len / lut_len) & (lut_len - 1); + g = (vop->lut[regno] / lut_len) & (lut_len - 1); + b = vop->lut[regno] & (lut_len - 1); + *red = r * 0xffff / (lut_len - 1); + *green = g * 0xffff / (lut_len - 1); + *blue = b * 0xffff / (lut_len - 1); +} + static void vop_power_enable(struct drm_crtc *crtc) { struct vop *vop = to_vop(crtc); @@ -811,6 +939,7 @@ err_disable_hclk: static void vop_initial(struct drm_crtc *crtc) { struct vop *vop = to_vop(crtc); + uint32_t irqs; int i; vop_power_enable(crtc); @@ -818,6 +947,12 @@ static void vop_initial(struct drm_crtc *crtc) VOP_CTRL_SET(vop, global_regdone_en, 1); VOP_CTRL_SET(vop, dsp_blank, 0); + /* + * restore the lut table. + */ + if (vop->lut_active) + vop_crtc_load_lut(crtc); + /* * We need to make sure that all windows are disabled before resume * the crtc. Otherwise we might try to scan from a destroyed @@ -825,7 +960,9 @@ static void vop_initial(struct drm_crtc *crtc) */ for (i = 0; i < vop->num_wins; i++) { struct vop_win *win = &vop->win[i]; + int channel = i * 2 + 1; + VOP_WIN_SET(vop, win, channel, (channel + 1) << 4 | channel); 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); @@ -836,12 +973,19 @@ static void vop_initial(struct drm_crtc *crtc) VOP_WIN_SET(vop, win, gate, 1); } VOP_CTRL_SET(vop, afbdc_en, 0); + + irqs = BUS_ERROR_INTR | WIN0_EMPTY_INTR | WIN1_EMPTY_INTR | + WIN2_EMPTY_INTR | WIN3_EMPTY_INTR | HWC_EMPTY_INTR | + POST_BUF_EMPTY_INTR; + VOP_INTR_SET_TYPE(vop, clear, irqs, 1); + VOP_INTR_SET_TYPE(vop, enable, irqs, 1); } static void vop_crtc_disable(struct drm_crtc *crtc) { struct vop *vop = to_vop(crtc); + mutex_lock(&vop->vop_lock); drm_crtc_vblank_off(crtc); /* @@ -880,6 +1024,7 @@ static void vop_crtc_disable(struct drm_crtc *crtc) clk_disable_unprepare(vop->dclk); clk_disable_unprepare(vop->aclk); clk_disable_unprepare(vop->hclk); + mutex_unlock(&vop->vop_lock); } static void vop_plane_destroy(struct drm_plane *plane) @@ -924,6 +1069,7 @@ static int vop_plane_atomic_check(struct drm_plane *plane, DRM_PLANE_HELPER_NO_SCALING; unsigned long offset; dma_addr_t dma_addr; + u16 vdisplay; crtc = crtc ? crtc : plane->state->crtc; /* @@ -945,10 +1091,14 @@ static int vop_plane_atomic_check(struct drm_plane *plane, dest->x2 = state->crtc_x + state->crtc_w; dest->y2 = state->crtc_y + state->crtc_h; + vdisplay = crtc_state->adjusted_mode.crtc_vdisplay; + if (crtc_state->adjusted_mode.flags & DRM_MODE_FLAG_INTERLACE) + vdisplay *= 2; + clip.x1 = 0; clip.y1 = 0; - clip.x2 = crtc_state->mode.hdisplay; - clip.y2 = crtc_state->mode.vdisplay; + clip.x2 = crtc_state->adjusted_mode.crtc_hdisplay; + clip.y2 = vdisplay; ret = drm_plane_helper_check_update(plane, crtc, state->fb, src, dest, &clip, @@ -982,11 +1132,14 @@ static int vop_plane_atomic_check(struct drm_plane *plane, * Src.x1 can be odd when do clip, but yuv plane start point * need align with 2 pixel. */ - if (is_yuv_support(fb->pixel_format) && ((src->x1 >> 16) % 2)) + if (is_yuv_support(fb->pixel_format) && ((src->x1 >> 16) % 2)) { + DRM_ERROR("Invalid Source: Yuv format Can't support odd xpos\n"); return -EINVAL; + } offset = (src->x1 >> 16) * drm_format_plane_bpp(fb->pixel_format, 0) / 8; - if (state->rotation & BIT(DRM_REFLECT_Y)) + if (state->rotation & BIT(DRM_REFLECT_Y) || + (rockchip_fb_is_logo(fb) && vop_plane_state->logo_ymirror)) offset += ((src->y2 >> 16) - 1) * fb->pitches[0]; else offset += (src->y1 >> 16) * fb->pitches[0]; @@ -1088,7 +1241,8 @@ static void vop_plane_atomic_update(struct drm_plane *plane, dsp_sty = dest->y1 + crtc->mode.vtotal - crtc->mode.vsync_start; dsp_st = dsp_sty << 16 | (dsp_stx & 0xffff); - ymirror = !!(state->rotation & BIT(DRM_REFLECT_Y)); + ymirror = state->rotation & BIT(DRM_REFLECT_Y) || + (rockchip_fb_is_logo(fb) && vop_plane_state->logo_ymirror); xmirror = !!(state->rotation & BIT(DRM_REFLECT_X)); vop = to_vop(state->crtc); @@ -1211,6 +1365,7 @@ static int vop_atomic_plane_set_property(struct drm_plane *plane, struct drm_property *property, uint64_t val) { + struct rockchip_drm_private *private = plane->dev->dev_private; struct vop_win *win = to_vop_win(plane); struct vop_plane_state *plane_state = to_vop_plane_state(state); @@ -1224,6 +1379,12 @@ static int vop_atomic_plane_set_property(struct drm_plane *plane, return 0; } + if (property == private->logo_ymirror_prop) { + WARN_ON(!rockchip_fb_is_logo(state->fb)); + plane_state->logo_ymirror = val; + return 0; + } + DRM_ERROR("failed to set vop plane property\n"); return -EINVAL; } @@ -1255,6 +1416,7 @@ static const struct drm_plane_funcs vop_plane_funcs = { .disable_plane = drm_atomic_helper_disable_plane, .destroy = vop_plane_destroy, .reset = vop_atomic_plane_reset, + .set_property = drm_atomic_helper_plane_set_property, .atomic_duplicate_state = vop_atomic_plane_duplicate_state, .atomic_destroy_state = vop_atomic_plane_destroy_state, .atomic_set_property = vop_atomic_plane_set_property, @@ -1294,14 +1456,6 @@ static void vop_crtc_disable_vblank(struct drm_crtc *crtc) spin_unlock_irqrestore(&vop->irq_lock, flags); } -static void vop_crtc_wait_for_update(struct drm_crtc *crtc) -{ - struct vop *vop = to_vop(crtc); - - reinit_completion(&vop->wait_update_complete); - WARN_ON(!wait_for_completion_timeout(&vop->wait_update_complete, 100)); -} - static void vop_crtc_cancel_pending_vblank(struct drm_crtc *crtc, struct drm_file *file_priv) { @@ -1342,6 +1496,14 @@ static int vop_crtc_loader_protect(struct drm_crtc *crtc, bool on) return 0; } +#define DEBUG_PRINT(args...) \ + do { \ + if (s) \ + seq_printf(s, args); \ + else \ + printk(args); \ + } while (0) + static int vop_plane_info_dump(struct seq_file *s, struct drm_plane *plane) { struct vop_win *win = to_vop_win(plane); @@ -1351,27 +1513,27 @@ static int vop_plane_info_dump(struct seq_file *s, struct drm_plane *plane) struct drm_framebuffer *fb = state->fb; int i; - seq_printf(s, " win%d-%d: %s\n", win->win_id, win->area_id, - pstate->enable ? "ACTIVE" : "DISABLED"); + DEBUG_PRINT(" win%d-%d: %s\n", win->win_id, win->area_id, + pstate->enable ? "ACTIVE" : "DISABLED"); if (!fb) return 0; src = &pstate->src; dest = &pstate->dest; - seq_printf(s, "\tformat: %s%s\n", drm_get_format_name(fb->pixel_format), - fb->modifier[0] == DRM_FORMAT_MOD_ARM_AFBC ? "[AFBC]" : ""); - seq_printf(s, "\tzpos: %d\n", pstate->zpos); - seq_printf(s, "\tsrc: pos[%dx%d] rect[%dx%d]\n", src->x1 >> 16, - src->y1 >> 16, drm_rect_width(src) >> 16, - drm_rect_height(src) >> 16); - seq_printf(s, "\tdst: pos[%dx%d] rect[%dx%d]\n", dest->x1, dest->y1, - drm_rect_width(dest), drm_rect_height(dest)); + DEBUG_PRINT("\tformat: %s%s\n", drm_get_format_name(fb->pixel_format), + fb->modifier[0] == DRM_FORMAT_MOD_ARM_AFBC ? "[AFBC]" : ""); + DEBUG_PRINT("\tzpos: %d\n", pstate->zpos); + DEBUG_PRINT("\tsrc: pos[%dx%d] rect[%dx%d]\n", src->x1 >> 16, + src->y1 >> 16, drm_rect_width(src) >> 16, + drm_rect_height(src) >> 16); + DEBUG_PRINT("\tdst: pos[%dx%d] rect[%dx%d]\n", dest->x1, dest->y1, + drm_rect_width(dest), drm_rect_height(dest)); for (i = 0; i < drm_format_num_planes(fb->pixel_format); i++) { dma_addr_t fb_addr = rockchip_fb_get_dma_addr(fb, i); - seq_printf(s, "\tbuf[%d]: addr: %pad pitch: %d offset: %d\n", - i, &fb_addr, fb->pitches[i], fb->offsets[i]); + DEBUG_PRINT("\tbuf[%d]: addr: %pad pitch: %d offset: %d\n", + i, &fb_addr, fb->pitches[i], fb->offsets[i]); } return 0; @@ -1387,25 +1549,25 @@ static int vop_crtc_debugfs_dump(struct drm_crtc *crtc, struct seq_file *s) struct drm_plane *plane; int i; - seq_printf(s, "VOP [%s]: %s\n", dev_name(vop->dev), - crtc_state->active ? "ACTIVE" : "DISABLED"); + DEBUG_PRINT("VOP [%s]: %s\n", dev_name(vop->dev), + crtc_state->active ? "ACTIVE" : "DISABLED"); if (!crtc_state->active) return 0; - seq_printf(s, " Connector: %s\n", - drm_get_connector_name(state->output_type)); - seq_printf(s, "\tbus_format[%x] output_mode[%x]\n", - state->bus_format, state->output_mode); - seq_printf(s, " Display mode: %dx%d%s%d\n", - mode->hdisplay, mode->vdisplay, interlaced ? "i" : "p", - drm_mode_vrefresh(mode)); - seq_printf(s, "\tclk[%d] real_clk[%d] type[%x] flag[%x]\n", - mode->clock, mode->crtc_clock, mode->type, mode->flags); - seq_printf(s, "\tH: %d %d %d %d\n", mode->hdisplay, mode->hsync_start, - mode->hsync_end, mode->htotal); - seq_printf(s, "\tV: %d %d %d %d\n", mode->vdisplay, mode->vsync_start, - mode->vsync_end, mode->vtotal); + DEBUG_PRINT(" Connector: %s\n", + drm_get_connector_name(state->output_type)); + DEBUG_PRINT("\tbus_format[%x] output_mode[%x]\n", + state->bus_format, state->output_mode); + DEBUG_PRINT(" Display mode: %dx%d%s%d\n", + mode->hdisplay, mode->vdisplay, interlaced ? "i" : "p", + drm_mode_vrefresh(mode)); + DEBUG_PRINT("\tclk[%d] real_clk[%d] type[%x] flag[%x]\n", + mode->clock, mode->crtc_clock, mode->type, mode->flags); + DEBUG_PRINT("\tH: %d %d %d %d\n", mode->hdisplay, mode->hsync_start, + mode->hsync_end, mode->htotal); + DEBUG_PRINT("\tV: %d %d %d %d\n", mode->vdisplay, mode->vsync_start, + mode->vsync_end, mode->vtotal); for (i = 0; i < vop->num_wins; i++) { plane = &vop->win[i].base; @@ -1415,6 +1577,25 @@ static int vop_crtc_debugfs_dump(struct drm_crtc *crtc, struct seq_file *s) return 0; } +static void vop_crtc_regs_dump(struct drm_crtc *crtc, struct seq_file *s) +{ + struct vop *vop = to_vop(crtc); + struct drm_crtc_state *crtc_state = crtc->state; + int dump_len = vop->len > 0x400 ? 0x400 : vop->len; + int i; + + if (!crtc_state->active) + return; + + for (i = 0; i < dump_len; i += 4) { + if (i % 16 == 0) + DEBUG_PRINT("\n0x%08x: ", i); + DEBUG_PRINT("%08x ", vop_readl(vop, i)); + } +} + +#undef DEBUG_PRINT + static enum drm_mode_status vop_crtc_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode *mode, int output_type) @@ -1426,8 +1607,11 @@ vop_crtc_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode *mode, if (mode->hdisplay > vop_data->max_output.width) return MODE_BAD_HVALUE; - if (mode->vdisplay > vop_data->max_output.height) - return MODE_BAD_VVALUE; + + if ((mode->flags & DRM_MODE_FLAG_INTERLACE) && + VOP_MAJOR(vop->data->version) == 3 && + VOP_MINOR(vop->data->version) <= 2) + return MODE_BAD; if (mode->flags & DRM_MODE_FLAG_DBLCLK) request_clock *= 2; @@ -1448,28 +1632,30 @@ static const struct rockchip_crtc_funcs private_crtc_funcs = { .loader_protect = vop_crtc_loader_protect, .enable_vblank = vop_crtc_enable_vblank, .disable_vblank = vop_crtc_disable_vblank, - .wait_for_update = vop_crtc_wait_for_update, .cancel_pending_vblank = vop_crtc_cancel_pending_vblank, .debugfs_dump = vop_crtc_debugfs_dump, + .regs_dump = vop_crtc_regs_dump, .mode_valid = vop_crtc_mode_valid, }; static bool vop_crtc_mode_fixup(struct drm_crtc *crtc, const struct drm_display_mode *mode, - struct drm_display_mode *adjusted_mode) + struct drm_display_mode *adj_mode) { struct vop *vop = to_vop(crtc); const struct vop_data *vop_data = vop->data; - int request_clock = mode->clock; - if (mode->hdisplay > vop_data->max_output.width || - mode->vdisplay > vop_data->max_output.height) + if (mode->hdisplay > vop_data->max_output.width) return false; + drm_mode_set_crtcinfo(adj_mode, + CRTC_INTERLACE_HALVE_V | CRTC_STEREO_DOUBLE); + if (mode->flags & DRM_MODE_FLAG_DBLCLK) - request_clock *= 2; - adjusted_mode->crtc_clock = - clk_round_rate(vop->dclk, request_clock * 1000) / 1000; + adj_mode->crtc_clock *= 2; + + adj_mode->crtc_clock = + clk_round_rate(vop->dclk, adj_mode->crtc_clock * 1000) / 1000; return true; } @@ -1492,36 +1678,65 @@ static void vop_crtc_enable(struct drm_crtc *crtc) u16 vact_end = vact_st + vdisplay; uint32_t val; + mutex_lock(&vop->vop_lock); vop_initial(crtc); - val = BIT(DCLK_INVERT); - val |= (adjusted_mode->flags & DRM_MODE_FLAG_NHSYNC) ? + VOP_CTRL_SET(vop, dclk_pol, 1); + val = (adjusted_mode->flags & DRM_MODE_FLAG_NHSYNC) ? 0 : BIT(HSYNC_POSITIVE); val |= (adjusted_mode->flags & DRM_MODE_FLAG_NVSYNC) ? 0 : BIT(VSYNC_POSITIVE); VOP_CTRL_SET(vop, pin_pol, val); + + if (vop->dclk_source && s->pll && s->pll->pll) { + if (clk_set_parent(vop->dclk_source, s->pll->pll)) + DRM_DEV_ERROR(vop->dev, + "failed to set dclk's parents\n"); + } + switch (s->output_type) { case DRM_MODE_CONNECTOR_LVDS: VOP_CTRL_SET(vop, rgb_en, 1); VOP_CTRL_SET(vop, rgb_pin_pol, val); + VOP_CTRL_SET(vop, rgb_dclk_pol, 1); + VOP_CTRL_SET(vop, lvds_en, 1); + VOP_CTRL_SET(vop, lvds_pin_pol, val); + VOP_CTRL_SET(vop, lvds_dclk_pol, 1); break; case DRM_MODE_CONNECTOR_eDP: VOP_CTRL_SET(vop, edp_en, 1); VOP_CTRL_SET(vop, edp_pin_pol, val); + VOP_CTRL_SET(vop, edp_dclk_pol, 1); break; case DRM_MODE_CONNECTOR_HDMIA: VOP_CTRL_SET(vop, hdmi_en, 1); VOP_CTRL_SET(vop, hdmi_pin_pol, val); + VOP_CTRL_SET(vop, hdmi_dclk_pol, 1); break; case DRM_MODE_CONNECTOR_DSI: VOP_CTRL_SET(vop, mipi_en, 1); VOP_CTRL_SET(vop, mipi_pin_pol, val); + VOP_CTRL_SET(vop, mipi_dclk_pol, 1); break; case DRM_MODE_CONNECTOR_DisplayPort: - val &= ~BIT(DCLK_INVERT); + VOP_CTRL_SET(vop, dp_dclk_pol, 0); VOP_CTRL_SET(vop, dp_pin_pol, val); VOP_CTRL_SET(vop, dp_en, 1); break; + case DRM_MODE_CONNECTOR_TV: + if (vdisplay == CVBS_PAL_VDISPLAY) + VOP_CTRL_SET(vop, tve_sw_mode, 1); + else + VOP_CTRL_SET(vop, tve_sw_mode, 0); + + VOP_CTRL_SET(vop, tve_dclk_pol, 1); + VOP_CTRL_SET(vop, tve_dclk_en, 1); + /* use the same pol reg with hdmi */ + VOP_CTRL_SET(vop, hdmi_pin_pol, val); + VOP_CTRL_SET(vop, sw_genlock, 1); + VOP_CTRL_SET(vop, sw_uv_offset_en, 1); + VOP_CTRL_SET(vop, dither_up, 1); + break; default: DRM_ERROR("unsupport connector_type[%d]\n", s->output_type); } @@ -1540,9 +1755,11 @@ static void vop_crtc_enable(struct drm_crtc *crtc) val = DITHER_DOWN_EN(1) | DITHER_DOWN_MODE(RGB888_TO_RGB666); break; case MEDIA_BUS_FMT_YUV8_1X24: + case MEDIA_BUS_FMT_UYYVYY8_0_5X24: val = DITHER_DOWN_EN(0) | PRE_DITHER_DOWN_EN(1); break; case MEDIA_BUS_FMT_YUV10_1X30: + case MEDIA_BUS_FMT_UYYVYY10_0_5X30: val = DITHER_DOWN_EN(0) | PRE_DITHER_DOWN_EN(0); break; case MEDIA_BUS_FMT_RGB888_1X24: @@ -1560,17 +1777,33 @@ static void vop_crtc_enable(struct drm_crtc *crtc) VOP_CTRL_SET(vop, dclk_ddr, s->output_mode == ROCKCHIP_OUT_MODE_YUV420 ? 1 : 0); VOP_CTRL_SET(vop, overlay_mode, is_yuv_output(s->bus_format)); + VOP_CTRL_SET(vop, dsp_out_yuv, is_yuv_output(s->bus_format)); + + /* + * Background color is 10bit depth if vop version >= 3.5 + */ + if (!is_yuv_output(s->bus_format)) + val = 0; + else if (VOP_MAJOR(vop->data->version) == 3 && + VOP_MINOR(vop->data->version) >= 5) + val = 0x20010200; + else + val = 0x801080; + VOP_CTRL_SET(vop, dsp_background, val); VOP_CTRL_SET(vop, htotal_pw, (htotal << 16) | hsync_len); val = hact_st << 16; val |= hact_end; VOP_CTRL_SET(vop, hact_st_end, val); VOP_CTRL_SET(vop, hpost_st_end, val); - VOP_CTRL_SET(vop, vtotal_pw, (adjusted_mode->vtotal << 16) | vsync_len); val = vact_st << 16; val |= vact_end; VOP_CTRL_SET(vop, vact_st_end, val); VOP_CTRL_SET(vop, vpost_st_end, val); + + VOP_INTR_SET(vop, line_flag_num[0], vact_end); + VOP_INTR_SET(vop, line_flag_num[1], + vact_end - us_to_vertical_line(adjusted_mode, 1000)); if (adjusted_mode->flags & DRM_MODE_FLAG_INTERLACE) { u16 vact_st_f1 = vtotal + vact_st + 1; u16 vact_end_f1 = vact_st_f1 + vdisplay; @@ -1583,14 +1816,23 @@ static void vop_crtc_enable(struct drm_crtc *crtc) VOP_CTRL_SET(vop, vs_st_end_f1, val); VOP_CTRL_SET(vop, dsp_interlace, 1); VOP_CTRL_SET(vop, p2i_en, 1); + vtotal += vtotal + 1; } else { VOP_CTRL_SET(vop, dsp_interlace, 0); VOP_CTRL_SET(vop, p2i_en, 0); } + VOP_CTRL_SET(vop, vtotal_pw, vtotal << 16 | vsync_len); VOP_CTRL_SET(vop, core_dclk_div, !!(adjusted_mode->flags & DRM_MODE_FLAG_DBLCLK)); + VOP_CTRL_SET(vop, cabc_total_num, hdisplay * vdisplay); + VOP_CTRL_SET(vop, cabc_config_mode, STAGE_BY_STAGE); + VOP_CTRL_SET(vop, cabc_stage_up_mode, MUL_MODE); + VOP_CTRL_SET(vop, cabc_scale_cfg_value, 1); + VOP_CTRL_SET(vop, cabc_scale_cfg_enable, 0); + VOP_CTRL_SET(vop, cabc_global_dn_limit_en, 1); + clk_set_rate(vop->dclk, adjusted_mode->crtc_clock * 1000); vop_cfg_done(vop); @@ -1601,6 +1843,7 @@ static void vop_crtc_enable(struct drm_crtc *crtc) enable_irq(vop->irq); drm_crtc_vblank_on(crtc); + mutex_unlock(&vop->vop_lock); } static int vop_zpos_cmp(const void *a, const void *b) @@ -1683,6 +1926,40 @@ static int vop_afbdc_atomic_check(struct drm_crtc *crtc, return 0; } +static void vop_dclk_source_generate(struct drm_crtc *crtc, + struct drm_crtc_state *crtc_state) +{ + struct rockchip_drm_private *private = crtc->dev->dev_private; + struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state); + struct rockchip_crtc_state *old_s = to_rockchip_crtc_state(crtc->state); + struct vop *vop = to_vop(crtc); + + if (!vop->dclk_source) + return; + + if (crtc_state->active) { + WARN_ON(s->pll && !s->pll->use_count); + if (!s->pll || s->pll->use_count > 1 || + s->output_type != old_s->output_type) { + if (s->pll) + s->pll->use_count--; + + if (s->output_type != DRM_MODE_CONNECTOR_HDMIA && + !private->default_pll.use_count) + s->pll = &private->default_pll; + else + s->pll = &private->hdmi_pll; + + s->pll->use_count++; + } + } else if (s->pll) { + s->pll->use_count--; + s->pll = NULL; + } + if (s->pll && s->pll != old_s->pll) + crtc_state->mode_changed = true; +} + static int vop_crtc_atomic_check(struct drm_crtc *crtc, struct drm_crtc_state *crtc_state) { @@ -1757,6 +2034,8 @@ static int vop_crtc_atomic_check(struct drm_crtc *crtc, s->dsp_layer_sel = dsp_layer_sel; + vop_dclk_source_generate(crtc, crtc_state); + err_free_pzpos: kfree(pzpos); return ret; @@ -1790,7 +2069,12 @@ static void vop_post_config(struct drm_crtc *crtc) val = scl_cal_scale2(vdisplay, vsize) << 16; val |= scl_cal_scale2(hdisplay, hsize); VOP_CTRL_SET(vop, post_scl_factor, val); - VOP_CTRL_SET(vop, post_scl_ctrl, 0x3); + +#define POST_HORIZONTAL_SCALEDOWN_EN(x) ((x) << 0) +#define POST_VERTICAL_SCALEDOWN_EN(x) ((x) << 1) + VOP_CTRL_SET(vop, post_scl_ctrl, + POST_HORIZONTAL_SCALEDOWN_EN(hdisplay != hsize) || + POST_VERTICAL_SCALEDOWN_EN(vdisplay != vsize)); if (mode->flags & DRM_MODE_FLAG_INTERLACE) { u16 vact_st_f1 = vtotal + vact_st + 1; u16 vact_end_f1 = vact_st_f1 + vsize; @@ -1800,6 +2084,86 @@ static void vop_post_config(struct drm_crtc *crtc) } } +static void vop_update_cabc_lut(struct drm_crtc *crtc, + struct drm_crtc_state *old_crtc_state) +{ + struct rockchip_crtc_state *s = + to_rockchip_crtc_state(crtc->state); + struct rockchip_crtc_state *old_s = + to_rockchip_crtc_state(old_crtc_state); + struct drm_property_blob *cabc_lut = s->cabc_lut; + struct drm_property_blob *old_cabc_lut = old_s->cabc_lut; + struct vop *vop = to_vop(crtc); + int lut_size; + u32 *lut; + u32 lut_len = vop->cabc_lut_len; + int i, dle; + + if (!cabc_lut && old_cabc_lut) { + VOP_CTRL_SET(vop, cabc_lut_en, 0); + return; + } + if (!cabc_lut) + return; + + if (old_cabc_lut && old_cabc_lut->base.id == cabc_lut->base.id) + return; + + lut = (u32 *)cabc_lut->data; + lut_size = cabc_lut->length / sizeof(u32); + if (WARN(lut_size != lut_len, "Unexpect cabc lut size not match\n")) + return; + +#define CTRL_GET(name) VOP_CTRL_GET(vop, name) + if (CTRL_GET(cabc_lut_en)) { + VOP_CTRL_SET(vop, cabc_lut_en, 0); + vop_cfg_done(vop); + readx_poll_timeout(CTRL_GET, cabc_lut_en, dle, !dle, 5, 33333); + } + + for (i = 0; i < lut_len; i++) + vop_write_cabc_lut(vop, (i << 2), lut[i]); +#undef CTRL_GET + VOP_CTRL_SET(vop, cabc_lut_en, 1); +} + +static void vop_update_cabc(struct drm_crtc *crtc, + struct drm_crtc_state *old_crtc_state) +{ + struct rockchip_crtc_state *s = + to_rockchip_crtc_state(crtc->state); + struct vop *vop = to_vop(crtc); + struct drm_display_mode *mode = &crtc->state->adjusted_mode; + int pixel_total = mode->hdisplay * mode->vdisplay; + + if (!vop->cabc_lut_regs) + return; + + vop_update_cabc_lut(crtc, old_crtc_state); + + if (s->cabc_mode != ROCKCHIP_DRM_CABC_MODE_DISABLE) { + VOP_CTRL_SET(vop, cabc_en, 1); + VOP_CTRL_SET(vop, cabc_handle_en, 1); + VOP_CTRL_SET(vop, cabc_stage_up, s->cabc_stage_up); + VOP_CTRL_SET(vop, cabc_stage_down, s->cabc_stage_down); + VOP_CTRL_SET(vop, cabc_global_dn, s->cabc_global_dn); + VOP_CTRL_SET(vop, cabc_calc_pixel_num, + s->cabc_calc_pixel_num * pixel_total / 1000); + } else { + /* + * There are some hardware issues on cabc disabling: + * 1: if cabc auto gating enable, cabc disabling will cause + * vop die + * 2: cabc disabling always would make timing several + * pixel cycle abnormal, cause some panel abnormal. + * + * So just keep cabc enable, and make it no work with max + * cabc_calc_pixel_num, it only has little power consume. + */ + VOP_CTRL_SET(vop, cabc_calc_pixel_num, pixel_total); + } +} + static void vop_cfg_update(struct drm_crtc *crtc, struct drm_crtc_state *old_crtc_state) { @@ -1828,10 +2192,40 @@ static void vop_cfg_update(struct drm_crtc *crtc, spin_unlock(&vop->reg_lock); } +static bool vop_fs_irq_is_pending(struct vop *vop) +{ + return VOP_INTR_GET_TYPE(vop, status, FS_INTR); +} + +static void vop_wait_for_irq_handler(struct vop *vop) +{ + bool pending; + int ret; + + /* + * Spin until frame start interrupt status bit goes low, which means + * that interrupt handler was invoked and cleared it. The timeout of + * 10 msecs is really too long, but it is just a safety measure if + * something goes really wrong. The wait will only happen in the very + * unlikely case of a vblank happening exactly at the same time and + * shouldn't exceed microseconds range. + */ + ret = readx_poll_timeout_atomic(vop_fs_irq_is_pending, vop, pending, + !pending, 0, 10 * 1000); + if (ret) + DRM_DEV_ERROR(vop->dev, "VOP vblank IRQ stuck for 10 ms\n"); + + synchronize_irq(vop->irq); +} + static void vop_crtc_atomic_flush(struct drm_crtc *crtc, struct drm_crtc_state *old_crtc_state) { + struct drm_atomic_state *old_state = old_crtc_state->state; + struct drm_plane_state *old_plane_state; struct vop *vop = to_vop(crtc); + struct drm_plane *plane; + int i; vop_cfg_update(crtc, old_crtc_state); @@ -1876,7 +2270,29 @@ static void vop_crtc_atomic_flush(struct drm_crtc *crtc, vop->is_iommu_enabled = true; } + vop_update_cabc(crtc, old_crtc_state); + vop_cfg_done(vop); + + /* + * There is a (rather unlikely) possiblity that a vblank interrupt + * fired before we set the cfg_done bit. To avoid spuriously + * signalling flip completion we need to wait for it to finish. + */ + vop_wait_for_irq_handler(vop); + + for_each_plane_in_state(old_state, plane, old_plane_state, i) { + if (!old_plane_state->fb) + continue; + + if (old_plane_state->fb == plane->state->fb) + continue; + + drm_framebuffer_reference(old_plane_state->fb); + drm_flip_work_queue(&vop->fb_unref_work, old_plane_state->fb); + set_bit(VOP_PENDING_FB_UNREF, &vop->pending); + WARN_ON(drm_crtc_vblank_get(crtc) != 0); + } } static void vop_crtc_atomic_begin(struct drm_crtc *crtc, @@ -1893,6 +2309,7 @@ static void vop_crtc_atomic_begin(struct drm_crtc *crtc, } static const struct drm_crtc_helper_funcs vop_crtc_helper_funcs = { + .load_lut = vop_crtc_load_lut, .enable = vop_crtc_enable, .disable = vop_crtc_disable, .mode_fixup = vop_crtc_mode_fixup, @@ -1909,6 +2326,8 @@ static void vop_crtc_destroy(struct drm_crtc *crtc) static void vop_crtc_reset(struct drm_crtc *crtc) { struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc->state); + struct rockchip_drm_private *private = crtc->dev->dev_private; + struct vop *vop = to_vop(crtc); if (crtc->state) { __drm_atomic_helper_crtc_destroy_state(crtc, crtc->state); @@ -1920,6 +2339,20 @@ static void vop_crtc_reset(struct drm_crtc *crtc) return; crtc->state = &s->base; crtc->state->crtc = crtc; + + if (vop->dclk_source) { + struct clk *parent; + + parent = clk_get_parent(vop->dclk_source); + if (parent) { + if (clk_is_match(private->default_pll.pll, parent)) + s->pll = &private->default_pll; + else if (clk_is_match(private->hdmi_pll.pll, parent)) + s->pll = &private->hdmi_pll; + if (s->pll) + s->pll->use_count++; + } + } s->left_margin = 100; s->right_margin = 100; s->top_margin = 100; @@ -1954,6 +2387,7 @@ static int vop_crtc_atomic_get_property(struct drm_crtc *crtc, uint64_t *val) { struct drm_device *drm_dev = crtc->dev; + struct rockchip_drm_private *private = drm_dev->dev_private; struct drm_mode_config *mode_config = &drm_dev->mode_config; struct rockchip_crtc_state *s = to_rockchip_crtc_state(state); @@ -1977,6 +2411,36 @@ static int vop_crtc_atomic_get_property(struct drm_crtc *crtc, return 0; } + if (property == private->cabc_mode_property) { + *val = s->cabc_mode; + return 0; + } + + if (property == private->cabc_stage_up_property) { + *val = s->cabc_stage_up; + return 0; + } + + if (property == private->cabc_stage_down_property) { + *val = s->cabc_stage_down; + return 0; + } + + if (property == private->cabc_global_dn_property) { + *val = s->cabc_global_dn; + return 0; + } + + if (property == private->cabc_calc_pixel_num_property) { + *val = s->cabc_calc_pixel_num; + return 0; + } + + if (property == private->cabc_lut_property) { + *val = s->cabc_lut ? s->cabc_lut->base.id : 0; + return 0; + } + DRM_ERROR("failed to get vop crtc property\n"); return -EINVAL; } @@ -1987,8 +2451,10 @@ static int vop_crtc_atomic_set_property(struct drm_crtc *crtc, uint64_t val) { struct drm_device *drm_dev = crtc->dev; + struct rockchip_drm_private *private = drm_dev->dev_private; struct drm_mode_config *mode_config = &drm_dev->mode_config; struct rockchip_crtc_state *s = to_rockchip_crtc_state(state); + struct vop *vop = to_vop(crtc); if (property == mode_config->tv_left_margin_property) { s->left_margin = val; @@ -2010,21 +2476,100 @@ static int vop_crtc_atomic_set_property(struct drm_crtc *crtc, return 0; } + if (property == private->cabc_mode_property) { + s->cabc_mode = val; + /* + * Pre-define lowpower and normal mode to make cabc + * easier to use. + */ + if (s->cabc_mode == ROCKCHIP_DRM_CABC_MODE_NORMAL) { + s->cabc_stage_up = 257; + s->cabc_stage_down = 255; + s->cabc_global_dn = 192; + s->cabc_calc_pixel_num = 995; + } else if (s->cabc_mode == ROCKCHIP_DRM_CABC_MODE_LOWPOWER) { + s->cabc_stage_up = 260; + s->cabc_stage_down = 252; + s->cabc_global_dn = 180; + s->cabc_calc_pixel_num = 992; + } + return 0; + } + + if (property == private->cabc_stage_up_property) { + s->cabc_stage_up = val; + return 0; + } + + if (property == private->cabc_stage_down_property) { + s->cabc_stage_down = val; + return 0; + } + + if (property == private->cabc_calc_pixel_num_property) { + s->cabc_calc_pixel_num = val; + return 0; + } + + if (property == private->cabc_global_dn_property) { + s->cabc_global_dn = val; + return 0; + } + + if (property == private->cabc_lut_property) { + bool replaced; + ssize_t size = vop->cabc_lut_len * 4; + + return drm_atomic_replace_property_blob_from_id(crtc, + &s->cabc_lut, + val, + size, + &replaced); + } + DRM_ERROR("failed to set vop crtc property\n"); return -EINVAL; } +static void vop_crtc_gamma_set(struct drm_crtc *crtc, u16 *red, u16 *green, + u16 *blue, uint32_t start, uint32_t size) +{ + struct vop *vop = to_vop(crtc); + int end = min_t(u32, start + size, vop->lut_len); + int i; + + if (!vop->lut) + return; + + for (i = start; i < end; i++) + rockchip_vop_crtc_fb_gamma_set(crtc, red[i], green[i], + blue[i], i); + + vop_crtc_load_lut(crtc); +} + static const struct drm_crtc_funcs vop_crtc_funcs = { + .gamma_set = vop_crtc_gamma_set, .set_config = drm_atomic_helper_set_config, .page_flip = drm_atomic_helper_page_flip, .destroy = vop_crtc_destroy, .reset = vop_crtc_reset, + .set_property = drm_atomic_helper_crtc_set_property, .atomic_get_property = vop_crtc_atomic_get_property, .atomic_set_property = vop_crtc_atomic_set_property, .atomic_duplicate_state = vop_crtc_duplicate_state, .atomic_destroy_state = vop_crtc_destroy_state, }; +static void vop_fb_unref_worker(struct drm_flip_work *work, void *val) +{ + struct vop *vop = container_of(work, struct vop, fb_unref_work); + struct drm_framebuffer *fb = val; + + drm_crtc_vblank_put(&vop->crtc); + drm_framebuffer_unreference(fb); +} + static void vop_handle_vblank(struct vop *vop) { struct drm_device *drm = vop->drm_dev; @@ -2043,8 +2588,9 @@ static void vop_handle_vblank(struct vop *vop) spin_unlock_irqrestore(&drm->event_lock, flags); } - if (!completion_done(&vop->wait_update_complete)) - complete(&vop->wait_update_complete); + + if (test_and_clear_bit(VOP_PENDING_FB_UNREF, &vop->pending)) + drm_flip_work_commit(&vop->fb_unref_work, system_unbound_wq); } static irqreturn_t vop_isr(int irq, void *data) @@ -2091,6 +2637,23 @@ static irqreturn_t vop_isr(int irq, void *data) ret = IRQ_HANDLED; } +#define ERROR_HANDLER(x) \ + do { \ + if (active_irqs & x##_INTR) {\ + DRM_DEV_ERROR_RATELIMITED(vop->dev, #x " irq err\n"); \ + active_irqs &= ~x##_INTR; \ + ret = IRQ_HANDLED; \ + } \ + } while (0) + + ERROR_HANDLER(BUS_ERROR); + ERROR_HANDLER(WIN0_EMPTY); + ERROR_HANDLER(WIN1_EMPTY); + ERROR_HANDLER(WIN2_EMPTY); + ERROR_HANDLER(WIN3_EMPTY); + ERROR_HANDLER(HWC_EMPTY); + ERROR_HANDLER(POST_BUF_EMPTY); + /* Unhandled irqs are spurious. */ if (active_irqs) DRM_ERROR("Unknown VOP IRQs: %#02x\n", active_irqs); @@ -2101,6 +2664,7 @@ static irqreturn_t vop_isr(int irq, void *data) static int vop_plane_init(struct vop *vop, struct vop_win *win, unsigned long possible_crtcs) { + struct rockchip_drm_private *private = vop->drm_dev->dev_private; struct drm_plane *share = NULL; unsigned int rotations = 0; struct drm_property *prop; @@ -2124,9 +2688,17 @@ static int vop_plane_init(struct vop *vop, struct vop_win *win, if (VOP_WIN_SUPPORT(vop, win, xmirror)) rotations |= BIT(DRM_REFLECT_X); - if (VOP_WIN_SUPPORT(vop, win, ymirror)) + if (VOP_WIN_SUPPORT(vop, win, ymirror)) { rotations |= BIT(DRM_REFLECT_Y); + prop = drm_property_create_bool(vop->drm_dev, + DRM_MODE_PROP_ATOMIC, + "LOGO_YMIRROR"); + if (!prop) + return -ENOMEM; + private->logo_ymirror_prop = prop; + } + if (rotations) { rotations |= BIT(DRM_ROTATE_0); prop = drm_mode_create_rotation_property(vop->drm_dev, @@ -2156,6 +2728,7 @@ static int vop_create_crtc(struct vop *vop) struct device *dev = vop->dev; const struct vop_data *vop_data = vop->data; struct drm_device *drm_dev = vop->drm_dev; + struct rockchip_drm_private *private = drm_dev->dev_private; struct drm_plane *primary = NULL, *cursor = NULL, *plane, *tmp; struct drm_crtc *crtc = &vop->crtc; struct device_node *port; @@ -2218,8 +2791,10 @@ static int vop_create_crtc(struct vop *vop) goto err_cleanup_crtc; } + drm_flip_work_init(&vop->fb_unref_work, "fb_unref", + vop_fb_unref_worker); + init_completion(&vop->dsp_hold_completion); - init_completion(&vop->wait_update_complete); init_completion(&vop->line_flag_completion); crtc->port = port; rockchip_register_crtc_funcs(crtc, &private_crtc_funcs); @@ -2234,12 +2809,41 @@ static int vop_create_crtc(struct vop *vop) VOP_ATTACH_MODE_CONFIG_PROP(tv_right_margin_property, 100); VOP_ATTACH_MODE_CONFIG_PROP(tv_top_margin_property, 100); VOP_ATTACH_MODE_CONFIG_PROP(tv_bottom_margin_property, 100); + #undef VOP_ATTACH_MODE_CONFIG_PROP + drm_object_attach_property(&crtc->base, private->cabc_lut_property, 0); + drm_object_attach_property(&crtc->base, private->cabc_mode_property, 0); + drm_object_attach_property(&crtc->base, private->cabc_stage_up_property, 0); + drm_object_attach_property(&crtc->base, private->cabc_stage_down_property, 0); + drm_object_attach_property(&crtc->base, private->cabc_global_dn_property, 0); + drm_object_attach_property(&crtc->base, private->cabc_calc_pixel_num_property, 0); + if (vop_data->feature & VOP_FEATURE_AFBDC) feature |= BIT(ROCKCHIP_DRM_CRTC_FEATURE_AFBDC); drm_object_attach_property(&crtc->base, vop->feature_prop, feature); + if (vop->lut_regs) { + u16 *r_base, *g_base, *b_base; + u32 lut_len = vop->lut_len; + + drm_mode_crtc_set_gamma_size(crtc, lut_len); + vop->lut = devm_kmalloc_array(dev, lut_len, sizeof(*vop->lut), + GFP_KERNEL); + if (!vop->lut) + return -ENOMEM; + + r_base = crtc->gamma_store; + g_base = r_base + crtc->gamma_size; + b_base = g_base + crtc->gamma_size; + + for (i = 0; i < lut_len; i++) { + vop->lut[i] = i * lut_len * lut_len | i * lut_len | i; + rockchip_vop_crtc_fb_gamma_get(crtc, &r_base[i], + &g_base[i], &b_base[i], + i); + } + } return 0; @@ -2280,6 +2884,7 @@ static void vop_destroy_crtc(struct vop *vop) * references the CRTC. */ drm_crtc_cleanup(crtc); + drm_flip_work_cleanup(&vop->fb_unref_work); } /* @@ -2383,15 +2988,22 @@ int rockchip_drm_wait_line_flag(struct drm_crtc *crtc, unsigned int line_num, { struct vop *vop = to_vop(crtc); unsigned long jiffies_left; + int ret = 0; if (!crtc || !vop->is_enabled) return -ENODEV; - if (line_num > crtc->mode.vtotal || mstimeout <= 0) - return -EINVAL; + mutex_lock(&vop->vop_lock); - if (vop_line_flag_irq_is_enabled(vop)) - return -EBUSY; + if (line_num > crtc->mode.vtotal || mstimeout <= 0) { + ret = -EINVAL; + goto out; + } + + if (vop_line_flag_irq_is_enabled(vop)) { + ret = -EBUSY; + goto out; + } reinit_completion(&vop->line_flag_completion); vop_line_flag_irq_enable(vop, line_num); @@ -2402,13 +3014,62 @@ int rockchip_drm_wait_line_flag(struct drm_crtc *crtc, unsigned int line_num, if (jiffies_left == 0) { dev_err(vop->dev, "Timeout waiting for IRQ\n"); - return -ETIMEDOUT; + ret = -ETIMEDOUT; + goto out; } - return 0; +out: + mutex_unlock(&vop->vop_lock); + + return ret; } EXPORT_SYMBOL(rockchip_drm_wait_line_flag); +static int dmc_notifier_call(struct notifier_block *nb, unsigned long event, + void *data) +{ + if (event == DEVFREQ_PRECHANGE) + mutex_lock(&dmc_vop->vop_lock); + else if (event == DEVFREQ_POSTCHANGE) + mutex_unlock(&dmc_vop->vop_lock); + + return NOTIFY_OK; +} + +int rockchip_drm_register_notifier_to_dmc(struct devfreq *devfreq) +{ + if (!dmc_vop) + return -ENOMEM; + + dmc_vop->devfreq = devfreq; + dmc_vop->dmc_nb.notifier_call = dmc_notifier_call; + devfreq_register_notifier(dmc_vop->devfreq, &dmc_vop->dmc_nb, + DEVFREQ_TRANSITION_NOTIFIER); + return 0; +} +EXPORT_SYMBOL(rockchip_drm_register_notifier_to_dmc); + +static void vop_backlight_config_done(struct device *dev, bool async) +{ + struct vop *vop = dev_get_drvdata(dev); + + if (vop && vop->is_enabled) { + int dle; + + vop_cfg_done(vop); + if (!async) { + #define CTRL_GET(name) VOP_CTRL_GET(vop, name) + readx_poll_timeout(CTRL_GET, cfg_done, + dle, !dle, 5, 33333); + #undef CTRL_GET + } + } +} + +static const struct rockchip_sub_backlight_ops rockchip_sub_backlight_ops = { + .config_done = vop_backlight_config_done, +}; + static int vop_bind(struct device *dev, struct device *master, void *data) { struct platform_device *pdev = to_platform_device(dev); @@ -2446,16 +3107,51 @@ static int vop_bind(struct device *dev, struct device *master, void *data) if (ret) return ret; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - vop->len = resource_size(res); + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "regs"); + if (!res) { + dev_warn(vop->dev, "failed to get vop register byname\n"); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + } vop->regs = devm_ioremap_resource(dev, res); if (IS_ERR(vop->regs)) return PTR_ERR(vop->regs); + vop->len = resource_size(res); vop->regsbak = devm_kzalloc(dev, vop->len, GFP_KERNEL); if (!vop->regsbak) return -ENOMEM; + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "gamma_lut"); + vop->lut_regs = devm_ioremap_resource(dev, res); + if (IS_ERR(vop->lut_regs)) { + dev_warn(vop->dev, "failed to get vop lut registers\n"); + vop->lut_regs = NULL; + } + if (vop->lut_regs) { + vop->lut_len = resource_size(res) / sizeof(*vop->lut); + if (vop->lut_len != 256 && vop->lut_len != 1024) { + dev_err(vop->dev, "unsupport lut sizes %d\n", + vop->lut_len); + return -EINVAL; + } + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "cabc_lut"); + vop->cabc_lut_regs = devm_ioremap_resource(dev, res); + if (IS_ERR(vop->cabc_lut_regs)) { + dev_warn(vop->dev, "failed to get vop cabc lut registers\n"); + vop->cabc_lut_regs = NULL; + } + + if (vop->cabc_lut_regs) { + vop->cabc_lut_len = resource_size(res) >> 2; + if (vop->cabc_lut_len != 128) { + dev_err(vop->dev, "unsupport cabc lut sizes %d\n", + vop->cabc_lut_len); + return -EINVAL; + } + } + vop->hclk = devm_clk_get(vop->dev, "hclk_vop"); if (IS_ERR(vop->hclk)) { dev_err(vop->dev, "failed to get hclk source\n"); @@ -2472,6 +3168,16 @@ static int vop_bind(struct device *dev, struct device *master, void *data) return PTR_ERR(vop->dclk); } + vop->dclk_source = devm_clk_get(vop->dev, "dclk_source"); + if (PTR_ERR(vop->dclk_source) == -ENOENT) { + vop->dclk_source = NULL; + } else if (PTR_ERR(vop->dclk_source) == -EPROBE_DEFER) { + return -EPROBE_DEFER; + } else if (IS_ERR(vop->dclk_source)) { + dev_err(vop->dev, "failed to get dclk source parent\n"); + return PTR_ERR(vop->dclk_source); + } + irq = platform_get_irq(pdev, 0); if (irq < 0) { dev_err(dev, "cannot find irq for vop\n"); @@ -2481,6 +3187,7 @@ static int vop_bind(struct device *dev, struct device *master, void *data) spin_lock_init(&vop->reg_lock); spin_lock_init(&vop->irq_lock); + mutex_init(&vop->vop_lock); mutex_init(&vop->vsync_mutex); @@ -2497,6 +3204,12 @@ static int vop_bind(struct device *dev, struct device *master, void *data) return ret; pm_runtime_enable(&pdev->dev); + + of_rockchip_drm_sub_backlight_register(dev, &vop->crtc, + &rockchip_sub_backlight_ops); + + dmc_vop = vop; + return 0; }