Hi all,
The full name of PSR is Panel Self Refresh, panel device could refresh itself with the hardware framebuffer in panel, this would make a lots of sense to save the power consumption.
This v3 version have splited an common PSR driver for Rockchip, which is biggest changes from v2.
This thread is based on Mark's RK3399 VOP thread[0] and my RK3399 eDP thread[1].
[0]: https://patchwork.kernel.org/patch/8886041/ [1]: https://patchwork.kernel.org/patch/9204497/
Thanks, - Yakir
Changes in v3: - Remove the notify for waiting line_flag event (Daniel) - implement the 'fb->dirty' callback function (Daniel) - avoid to use notify to acqiure for vact event (Daniel) - split the psr flow into an common abstracted PSR driver - Export the 'rockchip_drm_wait_line_flag' symbol, and document it. - Add 'line_flag_num_0' for RK3288/RK3036 - remove psr_active() callback which introduce in v2 - split analogix_dp_enable_psr(), make it more clearly analogix_dp_detect_sink_psr() analogix_dp_enable_sink_psr() - remove some nosie register setting comments - split the common psr logic into a seperate driver, make this to a simple sub-psr device driver.
Changes in v2: - Introduce in v2, split VOP line flag changes out - introduce in v2, splite the common Analogix DP changes out - remove vblank notify out (Daniel) - create a psr_active() callback in vop data struct.
Yakir Yang (4): drm/rockchip: vop: export line flag function drm/rockchip: add an common abstracted PSR driver drm/bridge: analogix_dp: add the PSR function support drm/rockchip: analogix_dp: implement PSR function
drivers/gpu/drm/bridge/analogix/analogix_dp_core.c | 64 +++++++ drivers/gpu/drm/bridge/analogix/analogix_dp_core.h | 4 + drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c | 54 ++++++ drivers/gpu/drm/bridge/analogix/analogix_dp_reg.h | 28 +++ drivers/gpu/drm/rockchip/Makefile | 2 +- drivers/gpu/drm/rockchip/analogix_dp-rockchip.c | 52 ++++++ drivers/gpu/drm/rockchip/rockchip_drm_drv.h | 3 + drivers/gpu/drm/rockchip/rockchip_drm_fb.c | 12 ++ drivers/gpu/drm/rockchip/rockchip_drm_psr.c | 200 +++++++++++++++++++++ drivers/gpu/drm/rockchip/rockchip_drm_psr.h | 12 ++ drivers/gpu/drm/rockchip/rockchip_drm_vop.c | 127 +++++++++++++ drivers/gpu/drm/rockchip/rockchip_drm_vop.h | 3 + drivers/gpu/drm/rockchip/rockchip_vop_reg.c | 4 + include/drm/bridge/analogix_dp.h | 3 + 14 files changed, 567 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_psr.c create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_psr.h
VOP have integrated a hardware counter which indicate the exact display line that vop is scanning. And if we're interested in a specific line, we can set the line number to vop line_flag register, and then vop would generate a line_flag interrupt for it.
For example eDP PSR function is interested in the vertical blanking period, then driver could set the line number to zero.
This patch have exported a symbol that allow other driver to listen the line flag event with given timeout limit: - rockchip_drm_wait_line_flag()
Signed-off-by: Yakir Yang ykk@rock-chips.com --- Changes in v3: - Export the 'rockchip_drm_wait_line_flag' symbol, and document it. - Add 'line_flag_num_0' for RK3288/RK3036 - Remove the notify for waiting line_flag event (Daniel)
Changes in v2: - Introduce in v2, split VOP line flag changes out
drivers/gpu/drm/rockchip/rockchip_drm_drv.h | 3 + drivers/gpu/drm/rockchip/rockchip_drm_vop.c | 103 ++++++++++++++++++++++++++++ drivers/gpu/drm/rockchip/rockchip_drm_vop.h | 3 + drivers/gpu/drm/rockchip/rockchip_vop_reg.c | 4 ++ 4 files changed, 113 insertions(+)
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h index ea39329..239b830 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h @@ -70,4 +70,7 @@ int rockchip_drm_dma_attach_device(struct drm_device *drm_dev, struct device *dev); void rockchip_drm_dma_detach_device(struct drm_device *drm_dev, struct device *dev); +int rockchip_drm_wait_line_flag(struct drm_crtc *crtc, unsigned int line_num, + unsigned int mstimeout); + #endif /* _ROCKCHIP_DRM_DRV_H_ */ diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c index c8a62a8..cd3cac5 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c @@ -121,6 +121,8 @@ struct vop { /* protected by dev->event_lock */ struct drm_pending_vblank_event *event;
+ struct completion line_flag_completion; + const struct vop_data *data;
uint32_t *regsbak; @@ -431,6 +433,59 @@ static void vop_dsp_hold_valid_irq_disable(struct vop *vop) spin_unlock_irqrestore(&vop->irq_lock, flags); }
+/* + * (1) each frame starts at the start of the Vsync pulse which is signaled by + * the "FRAME_SYNC" interrupt. + * (2) the active data region of each frame ends at dsp_vact_end + * (3) we should program this same number (dsp_vact_end) into dsp_line_frag_num, + * to get "LINE_FLAG" interrupt at the end of the active on screen data. + * + * VOP_INTR_CTRL0.dsp_line_frag_num = VOP_DSP_VACT_ST_END.dsp_vact_end + * Interrupts + * LINE_FLAG -------------------------------+ + * FRAME_SYNC ----+ | + * | | + * v v + * | Vsync | Vbp | Vactive | Vfp | + * ^ ^ ^ ^ + * | | | | + * | | | | + * dsp_vs_end ------------+ | | | VOP_DSP_VTOTAL_VS_END + * dsp_vact_start --------------+ | | VOP_DSP_VACT_ST_END + * dsp_vact_end ----------------------------+ | VOP_DSP_VACT_ST_END + * dsp_total -------------------------------------+ VOP_DSP_VTOTAL_VS_END + */ + +static void vop_line_flag_irq_enable(struct vop *vop, int line_num) +{ + unsigned long flags; + + if (WARN_ON(!vop->is_enabled)) + return; + + spin_lock_irqsave(&vop->irq_lock, flags); + + VOP_CTRL_SET(vop, line_flag_num_0, line_num); + VOP_INTR_SET_TYPE(vop, enable, LINE_FLAG_INTR, 1); + vop_cfg_done(vop); + + spin_unlock_irqrestore(&vop->irq_lock, flags); +} + +static void vop_line_flag_irq_disable(struct vop *vop) +{ + unsigned long flags; + + if (WARN_ON(!vop->is_enabled)) + return; + + spin_lock_irqsave(&vop->irq_lock, flags); + + VOP_INTR_SET_TYPE(vop, enable, LINE_FLAG_INTR, 0); + + spin_unlock_irqrestore(&vop->irq_lock, flags); +} + static void vop_enable(struct drm_crtc *crtc) { struct vop *vop = to_vop(crtc); @@ -1157,6 +1212,13 @@ static irqreturn_t vop_isr(int irq, void *data) ret = IRQ_HANDLED; }
+ if (active_irqs & LINE_FLAG_INTR) { + if (!completion_done(&vop->line_flag_completion)) + complete(&vop->line_flag_completion); + active_irqs &= ~LINE_FLAG_INTR; + ret = IRQ_HANDLED; + } + if (active_irqs & FS_INTR) { drm_crtc_handle_vblank(crtc); vop_handle_vblank(vop); @@ -1255,6 +1317,7 @@ static int vop_create_crtc(struct vop *vop)
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);
@@ -1411,6 +1474,46 @@ static void vop_win_init(struct vop *vop) } }
+/** + * rockchip_drm_wait_line_flag - acqiure the give line flag event + * @crtc: CRTC to enable line flag + * @line_num: interested line number + * @mstimeout: millisecond for timeout + * + * Driver would hold here until the interested line flag interrupt have + * happened or timeout to wait. + * + * Returns: + * Zero on success, negative errno on failure. + */ +int rockchip_drm_wait_line_flag(struct drm_crtc *crtc, unsigned int line_num, + unsigned int mstimeout) +{ + struct vop *vop = to_vop(crtc); + unsigned long jiffies_left; + + if (!crtc || !vop->is_enabled) + return -ENODEV; + + if (line_num > crtc->mode.vtotal || mstimeout <= 0) + return -EINVAL; + + reinit_completion(&vop->line_flag_completion); + vop_line_flag_irq_enable(vop, line_num); + + jiffies_left = wait_for_completion_timeout(&vop->line_flag_completion, + msecs_to_jiffies(mstimeout)); + vop_line_flag_irq_disable(vop); + + if (jiffies_left == 0) { + dev_err(vop->dev, "Timeout waiting for IRQ\n"); + return -ETIMEDOUT; + } + + return 0; +} +EXPORT_SYMBOL(rockchip_drm_wait_line_flag); + static int vop_bind(struct device *dev, struct device *master, void *data) { struct platform_device *pdev = to_platform_device(dev); diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h index ff4f52e..34fcd03 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h @@ -61,6 +61,9 @@ struct vop_ctrl { struct vop_reg hpost_st_end; struct vop_reg vpost_st_end;
+ struct vop_reg line_flag_num_0; + struct vop_reg line_flag_num_1; + struct vop_reg cfg_done; };
diff --git a/drivers/gpu/drm/rockchip/rockchip_vop_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop_reg.c index 6f42e56..e9211c9 100644 --- a/drivers/gpu/drm/rockchip/rockchip_vop_reg.c +++ b/drivers/gpu/drm/rockchip/rockchip_vop_reg.c @@ -122,6 +122,7 @@ static const struct vop_ctrl rk3036_ctrl_data = { .hact_st_end = VOP_REG(RK3036_DSP_HACT_ST_END, 0x1fff1fff, 0), .vtotal_pw = VOP_REG(RK3036_DSP_VTOTAL_VS_END, 0x1fff1fff, 0), .vact_st_end = VOP_REG(RK3036_DSP_VACT_ST_END, 0x1fff1fff, 0), + .line_flag_num_0 = VOP_REG(RK3036_INT_STATUS, 0xfff, 12), .cfg_done = VOP_REG(RK3036_REG_CFG_DONE, 0x1, 0), };
@@ -221,6 +222,7 @@ static const struct vop_ctrl rk3288_ctrl_data = { .vact_st_end = VOP_REG(RK3288_DSP_VACT_ST_END, 0x1fff1fff, 0), .hpost_st_end = VOP_REG(RK3288_POST_DSP_HACT_INFO, 0x1fff1fff, 0), .vpost_st_end = VOP_REG(RK3288_POST_DSP_VACT_INFO, 0x1fff1fff, 0), + .line_flag_num_0 = VOP_REG(RK3288_INTR_CTRL0, 0x1fff, 12), .cfg_done = VOP_REG(RK3288_REG_CFG_DONE, 0x1, 0), };
@@ -299,6 +301,8 @@ static const struct vop_ctrl rk3399_ctrl_data = { .vact_st_end = VOP_REG(RK3399_DSP_VACT_ST_END, 0x1fff1fff, 0), .hpost_st_end = VOP_REG(RK3399_POST_DSP_HACT_INFO, 0x1fff1fff, 0), .vpost_st_end = VOP_REG(RK3399_POST_DSP_VACT_INFO, 0x1fff1fff, 0), + .line_flag_num_0 = VOP_REG(RK3399_LINE_FLAG, 0xffff, 0), + .line_flag_num_1 = VOP_REG(RK3399_LINE_FLAG, 0xffff, 16), .cfg_done = VOP_REG_MASK(RK3399_REG_CFG_DONE, 0x1, 0), };
On Fri, Jul 1, 2016 at 5:19 AM, Yakir Yang ykk@rock-chips.com wrote:
VOP have integrated a hardware counter which indicate the exact display line that vop is scanning. And if we're interested in a specific line, we can set the line number to vop line_flag register, and then vop would generate a line_flag interrupt for it.
For example eDP PSR function is interested in the vertical blanking period, then driver could set the line number to zero.
This patch have exported a symbol that allow other driver to listen the line flag event with given timeout limit:
- rockchip_drm_wait_line_flag()
Signed-off-by: Yakir Yang ykk@rock-chips.com
Changes in v3:
- Export the 'rockchip_drm_wait_line_flag' symbol, and document it.
- Add 'line_flag_num_0' for RK3288/RK3036
- Remove the notify for waiting line_flag event (Daniel)
Changes in v2:
- Introduce in v2, split VOP line flag changes out
drivers/gpu/drm/rockchip/rockchip_drm_drv.h | 3 + drivers/gpu/drm/rockchip/rockchip_drm_vop.c | 103 ++++++++++++++++++++++++++++ drivers/gpu/drm/rockchip/rockchip_drm_vop.h | 3 + drivers/gpu/drm/rockchip/rockchip_vop_reg.c | 4 ++ 4 files changed, 113 insertions(+)
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h index ea39329..239b830 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h @@ -70,4 +70,7 @@ int rockchip_drm_dma_attach_device(struct drm_device *drm_dev, struct device *dev); void rockchip_drm_dma_detach_device(struct drm_device *drm_dev, struct device *dev); +int rockchip_drm_wait_line_flag(struct drm_crtc *crtc, unsigned int line_num,
unsigned int mstimeout);
#endif /* _ROCKCHIP_DRM_DRV_H_ */ diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c index c8a62a8..cd3cac5 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c @@ -121,6 +121,8 @@ struct vop { /* protected by dev->event_lock */ struct drm_pending_vblank_event *event;
struct completion line_flag_completion;
const struct vop_data *data; uint32_t *regsbak;
@@ -431,6 +433,59 @@ static void vop_dsp_hold_valid_irq_disable(struct vop *vop) spin_unlock_irqrestore(&vop->irq_lock, flags); }
+/*
- (1) each frame starts at the start of the Vsync pulse which is signaled by
the "FRAME_SYNC" interrupt.
- (2) the active data region of each frame ends at dsp_vact_end
- (3) we should program this same number (dsp_vact_end) into dsp_line_frag_num,
to get "LINE_FLAG" interrupt at the end of the active on screen data.
- VOP_INTR_CTRL0.dsp_line_frag_num = VOP_DSP_VACT_ST_END.dsp_vact_end
- Interrupts
- LINE_FLAG -------------------------------+
- FRAME_SYNC ----+ |
| |
v v
| Vsync | Vbp | Vactive | Vfp |
^ ^ ^ ^
| | | |
| | | |
- dsp_vs_end ------------+ | | | VOP_DSP_VTOTAL_VS_END
- dsp_vact_start --------------+ | | VOP_DSP_VACT_ST_END
- dsp_vact_end ----------------------------+ | VOP_DSP_VACT_ST_END
- dsp_total -------------------------------------+ VOP_DSP_VTOTAL_VS_END
- */
+static void vop_line_flag_irq_enable(struct vop *vop, int line_num) +{
unsigned long flags;
if (WARN_ON(!vop->is_enabled))
return;
spin_lock_irqsave(&vop->irq_lock, flags);
VOP_CTRL_SET(vop, line_flag_num_0, line_num);
VOP_INTR_SET_TYPE(vop, enable, LINE_FLAG_INTR, 1);
vop_cfg_done(vop);
spin_unlock_irqrestore(&vop->irq_lock, flags);
+}
+static void vop_line_flag_irq_disable(struct vop *vop) +{
unsigned long flags;
if (WARN_ON(!vop->is_enabled))
return;
spin_lock_irqsave(&vop->irq_lock, flags);
VOP_INTR_SET_TYPE(vop, enable, LINE_FLAG_INTR, 0);
spin_unlock_irqrestore(&vop->irq_lock, flags);
+}
static void vop_enable(struct drm_crtc *crtc) { struct vop *vop = to_vop(crtc); @@ -1157,6 +1212,13 @@ static irqreturn_t vop_isr(int irq, void *data) ret = IRQ_HANDLED; }
if (active_irqs & LINE_FLAG_INTR) {
if (!completion_done(&vop->line_flag_completion))
complete(&vop->line_flag_completion);
I think there's potential to miss flags here if the timing is just right because the completion_done and complete are not atomic wrt the wait_line_flag function. I think you need some locking around the completion operations to ensure they're properly sequenced.
FWIW, if you're reinitializing the completion every time in drm_wait_line_flag, you should be able to just call complete (or complete_all) without checking if there are any waiters.
active_irqs &= ~LINE_FLAG_INTR;
ret = IRQ_HANDLED;
}
if (active_irqs & FS_INTR) { drm_crtc_handle_vblank(crtc); vop_handle_vblank(vop);
@@ -1255,6 +1317,7 @@ static int vop_create_crtc(struct vop *vop)
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);
@@ -1411,6 +1474,46 @@ static void vop_win_init(struct vop *vop) } }
+/**
- rockchip_drm_wait_line_flag - acqiure the give line flag event
- @crtc: CRTC to enable line flag
- @line_num: interested line number
- @mstimeout: millisecond for timeout
- Driver would hold here until the interested line flag interrupt have
- happened or timeout to wait.
- Returns:
- Zero on success, negative errno on failure.
- */
+int rockchip_drm_wait_line_flag(struct drm_crtc *crtc, unsigned int line_num,
unsigned int mstimeout)
+{
struct vop *vop = to_vop(crtc);
unsigned long jiffies_left;
if (!crtc || !vop->is_enabled)
return -ENODEV;
if (line_num > crtc->mode.vtotal || mstimeout <= 0)
return -EINVAL;
reinit_completion(&vop->line_flag_completion);
vop_line_flag_irq_enable(vop, line_num);
This will only work for one waiter per vop. Is it worth enforcing this explicitly to avoid weird behavior when there are more than one?
jiffies_left = wait_for_completion_timeout(&vop->line_flag_completion,
msecs_to_jiffies(mstimeout));
vop_line_flag_irq_disable(vop);
if (jiffies_left == 0) {
dev_err(vop->dev, "Timeout waiting for IRQ\n");
return -ETIMEDOUT;
}
return 0;
+} +EXPORT_SYMBOL(rockchip_drm_wait_line_flag);
static int vop_bind(struct device *dev, struct device *master, void *data) { struct platform_device *pdev = to_platform_device(dev); diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h index ff4f52e..34fcd03 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h @@ -61,6 +61,9 @@ struct vop_ctrl { struct vop_reg hpost_st_end; struct vop_reg vpost_st_end;
struct vop_reg line_flag_num_0;
struct vop_reg line_flag_num_1;
nit: you could make this an array:
struct vop_reg line_flag_num[2];
struct vop_reg cfg_done;
};
diff --git a/drivers/gpu/drm/rockchip/rockchip_vop_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop_reg.c index 6f42e56..e9211c9 100644 --- a/drivers/gpu/drm/rockchip/rockchip_vop_reg.c +++ b/drivers/gpu/drm/rockchip/rockchip_vop_reg.c @@ -122,6 +122,7 @@ static const struct vop_ctrl rk3036_ctrl_data = { .hact_st_end = VOP_REG(RK3036_DSP_HACT_ST_END, 0x1fff1fff, 0), .vtotal_pw = VOP_REG(RK3036_DSP_VTOTAL_VS_END, 0x1fff1fff, 0), .vact_st_end = VOP_REG(RK3036_DSP_VACT_ST_END, 0x1fff1fff, 0),
.line_flag_num_0 = VOP_REG(RK3036_INT_STATUS, 0xfff, 12), .cfg_done = VOP_REG(RK3036_REG_CFG_DONE, 0x1, 0),
};
@@ -221,6 +222,7 @@ static const struct vop_ctrl rk3288_ctrl_data = { .vact_st_end = VOP_REG(RK3288_DSP_VACT_ST_END, 0x1fff1fff, 0), .hpost_st_end = VOP_REG(RK3288_POST_DSP_HACT_INFO, 0x1fff1fff, 0), .vpost_st_end = VOP_REG(RK3288_POST_DSP_VACT_INFO, 0x1fff1fff, 0),
.line_flag_num_0 = VOP_REG(RK3288_INTR_CTRL0, 0x1fff, 12), .cfg_done = VOP_REG(RK3288_REG_CFG_DONE, 0x1, 0),
};
@@ -299,6 +301,8 @@ static const struct vop_ctrl rk3399_ctrl_data = { .vact_st_end = VOP_REG(RK3399_DSP_VACT_ST_END, 0x1fff1fff, 0), .hpost_st_end = VOP_REG(RK3399_POST_DSP_HACT_INFO, 0x1fff1fff, 0), .vpost_st_end = VOP_REG(RK3399_POST_DSP_VACT_INFO, 0x1fff1fff, 0),
.line_flag_num_0 = VOP_REG(RK3399_LINE_FLAG, 0xffff, 0),
.line_flag_num_1 = VOP_REG(RK3399_LINE_FLAG, 0xffff, 16), .cfg_done = VOP_REG_MASK(RK3399_REG_CFG_DONE, 0x1, 0),
};
-- 1.9.1
On Fri, Jul 1, 2016 at 11:30 AM, Sean Paul seanpaul@chromium.org wrote:
On Fri, Jul 1, 2016 at 5:19 AM, Yakir Yang ykk@rock-chips.com wrote:
VOP have integrated a hardware counter which indicate the exact display line that vop is scanning. And if we're interested in a specific line, we can set the line number to vop line_flag register, and then vop would generate a line_flag interrupt for it.
For example eDP PSR function is interested in the vertical blanking period, then driver could set the line number to zero.
This patch have exported a symbol that allow other driver to listen the line flag event with given timeout limit:
- rockchip_drm_wait_line_flag()
Signed-off-by: Yakir Yang ykk@rock-chips.com
Changes in v3:
- Export the 'rockchip_drm_wait_line_flag' symbol, and document it.
- Add 'line_flag_num_0' for RK3288/RK3036
- Remove the notify for waiting line_flag event (Daniel)
Changes in v2:
- Introduce in v2, split VOP line flag changes out
drivers/gpu/drm/rockchip/rockchip_drm_drv.h | 3 + drivers/gpu/drm/rockchip/rockchip_drm_vop.c | 103 ++++++++++++++++++++++++++++ drivers/gpu/drm/rockchip/rockchip_drm_vop.h | 3 + drivers/gpu/drm/rockchip/rockchip_vop_reg.c | 4 ++ 4 files changed, 113 insertions(+)
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h index ea39329..239b830 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h @@ -70,4 +70,7 @@ int rockchip_drm_dma_attach_device(struct drm_device *drm_dev, struct device *dev); void rockchip_drm_dma_detach_device(struct drm_device *drm_dev, struct device *dev); +int rockchip_drm_wait_line_flag(struct drm_crtc *crtc, unsigned int line_num,
unsigned int mstimeout);
#endif /* _ROCKCHIP_DRM_DRV_H_ */ diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c index c8a62a8..cd3cac5 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c @@ -121,6 +121,8 @@ struct vop { /* protected by dev->event_lock */ struct drm_pending_vblank_event *event;
struct completion line_flag_completion;
const struct vop_data *data; uint32_t *regsbak;
@@ -431,6 +433,59 @@ static void vop_dsp_hold_valid_irq_disable(struct vop *vop) spin_unlock_irqrestore(&vop->irq_lock, flags); }
+/*
- (1) each frame starts at the start of the Vsync pulse which is signaled by
the "FRAME_SYNC" interrupt.
- (2) the active data region of each frame ends at dsp_vact_end
- (3) we should program this same number (dsp_vact_end) into dsp_line_frag_num,
to get "LINE_FLAG" interrupt at the end of the active on screen data.
- VOP_INTR_CTRL0.dsp_line_frag_num = VOP_DSP_VACT_ST_END.dsp_vact_end
- Interrupts
- LINE_FLAG -------------------------------+
- FRAME_SYNC ----+ |
| |
v v
| Vsync | Vbp | Vactive | Vfp |
^ ^ ^ ^
| | | |
| | | |
- dsp_vs_end ------------+ | | | VOP_DSP_VTOTAL_VS_END
- dsp_vact_start --------------+ | | VOP_DSP_VACT_ST_END
- dsp_vact_end ----------------------------+ | VOP_DSP_VACT_ST_END
- dsp_total -------------------------------------+ VOP_DSP_VTOTAL_VS_END
- */
+static void vop_line_flag_irq_enable(struct vop *vop, int line_num) +{
unsigned long flags;
if (WARN_ON(!vop->is_enabled))
return;
spin_lock_irqsave(&vop->irq_lock, flags);
VOP_CTRL_SET(vop, line_flag_num_0, line_num);
VOP_INTR_SET_TYPE(vop, enable, LINE_FLAG_INTR, 1);
vop_cfg_done(vop);
spin_unlock_irqrestore(&vop->irq_lock, flags);
+}
+static void vop_line_flag_irq_disable(struct vop *vop) +{
unsigned long flags;
if (WARN_ON(!vop->is_enabled))
return;
spin_lock_irqsave(&vop->irq_lock, flags);
VOP_INTR_SET_TYPE(vop, enable, LINE_FLAG_INTR, 0);
spin_unlock_irqrestore(&vop->irq_lock, flags);
+}
static void vop_enable(struct drm_crtc *crtc) { struct vop *vop = to_vop(crtc); @@ -1157,6 +1212,13 @@ static irqreturn_t vop_isr(int irq, void *data) ret = IRQ_HANDLED; }
if (active_irqs & LINE_FLAG_INTR) {
if (!completion_done(&vop->line_flag_completion))
complete(&vop->line_flag_completion);
I think there's potential to miss flags here if the timing is just right because the completion_done and complete are not atomic wrt the wait_line_flag function. I think you need some locking around the completion operations to ensure they're properly sequenced.
FWIW, if you're reinitializing the completion every time in drm_wait_line_flag, you should be able to just call complete (or complete_all) without checking if there are any waiters.
I should add, if you just call complete/complete_all() here, you shouldn't need to add locking (assuming only one waiter).
active_irqs &= ~LINE_FLAG_INTR;
ret = IRQ_HANDLED;
}
if (active_irqs & FS_INTR) { drm_crtc_handle_vblank(crtc); vop_handle_vblank(vop);
@@ -1255,6 +1317,7 @@ static int vop_create_crtc(struct vop *vop)
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);
@@ -1411,6 +1474,46 @@ static void vop_win_init(struct vop *vop) } }
+/**
- rockchip_drm_wait_line_flag - acqiure the give line flag event
- @crtc: CRTC to enable line flag
- @line_num: interested line number
- @mstimeout: millisecond for timeout
- Driver would hold here until the interested line flag interrupt have
- happened or timeout to wait.
- Returns:
- Zero on success, negative errno on failure.
- */
+int rockchip_drm_wait_line_flag(struct drm_crtc *crtc, unsigned int line_num,
unsigned int mstimeout)
+{
struct vop *vop = to_vop(crtc);
unsigned long jiffies_left;
if (!crtc || !vop->is_enabled)
return -ENODEV;
if (line_num > crtc->mode.vtotal || mstimeout <= 0)
return -EINVAL;
reinit_completion(&vop->line_flag_completion);
vop_line_flag_irq_enable(vop, line_num);
This will only work for one waiter per vop. Is it worth enforcing this explicitly to avoid weird behavior when there are more than one?
jiffies_left = wait_for_completion_timeout(&vop->line_flag_completion,
msecs_to_jiffies(mstimeout));
vop_line_flag_irq_disable(vop);
if (jiffies_left == 0) {
dev_err(vop->dev, "Timeout waiting for IRQ\n");
return -ETIMEDOUT;
}
return 0;
+} +EXPORT_SYMBOL(rockchip_drm_wait_line_flag);
static int vop_bind(struct device *dev, struct device *master, void *data) { struct platform_device *pdev = to_platform_device(dev); diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h index ff4f52e..34fcd03 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h @@ -61,6 +61,9 @@ struct vop_ctrl { struct vop_reg hpost_st_end; struct vop_reg vpost_st_end;
struct vop_reg line_flag_num_0;
struct vop_reg line_flag_num_1;
nit: you could make this an array:
struct vop_reg line_flag_num[2];
struct vop_reg cfg_done;
};
diff --git a/drivers/gpu/drm/rockchip/rockchip_vop_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop_reg.c index 6f42e56..e9211c9 100644 --- a/drivers/gpu/drm/rockchip/rockchip_vop_reg.c +++ b/drivers/gpu/drm/rockchip/rockchip_vop_reg.c @@ -122,6 +122,7 @@ static const struct vop_ctrl rk3036_ctrl_data = { .hact_st_end = VOP_REG(RK3036_DSP_HACT_ST_END, 0x1fff1fff, 0), .vtotal_pw = VOP_REG(RK3036_DSP_VTOTAL_VS_END, 0x1fff1fff, 0), .vact_st_end = VOP_REG(RK3036_DSP_VACT_ST_END, 0x1fff1fff, 0),
.line_flag_num_0 = VOP_REG(RK3036_INT_STATUS, 0xfff, 12), .cfg_done = VOP_REG(RK3036_REG_CFG_DONE, 0x1, 0),
};
@@ -221,6 +222,7 @@ static const struct vop_ctrl rk3288_ctrl_data = { .vact_st_end = VOP_REG(RK3288_DSP_VACT_ST_END, 0x1fff1fff, 0), .hpost_st_end = VOP_REG(RK3288_POST_DSP_HACT_INFO, 0x1fff1fff, 0), .vpost_st_end = VOP_REG(RK3288_POST_DSP_VACT_INFO, 0x1fff1fff, 0),
.line_flag_num_0 = VOP_REG(RK3288_INTR_CTRL0, 0x1fff, 12), .cfg_done = VOP_REG(RK3288_REG_CFG_DONE, 0x1, 0),
};
@@ -299,6 +301,8 @@ static const struct vop_ctrl rk3399_ctrl_data = { .vact_st_end = VOP_REG(RK3399_DSP_VACT_ST_END, 0x1fff1fff, 0), .hpost_st_end = VOP_REG(RK3399_POST_DSP_HACT_INFO, 0x1fff1fff, 0), .vpost_st_end = VOP_REG(RK3399_POST_DSP_VACT_INFO, 0x1fff1fff, 0),
.line_flag_num_0 = VOP_REG(RK3399_LINE_FLAG, 0xffff, 0),
.line_flag_num_1 = VOP_REG(RK3399_LINE_FLAG, 0xffff, 16), .cfg_done = VOP_REG_MASK(RK3399_REG_CFG_DONE, 0x1, 0),
};
-- 1.9.1
Sean,
On 07/01/2016 11:32 PM, Sean Paul wrote:
On Fri, Jul 1, 2016 at 11:30 AM, Sean Paul seanpaul@chromium.org wrote:
On Fri, Jul 1, 2016 at 5:19 AM, Yakir Yang ykk@rock-chips.com wrote:
VOP have integrated a hardware counter which indicate the exact display line that vop is scanning. And if we're interested in a specific line, we can set the line number to vop line_flag register, and then vop would generate a line_flag interrupt for it.
For example eDP PSR function is interested in the vertical blanking period, then driver could set the line number to zero.
This patch have exported a symbol that allow other driver to listen the line flag event with given timeout limit:
- rockchip_drm_wait_line_flag()
Signed-off-by: Yakir Yang ykk@rock-chips.com
Changes in v3:
- Export the 'rockchip_drm_wait_line_flag' symbol, and document it.
- Add 'line_flag_num_0' for RK3288/RK3036
- Remove the notify for waiting line_flag event (Daniel)
Changes in v2:
Introduce in v2, split VOP line flag changes out
drivers/gpu/drm/rockchip/rockchip_drm_drv.h | 3 + drivers/gpu/drm/rockchip/rockchip_drm_vop.c | 103 ++++++++++++++++++++++++++++ drivers/gpu/drm/rockchip/rockchip_drm_vop.h | 3 + drivers/gpu/drm/rockchip/rockchip_vop_reg.c | 4 ++ 4 files changed, 113 insertions(+)
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h index ea39329..239b830 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h @@ -70,4 +70,7 @@ int rockchip_drm_dma_attach_device(struct drm_device *drm_dev, struct device *dev); void rockchip_drm_dma_detach_device(struct drm_device *drm_dev, struct device *dev); +int rockchip_drm_wait_line_flag(struct drm_crtc *crtc, unsigned int line_num,
unsigned int mstimeout);
- #endif /* _ROCKCHIP_DRM_DRV_H_ */
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c index c8a62a8..cd3cac5 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c @@ -121,6 +121,8 @@ struct vop { /* protected by dev->event_lock */ struct drm_pending_vblank_event *event;
struct completion line_flag_completion;
const struct vop_data *data; uint32_t *regsbak;
@@ -431,6 +433,59 @@ static void vop_dsp_hold_valid_irq_disable(struct vop *vop) spin_unlock_irqrestore(&vop->irq_lock, flags); }
+/*
- (1) each frame starts at the start of the Vsync pulse which is signaled by
the "FRAME_SYNC" interrupt.
- (2) the active data region of each frame ends at dsp_vact_end
- (3) we should program this same number (dsp_vact_end) into dsp_line_frag_num,
to get "LINE_FLAG" interrupt at the end of the active on screen data.
- VOP_INTR_CTRL0.dsp_line_frag_num = VOP_DSP_VACT_ST_END.dsp_vact_end
- Interrupts
- LINE_FLAG -------------------------------+
- FRAME_SYNC ----+ |
| |
v v
| Vsync | Vbp | Vactive | Vfp |
^ ^ ^ ^
| | | |
| | | |
- dsp_vs_end ------------+ | | | VOP_DSP_VTOTAL_VS_END
- dsp_vact_start --------------+ | | VOP_DSP_VACT_ST_END
- dsp_vact_end ----------------------------+ | VOP_DSP_VACT_ST_END
- dsp_total -------------------------------------+ VOP_DSP_VTOTAL_VS_END
- */
+static void vop_line_flag_irq_enable(struct vop *vop, int line_num) +{
unsigned long flags;
if (WARN_ON(!vop->is_enabled))
return;
spin_lock_irqsave(&vop->irq_lock, flags);
VOP_CTRL_SET(vop, line_flag_num_0, line_num);
VOP_INTR_SET_TYPE(vop, enable, LINE_FLAG_INTR, 1);
vop_cfg_done(vop);
spin_unlock_irqrestore(&vop->irq_lock, flags);
+}
+static void vop_line_flag_irq_disable(struct vop *vop) +{
unsigned long flags;
if (WARN_ON(!vop->is_enabled))
return;
spin_lock_irqsave(&vop->irq_lock, flags);
VOP_INTR_SET_TYPE(vop, enable, LINE_FLAG_INTR, 0);
spin_unlock_irqrestore(&vop->irq_lock, flags);
+}
- static void vop_enable(struct drm_crtc *crtc) { struct vop *vop = to_vop(crtc);
@@ -1157,6 +1212,13 @@ static irqreturn_t vop_isr(int irq, void *data) ret = IRQ_HANDLED; }
if (active_irqs & LINE_FLAG_INTR) {
if (!completion_done(&vop->line_flag_completion))
complete(&vop->line_flag_completion);
I think there's potential to miss flags here if the timing is just right because the completion_done and complete are not atomic wrt the wait_line_flag function. I think you need some locking around the completion operations to ensure they're properly sequenced.
FWIW, if you're reinitializing the completion every time in drm_wait_line_flag, you should be able to just call complete (or complete_all) without checking if there are any waiters.
I should add, if you just call complete/complete_all() here, you shouldn't need to add locking (assuming only one waiter).
Yes, only one waiter.
active_irqs &= ~LINE_FLAG_INTR;
ret = IRQ_HANDLED;
}
if (active_irqs & FS_INTR) { drm_crtc_handle_vblank(crtc); vop_handle_vblank(vop);
@@ -1255,6 +1317,7 @@ static int vop_create_crtc(struct vop *vop)
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);
@@ -1411,6 +1474,46 @@ static void vop_win_init(struct vop *vop) } }
+/**
- rockchip_drm_wait_line_flag - acqiure the give line flag event
- @crtc: CRTC to enable line flag
- @line_num: interested line number
- @mstimeout: millisecond for timeout
- Driver would hold here until the interested line flag interrupt have
- happened or timeout to wait.
- Returns:
- Zero on success, negative errno on failure.
- */
+int rockchip_drm_wait_line_flag(struct drm_crtc *crtc, unsigned int line_num,
unsigned int mstimeout)
+{
struct vop *vop = to_vop(crtc);
unsigned long jiffies_left;
if (!crtc || !vop->is_enabled)
return -ENODEV;
if (line_num > crtc->mode.vtotal || mstimeout <= 0)
return -EINVAL;
reinit_completion(&vop->line_flag_completion);
vop_line_flag_irq_enable(vop, line_num);
This will only work for one waiter per vop. Is it worth enforcing this explicitly to avoid weird behavior when there are more than one?
Yes, you're right, need to avoid the weird behavior here.
jiffies_left = wait_for_completion_timeout(&vop->line_flag_completion,
msecs_to_jiffies(mstimeout));
vop_line_flag_irq_disable(vop);
if (jiffies_left == 0) {
dev_err(vop->dev, "Timeout waiting for IRQ\n");
return -ETIMEDOUT;
}
return 0;
+} +EXPORT_SYMBOL(rockchip_drm_wait_line_flag);
- static int vop_bind(struct device *dev, struct device *master, void *data) { struct platform_device *pdev = to_platform_device(dev);
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h index ff4f52e..34fcd03 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h @@ -61,6 +61,9 @@ struct vop_ctrl { struct vop_reg hpost_st_end; struct vop_reg vpost_st_end;
struct vop_reg line_flag_num_0;
struct vop_reg line_flag_num_1;
nit: you could make this an array:
struct vop_reg line_flag_num[2];
Done,
struct vop_reg cfg_done;
};
diff --git a/drivers/gpu/drm/rockchip/rockchip_vop_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop_reg.c index 6f42e56..e9211c9 100644 --- a/drivers/gpu/drm/rockchip/rockchip_vop_reg.c +++ b/drivers/gpu/drm/rockchip/rockchip_vop_reg.c @@ -122,6 +122,7 @@ static const struct vop_ctrl rk3036_ctrl_data = { .hact_st_end = VOP_REG(RK3036_DSP_HACT_ST_END, 0x1fff1fff, 0), .vtotal_pw = VOP_REG(RK3036_DSP_VTOTAL_VS_END, 0x1fff1fff, 0), .vact_st_end = VOP_REG(RK3036_DSP_VACT_ST_END, 0x1fff1fff, 0),
};.line_flag_num_0 = VOP_REG(RK3036_INT_STATUS, 0xfff, 12), .cfg_done = VOP_REG(RK3036_REG_CFG_DONE, 0x1, 0),
@@ -221,6 +222,7 @@ static const struct vop_ctrl rk3288_ctrl_data = { .vact_st_end = VOP_REG(RK3288_DSP_VACT_ST_END, 0x1fff1fff, 0), .hpost_st_end = VOP_REG(RK3288_POST_DSP_HACT_INFO, 0x1fff1fff, 0), .vpost_st_end = VOP_REG(RK3288_POST_DSP_VACT_INFO, 0x1fff1fff, 0),
};.line_flag_num_0 = VOP_REG(RK3288_INTR_CTRL0, 0x1fff, 12), .cfg_done = VOP_REG(RK3288_REG_CFG_DONE, 0x1, 0),
@@ -299,6 +301,8 @@ static const struct vop_ctrl rk3399_ctrl_data = { .vact_st_end = VOP_REG(RK3399_DSP_VACT_ST_END, 0x1fff1fff, 0), .hpost_st_end = VOP_REG(RK3399_POST_DSP_HACT_INFO, 0x1fff1fff, 0), .vpost_st_end = VOP_REG(RK3399_POST_DSP_VACT_INFO, 0x1fff1fff, 0),
.line_flag_num_0 = VOP_REG(RK3399_LINE_FLAG, 0xffff, 0),
};.line_flag_num_1 = VOP_REG(RK3399_LINE_FLAG, 0xffff, 16), .cfg_done = VOP_REG_MASK(RK3399_REG_CFG_DONE, 0x1, 0),
-- 1.9.1
The PSR driver have exported four symbols for specific device driver: - rockchip_drm_psr_register() - rockchip_drm_psr_unregister() - rockchip_drm_psr_enable() - rockchip_drm_psr_disable() - rockchip_drm_psr_flush()
Encoder driver should call the register/unregister interfaces to hook itself into common PSR driver, encoder have implement the 'psr_set' callback which use the set PSR state in hardware side.
Crtc driver would call the enable/disable interfaces when vblank is enable/disable, after that the common PSR driver would call the encoder registered callback to set the PSR state.
Fb driver would call the flush interface in 'fb->dirty' callback, this helper function would force all PSR enabled encoders to exit from PSR for 3 seconds.
Signed-off-by: Yakir Yang ykk@rock-chips.com --- Changes in v3: - split the psr flow into an common abstracted PSR driver - implement the 'fb->dirty' callback function (Daniel) - avoid to use notify to acqiure for vact event (Daniel) - remove psr_active() callback which introduce in v2
Changes in v2: None
drivers/gpu/drm/rockchip/Makefile | 2 +- drivers/gpu/drm/rockchip/rockchip_drm_fb.c | 12 ++ drivers/gpu/drm/rockchip/rockchip_drm_psr.c | 200 ++++++++++++++++++++++++++++ drivers/gpu/drm/rockchip/rockchip_drm_psr.h | 12 ++ drivers/gpu/drm/rockchip/rockchip_drm_vop.c | 24 ++++ 5 files changed, 249 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_psr.c create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_psr.h
diff --git a/drivers/gpu/drm/rockchip/Makefile b/drivers/gpu/drm/rockchip/Makefile index 05d0713..9746365 100644 --- a/drivers/gpu/drm/rockchip/Makefile +++ b/drivers/gpu/drm/rockchip/Makefile @@ -3,7 +3,7 @@ # Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher.
rockchipdrm-y := rockchip_drm_drv.o rockchip_drm_fb.o \ - rockchip_drm_gem.o rockchip_drm_vop.o + rockchip_drm_gem.o rockchip_drm_psr.o rockchip_drm_vop.o rockchipdrm-$(CONFIG_DRM_FBDEV_EMULATION) += rockchip_drm_fbdev.o
obj-$(CONFIG_ROCKCHIP_ANALOGIX_DP) += analogix_dp-rockchip.o diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fb.c b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c index 20f12bc..0fec18f 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_fb.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c @@ -21,6 +21,7 @@
#include "rockchip_drm_drv.h" #include "rockchip_drm_gem.h" +#include "rockchip_drm_psr.h"
#define to_rockchip_fb(x) container_of(x, struct rockchip_drm_fb, fb)
@@ -66,9 +67,20 @@ static int rockchip_drm_fb_create_handle(struct drm_framebuffer *fb, rockchip_fb->obj[0], handle); }
+static int rockchip_drm_fb_dirty(struct drm_framebuffer *fb, + struct drm_file *file, + unsigned int flags, unsigned int color, + struct drm_clip_rect *clips, + unsigned int num_clips) +{ + rockchip_drm_psr_flush(); + return 0; +} + static const struct drm_framebuffer_funcs rockchip_drm_fb_funcs = { .destroy = rockchip_drm_fb_destroy, .create_handle = rockchip_drm_fb_create_handle, + .dirty = rockchip_drm_fb_dirty, };
static struct rockchip_drm_fb * diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_psr.c b/drivers/gpu/drm/rockchip/rockchip_drm_psr.c new file mode 100644 index 0000000..c044443 --- /dev/null +++ b/drivers/gpu/drm/rockchip/rockchip_drm_psr.c @@ -0,0 +1,200 @@ +#include <drm/drm_crtc_helper.h> + +#include "rockchip_drm_psr.h" + +#define PSR_FLUSH_TIMEOUT msecs_to_jiffies(3000) /* 3 seconds */ + +static LIST_HEAD(psr_list); +static DEFINE_MUTEX(psr_list_mutex); + +enum psr_state { + PSR_FLUSH, + PSR_ENABLE, + PSR_DISABLE, +}; + +struct psr_drv { + struct list_head list; + enum psr_state state; + struct mutex state_mutex; + + struct timer_list flush_timer; + + struct drm_encoder *encoder; + int (*set)(struct drm_encoder *encoder, bool enable); +}; + +static struct psr_drv *find_psr_by_crtc(struct drm_crtc *crtc) +{ + struct psr_drv *psr; + + mutex_lock(&psr_list_mutex); + list_for_each_entry(psr, &psr_list, list) { + if (psr->encoder->crtc == crtc) { + mutex_unlock(&psr_list_mutex); + return psr; + } + } + mutex_unlock(&psr_list_mutex); + + return ERR_PTR(-ENODEV); +} + +static void psr_enable(struct psr_drv *psr) +{ + if (psr->state == PSR_ENABLE) + return; + + mutex_lock(&psr->state_mutex); + psr->state = PSR_ENABLE; + psr->set(psr->encoder, true); + mutex_unlock(&psr->state_mutex); +} + +static void psr_disable(struct psr_drv *psr) +{ + if (psr->state == PSR_DISABLE) + return; + + mutex_lock(&psr->state_mutex); + psr->state = PSR_DISABLE; + psr->set(psr->encoder, false); + mutex_unlock(&psr->state_mutex); +} + +static void psr_flush_handler(unsigned long data) +{ + struct psr_drv *psr = (struct psr_drv *)data; + + if (!psr || psr->state != PSR_FLUSH) + return; + + psr_enable(psr); +} + +/** + * rockchip_drm_psr_enable - enable the encoder PSR which bind to given CRTC + * @crtc: CRTC to obtain the PSR encoder + * + * Returns: + * Zero on success, negative errno on failure. + */ +int rockchip_drm_psr_enable(struct drm_crtc *crtc) +{ + struct psr_drv *psr = find_psr_by_crtc(crtc); + + if (IS_ERR(psr)) + return PTR_ERR(psr); + + psr_enable(psr); + return 0; +} +EXPORT_SYMBOL(rockchip_drm_psr_enable); + +/** + * rockchip_drm_psr_disable - disable the encoder PSR which bind to given CRTC + * @crtc: CRTC to obtain the PSR encoder + * + * Returns: + * Zero on success, negative errno on failure. + */ +int rockchip_drm_psr_disable(struct drm_crtc *crtc) +{ + struct psr_drv *psr = find_psr_by_crtc(crtc); + + if (IS_ERR(psr)) + return PTR_ERR(psr); + + psr_disable(psr); + return 0; +} +EXPORT_SYMBOL(rockchip_drm_psr_disable); + +/** + * rockchip_drm_psr_flush - force to flush all registered PSR encoders + * + * Disable the PSR function for all registered encoders, and then enable the + * PSR function back after 5 second. If encoder PSR state have been changed + * during flush time, then keep the state no change after flush timeout. + * + * Returns: + * Zero on success, negative errno on failure. + */ +void rockchip_drm_psr_flush(void) +{ + struct psr_drv *psr; + + mutex_lock(&psr_list_mutex); + list_for_each_entry(psr, &psr_list, list) { + if (psr->state == PSR_DISABLE) + continue; + + mod_timer(&psr->flush_timer, + round_jiffies_up(jiffies + PSR_FLUSH_TIMEOUT)); + + psr_disable(psr); + psr->state = PSR_FLUSH; + } + mutex_unlock(&psr_list_mutex); +} +EXPORT_SYMBOL(rockchip_drm_psr_flush); + +/** + * rockchip_drm_psr_register - register encoder to psr driver + * @encoder: encoder that obtain the PSR function + * @psr_set: call back to set PSR state + * + * Returns: + * Zero on success, negative errno on failure. + */ +int rockchip_drm_psr_register(struct drm_encoder *encoder, + int (*psr_set)(struct drm_encoder *, bool enable)) +{ + struct psr_drv *psr; + + if (!encoder || !psr_set) + return -EINVAL; + + psr = kzalloc(sizeof(struct psr_drv), GFP_KERNEL); + if (!psr) + return -ENOMEM; + + setup_timer(&psr->flush_timer, psr_flush_handler, (unsigned long)psr); + + mutex_init(&psr->state_mutex); + + psr->state = PSR_DISABLE; + psr->encoder = encoder; + psr->set = psr_set; + + mutex_lock(&psr_list_mutex); + list_add_tail(&psr->list, &psr_list); + mutex_unlock(&psr_list_mutex); + + return 0; +} +EXPORT_SYMBOL(rockchip_drm_psr_register); + +/** + * rockchip_drm_psr_unregister - unregister encoder to psr driver + * @encoder: encoder that obtain the PSR function + * @psr_set: call back to set PSR state + * + * Returns: + * Zero on success, negative errno on failure. + */ +void rockchip_drm_psr_unregister(struct drm_encoder *encoder) +{ + struct psr_drv *psr; + + mutex_lock(&psr_list_mutex); + list_for_each_entry(psr, &psr_list, list) { + if (psr->encoder == encoder) { + del_timer(&psr->flush_timer); + list_del(&psr->list); + kfree(psr); + } + } + mutex_unlock(&psr_list_mutex); +} +EXPORT_SYMBOL(rockchip_drm_psr_unregister); diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_psr.h b/drivers/gpu/drm/rockchip/rockchip_drm_psr.h new file mode 100644 index 0000000..622f605 --- /dev/null +++ b/drivers/gpu/drm/rockchip/rockchip_drm_psr.h @@ -0,0 +1,12 @@ +#ifndef __ROCKCHIP_DRM_PSR___ +#define __ROCKCHIP_DRM_PSR___ + +void rockchip_drm_psr_flush(void); +int rockchip_drm_psr_enable(struct drm_crtc *crtc); +int rockchip_drm_psr_disable(struct drm_crtc *crtc); + +int rockchip_drm_psr_register(struct drm_encoder *encoder, + int (*psr_set)(struct drm_encoder *, bool enable)); +void rockchip_drm_psr_unregister(struct drm_encoder *encoder); + +#endif /* __ROCKCHIP_DRM_PSR__ */ diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c index cd3cac5..3c6dfc5 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c @@ -34,6 +34,7 @@ #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"
#define __REG_SET_RELAXED(x, off, mask, shift, v, write_mask) \ @@ -121,6 +122,9 @@ struct vop { /* protected by dev->event_lock */ struct drm_pending_vblank_event *event;
+ bool psr_enabled; + struct delayed_work psr_work; + struct completion line_flag_completion;
const struct vop_data *data; @@ -911,6 +915,16 @@ static const struct drm_plane_funcs vop_plane_funcs = { .atomic_destroy_state = vop_atomic_plane_destroy_state, };
+static void vop_psr_work(struct work_struct *work) +{ + struct vop *vop = container_of(work, typeof(*vop), psr_work.work); + + if (vop->psr_enabled) + rockchip_drm_psr_enable(&vop->crtc); + else + rockchip_drm_psr_disable(&vop->crtc); +} + static int vop_crtc_enable_vblank(struct drm_crtc *crtc) { struct vop *vop = to_vop(crtc); @@ -925,6 +939,9 @@ static int vop_crtc_enable_vblank(struct drm_crtc *crtc)
spin_unlock_irqrestore(&vop->irq_lock, flags);
+ vop->psr_enabled = false; + schedule_delayed_work(&vop->psr_work, msecs_to_jiffies(10)); + return 0; }
@@ -941,6 +958,9 @@ static void vop_crtc_disable_vblank(struct drm_crtc *crtc) VOP_INTR_SET_TYPE(vop, enable, FS_INTR, 0);
spin_unlock_irqrestore(&vop->irq_lock, flags); + + vop->psr_enabled = true; + schedule_delayed_work(&vop->psr_work, msecs_to_jiffies(10)); }
static void vop_crtc_wait_for_update(struct drm_crtc *crtc) @@ -1582,6 +1602,10 @@ static int vop_bind(struct device *dev, struct device *master, void *data) return ret;
pm_runtime_enable(&pdev->dev); + + vop->psr_enabled = false; + INIT_DELAYED_WORK(&vop->psr_work, vop_psr_work); + return 0; }
On Fri, Jul 1, 2016 at 5:19 AM, Yakir Yang ykk@rock-chips.com wrote:
The PSR driver have exported four symbols for specific device driver:
- rockchip_drm_psr_register()
- rockchip_drm_psr_unregister()
- rockchip_drm_psr_enable()
- rockchip_drm_psr_disable()
- rockchip_drm_psr_flush()
Encoder driver should call the register/unregister interfaces to hook itself into common PSR driver, encoder have implement the 'psr_set' callback which use the set PSR state in hardware side.
Crtc driver would call the enable/disable interfaces when vblank is enable/disable, after that the common PSR driver would call the encoder registered callback to set the PSR state.
This feels overly complicated. It seems like you could cut out a bunch of code by just coding the psr functions into vop and analogix_dp-rockchip. I suppose the only reason to keep it abstracted would be if you plan on supporting psr in a different encoder or crtc in rockchip, or if you're planning on moving this into drm core.
Perhaps others will disagree with this sentiment and this is the right thing to do.
Fb driver would call the flush interface in 'fb->dirty' callback, this helper function would force all PSR enabled encoders to exit from PSR for 3 seconds.
Signed-off-by: Yakir Yang ykk@rock-chips.com
Changes in v3:
- split the psr flow into an common abstracted PSR driver
- implement the 'fb->dirty' callback function (Daniel)
- avoid to use notify to acqiure for vact event (Daniel)
- remove psr_active() callback which introduce in v2
Changes in v2: None
drivers/gpu/drm/rockchip/Makefile | 2 +- drivers/gpu/drm/rockchip/rockchip_drm_fb.c | 12 ++ drivers/gpu/drm/rockchip/rockchip_drm_psr.c | 200 ++++++++++++++++++++++++++++ drivers/gpu/drm/rockchip/rockchip_drm_psr.h | 12 ++ drivers/gpu/drm/rockchip/rockchip_drm_vop.c | 24 ++++ 5 files changed, 249 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_psr.c create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_psr.h
diff --git a/drivers/gpu/drm/rockchip/Makefile b/drivers/gpu/drm/rockchip/Makefile index 05d0713..9746365 100644 --- a/drivers/gpu/drm/rockchip/Makefile +++ b/drivers/gpu/drm/rockchip/Makefile @@ -3,7 +3,7 @@ # Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher.
rockchipdrm-y := rockchip_drm_drv.o rockchip_drm_fb.o \
rockchip_drm_gem.o rockchip_drm_vop.o
rockchip_drm_gem.o rockchip_drm_psr.o rockchip_drm_vop.o
rockchipdrm-$(CONFIG_DRM_FBDEV_EMULATION) += rockchip_drm_fbdev.o
obj-$(CONFIG_ROCKCHIP_ANALOGIX_DP) += analogix_dp-rockchip.o diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fb.c b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c index 20f12bc..0fec18f 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_fb.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c @@ -21,6 +21,7 @@
#include "rockchip_drm_drv.h" #include "rockchip_drm_gem.h" +#include "rockchip_drm_psr.h"
#define to_rockchip_fb(x) container_of(x, struct rockchip_drm_fb, fb)
@@ -66,9 +67,20 @@ static int rockchip_drm_fb_create_handle(struct drm_framebuffer *fb, rockchip_fb->obj[0], handle); }
+static int rockchip_drm_fb_dirty(struct drm_framebuffer *fb,
struct drm_file *file,
unsigned int flags, unsigned int color,
struct drm_clip_rect *clips,
unsigned int num_clips)
+{
rockchip_drm_psr_flush();
return 0;
+}
static const struct drm_framebuffer_funcs rockchip_drm_fb_funcs = { .destroy = rockchip_drm_fb_destroy, .create_handle = rockchip_drm_fb_create_handle,
.dirty = rockchip_drm_fb_dirty,
};
static struct rockchip_drm_fb * diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_psr.c b/drivers/gpu/drm/rockchip/rockchip_drm_psr.c new file mode 100644 index 0000000..c044443 --- /dev/null +++ b/drivers/gpu/drm/rockchip/rockchip_drm_psr.c @@ -0,0 +1,200 @@ +#include <drm/drm_crtc_helper.h>
+#include "rockchip_drm_psr.h"
+#define PSR_FLUSH_TIMEOUT msecs_to_jiffies(3000) /* 3 seconds */
+static LIST_HEAD(psr_list); +static DEFINE_MUTEX(psr_list_mutex);
I'm not crazy about these globals. Perhaps you can initialize them with the rockchip driver and tuck them in a driver-level struct (rockchip_drm_private or something).
+enum psr_state {
PSR_FLUSH,
PSR_ENABLE,
PSR_DISABLE,
+};
+struct psr_drv {
struct list_head list;
enum psr_state state;
struct mutex state_mutex;
struct timer_list flush_timer;
struct drm_encoder *encoder;
int (*set)(struct drm_encoder *encoder, bool enable);
+};
+static struct psr_drv *find_psr_by_crtc(struct drm_crtc *crtc) +{
struct psr_drv *psr;
mutex_lock(&psr_list_mutex);
list_for_each_entry(psr, &psr_list, list) {
if (psr->encoder->crtc == crtc) {
mutex_unlock(&psr_list_mutex);
return psr;
}
}
mutex_unlock(&psr_list_mutex);
return ERR_PTR(-ENODEV);
+}
+static void psr_enable(struct psr_drv *psr) +{
if (psr->state == PSR_ENABLE)
return;
Should you be worried about races by accessing this outside of the state mutex?
mutex_lock(&psr->state_mutex);
psr->state = PSR_ENABLE;
psr->set(psr->encoder, true);
mutex_unlock(&psr->state_mutex);
+}
+static void psr_disable(struct psr_drv *psr) +{
if (psr->state == PSR_DISABLE)
return;
mutex_lock(&psr->state_mutex);
psr->state = PSR_DISABLE;
psr->set(psr->encoder, false);
mutex_unlock(&psr->state_mutex);
+}
+static void psr_flush_handler(unsigned long data) +{
struct psr_drv *psr = (struct psr_drv *)data;
if (!psr || psr->state != PSR_FLUSH)
return;
psr_enable(psr);
+}
+/**
- rockchip_drm_psr_enable - enable the encoder PSR which bind to given CRTC
- @crtc: CRTC to obtain the PSR encoder
- Returns:
- Zero on success, negative errno on failure.
- */
+int rockchip_drm_psr_enable(struct drm_crtc *crtc) +{
struct psr_drv *psr = find_psr_by_crtc(crtc);
if (IS_ERR(psr))
return PTR_ERR(psr);
psr_enable(psr);
return 0;
+} +EXPORT_SYMBOL(rockchip_drm_psr_enable);
+/**
- rockchip_drm_psr_disable - disable the encoder PSR which bind to given CRTC
- @crtc: CRTC to obtain the PSR encoder
- Returns:
- Zero on success, negative errno on failure.
- */
+int rockchip_drm_psr_disable(struct drm_crtc *crtc) +{
struct psr_drv *psr = find_psr_by_crtc(crtc);
if (IS_ERR(psr))
return PTR_ERR(psr);
psr_disable(psr);
return 0;
+} +EXPORT_SYMBOL(rockchip_drm_psr_disable);
+/**
- rockchip_drm_psr_flush - force to flush all registered PSR encoders
- Disable the PSR function for all registered encoders, and then enable the
- PSR function back after 5 second. If encoder PSR state have been changed
s/5 second/PSR_FLUSH_TIMEOUT/
- during flush time, then keep the state no change after flush timeout.
- Returns:
- Zero on success, negative errno on failure.
- */
+void rockchip_drm_psr_flush(void) +{
struct psr_drv *psr;
mutex_lock(&psr_list_mutex);
list_for_each_entry(psr, &psr_list, list) {
if (psr->state == PSR_DISABLE)
continue;
mod_timer(&psr->flush_timer,
round_jiffies_up(jiffies + PSR_FLUSH_TIMEOUT));
psr_disable(psr);
psr->state = PSR_FLUSH;
This is set outside of state_mutex, is that intentional?
}
mutex_unlock(&psr_list_mutex);
+} +EXPORT_SYMBOL(rockchip_drm_psr_flush);
+/**
- rockchip_drm_psr_register - register encoder to psr driver
- @encoder: encoder that obtain the PSR function
- @psr_set: call back to set PSR state
- Returns:
- Zero on success, negative errno on failure.
- */
+int rockchip_drm_psr_register(struct drm_encoder *encoder,
int (*psr_set)(struct drm_encoder *, bool enable))
+{
struct psr_drv *psr;
if (!encoder || !psr_set)
return -EINVAL;
psr = kzalloc(sizeof(struct psr_drv), GFP_KERNEL);
if (!psr)
return -ENOMEM;
setup_timer(&psr->flush_timer, psr_flush_handler, (unsigned long)psr);
mutex_init(&psr->state_mutex);
psr->state = PSR_DISABLE;
psr->encoder = encoder;
psr->set = psr_set;
mutex_lock(&psr_list_mutex);
list_add_tail(&psr->list, &psr_list);
mutex_unlock(&psr_list_mutex);
return 0;
+} +EXPORT_SYMBOL(rockchip_drm_psr_register);
+/**
- rockchip_drm_psr_unregister - unregister encoder to psr driver
- @encoder: encoder that obtain the PSR function
- @psr_set: call back to set PSR state
- Returns:
- Zero on success, negative errno on failure.
- */
+void rockchip_drm_psr_unregister(struct drm_encoder *encoder) +{
struct psr_drv *psr;
mutex_lock(&psr_list_mutex);
list_for_each_entry(psr, &psr_list, list) {
if (psr->encoder == encoder) {
del_timer(&psr->flush_timer);
list_del(&psr->list);
kfree(psr);
}
}
mutex_unlock(&psr_list_mutex);
+} +EXPORT_SYMBOL(rockchip_drm_psr_unregister); diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_psr.h b/drivers/gpu/drm/rockchip/rockchip_drm_psr.h new file mode 100644 index 0000000..622f605 --- /dev/null +++ b/drivers/gpu/drm/rockchip/rockchip_drm_psr.h @@ -0,0 +1,12 @@ +#ifndef __ROCKCHIP_DRM_PSR___ +#define __ROCKCHIP_DRM_PSR___
+void rockchip_drm_psr_flush(void); +int rockchip_drm_psr_enable(struct drm_crtc *crtc); +int rockchip_drm_psr_disable(struct drm_crtc *crtc);
+int rockchip_drm_psr_register(struct drm_encoder *encoder,
int (*psr_set)(struct drm_encoder *, bool enable));
+void rockchip_drm_psr_unregister(struct drm_encoder *encoder);
+#endif /* __ROCKCHIP_DRM_PSR__ */ diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c index cd3cac5..3c6dfc5 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c @@ -34,6 +34,7 @@ #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"
#define __REG_SET_RELAXED(x, off, mask, shift, v, write_mask) \ @@ -121,6 +122,9 @@ struct vop { /* protected by dev->event_lock */ struct drm_pending_vblank_event *event;
bool psr_enabled;
struct delayed_work psr_work;
struct completion line_flag_completion; const struct vop_data *data;
@@ -911,6 +915,16 @@ static const struct drm_plane_funcs vop_plane_funcs = { .atomic_destroy_state = vop_atomic_plane_destroy_state, };
+static void vop_psr_work(struct work_struct *work) +{
struct vop *vop = container_of(work, typeof(*vop), psr_work.work);
if (vop->psr_enabled)
rockchip_drm_psr_enable(&vop->crtc);
else
rockchip_drm_psr_disable(&vop->crtc);
+}
static int vop_crtc_enable_vblank(struct drm_crtc *crtc) { struct vop *vop = to_vop(crtc); @@ -925,6 +939,9 @@ static int vop_crtc_enable_vblank(struct drm_crtc *crtc)
spin_unlock_irqrestore(&vop->irq_lock, flags);
vop->psr_enabled = false;
schedule_delayed_work(&vop->psr_work, msecs_to_jiffies(10));
return 0;
}
@@ -941,6 +958,9 @@ static void vop_crtc_disable_vblank(struct drm_crtc *crtc) VOP_INTR_SET_TYPE(vop, enable, FS_INTR, 0);
spin_unlock_irqrestore(&vop->irq_lock, flags);
vop->psr_enabled = true;
schedule_delayed_work(&vop->psr_work, msecs_to_jiffies(10));
}
static void vop_crtc_wait_for_update(struct drm_crtc *crtc) @@ -1582,6 +1602,10 @@ static int vop_bind(struct device *dev, struct device *master, void *data) return ret;
pm_runtime_enable(&pdev->dev);
vop->psr_enabled = false;
INIT_DELAYED_WORK(&vop->psr_work, vop_psr_work);
return 0;
}
-- 1.9.1
Sean,
On 07/02/2016 02:00 AM, Sean Paul wrote:
On Fri, Jul 1, 2016 at 5:19 AM, Yakir Yang ykk@rock-chips.com wrote:
The PSR driver have exported four symbols for specific device driver:
- rockchip_drm_psr_register()
- rockchip_drm_psr_unregister()
- rockchip_drm_psr_enable()
- rockchip_drm_psr_disable()
- rockchip_drm_psr_flush()
Encoder driver should call the register/unregister interfaces to hook itself into common PSR driver, encoder have implement the 'psr_set' callback which use the set PSR state in hardware side.
Crtc driver would call the enable/disable interfaces when vblank is enable/disable, after that the common PSR driver would call the encoder registered callback to set the PSR state.
This feels overly complicated. It seems like you could cut out a bunch of code by just coding the psr functions into vop and analogix_dp-rockchip. I suppose the only reason to keep it abstracted would be if you plan on supporting psr in a different encoder or crtc in rockchip, or if you're planning on moving this into drm core.
I split the psr code into a separate driver, cause it seems make things more clearly.
'rockchip_drm_vop.c' need to call the PSR enable/disable interfaces, and 'rockchip_drm_drv.c' need to call the PSR flush interface. If I put the whole psr code into analogix_dp-rockchip driver, then the Rockchip drm core driver would tie to analogix_dp-rockchip driver very deeply, and hard to expand if there are two PSR device in further.
Perhaps others will disagree with this sentiment and this is the right thing to do.
Fb driver would call the flush interface in 'fb->dirty' callback, this helper function would force all PSR enabled encoders to exit from PSR for 3 seconds.
Signed-off-by: Yakir Yang ykk@rock-chips.com
Changes in v3:
- split the psr flow into an common abstracted PSR driver
- implement the 'fb->dirty' callback function (Daniel)
- avoid to use notify to acqiure for vact event (Daniel)
- remove psr_active() callback which introduce in v2
Changes in v2: None
drivers/gpu/drm/rockchip/Makefile | 2 +- drivers/gpu/drm/rockchip/rockchip_drm_fb.c | 12 ++ drivers/gpu/drm/rockchip/rockchip_drm_psr.c | 200 ++++++++++++++++++++++++++++ drivers/gpu/drm/rockchip/rockchip_drm_psr.h | 12 ++ drivers/gpu/drm/rockchip/rockchip_drm_vop.c | 24 ++++ 5 files changed, 249 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_psr.c create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_psr.h
diff --git a/drivers/gpu/drm/rockchip/Makefile b/drivers/gpu/drm/rockchip/Makefile index 05d0713..9746365 100644 --- a/drivers/gpu/drm/rockchip/Makefile +++ b/drivers/gpu/drm/rockchip/Makefile @@ -3,7 +3,7 @@ # Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher.
rockchipdrm-y := rockchip_drm_drv.o rockchip_drm_fb.o \
rockchip_drm_gem.o rockchip_drm_vop.o
rockchip_drm_gem.o rockchip_drm_psr.o rockchip_drm_vop.o
rockchipdrm-$(CONFIG_DRM_FBDEV_EMULATION) += rockchip_drm_fbdev.o
obj-$(CONFIG_ROCKCHIP_ANALOGIX_DP) += analogix_dp-rockchip.o
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fb.c b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c index 20f12bc..0fec18f 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_fb.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c @@ -21,6 +21,7 @@
#include "rockchip_drm_drv.h" #include "rockchip_drm_gem.h" +#include "rockchip_drm_psr.h"
#define to_rockchip_fb(x) container_of(x, struct rockchip_drm_fb, fb)
@@ -66,9 +67,20 @@ static int rockchip_drm_fb_create_handle(struct drm_framebuffer *fb, rockchip_fb->obj[0], handle); }
+static int rockchip_drm_fb_dirty(struct drm_framebuffer *fb,
struct drm_file *file,
unsigned int flags, unsigned int color,
struct drm_clip_rect *clips,
unsigned int num_clips)
+{
rockchip_drm_psr_flush();
return 0;
+}
static const struct drm_framebuffer_funcs rockchip_drm_fb_funcs = { .destroy = rockchip_drm_fb_destroy, .create_handle = rockchip_drm_fb_create_handle,
.dirty = rockchip_drm_fb_dirty,
};
static struct rockchip_drm_fb *
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_psr.c b/drivers/gpu/drm/rockchip/rockchip_drm_psr.c new file mode 100644 index 0000000..c044443 --- /dev/null +++ b/drivers/gpu/drm/rockchip/rockchip_drm_psr.c @@ -0,0 +1,200 @@ +#include <drm/drm_crtc_helper.h>
+#include "rockchip_drm_psr.h"
+#define PSR_FLUSH_TIMEOUT msecs_to_jiffies(3000) /* 3 seconds */
+static LIST_HEAD(psr_list); +static DEFINE_MUTEX(psr_list_mutex);
I'm not crazy about these globals. Perhaps you can initialize them with the rockchip driver and tuck them in a driver-level struct (rockchip_drm_private or something).
Okay, done.
+enum psr_state {
PSR_FLUSH,
PSR_ENABLE,
PSR_DISABLE,
+};
+struct psr_drv {
struct list_head list;
enum psr_state state;
struct mutex state_mutex;
struct timer_list flush_timer;
struct drm_encoder *encoder;
int (*set)(struct drm_encoder *encoder, bool enable);
+};
+static struct psr_drv *find_psr_by_crtc(struct drm_crtc *crtc) +{
struct psr_drv *psr;
mutex_lock(&psr_list_mutex);
list_for_each_entry(psr, &psr_list, list) {
if (psr->encoder->crtc == crtc) {
mutex_unlock(&psr_list_mutex);
return psr;
}
}
mutex_unlock(&psr_list_mutex);
return ERR_PTR(-ENODEV);
+}
+static void psr_enable(struct psr_drv *psr) +{
if (psr->state == PSR_ENABLE)
return;
Should you be worried about races by accessing this outside of the state mutex?
Yes, this should under the protect of state_mutex.
mutex_lock(&psr->state_mutex);
psr->state = PSR_ENABLE;
psr->set(psr->encoder, true);
mutex_unlock(&psr->state_mutex);
+}
+static void psr_disable(struct psr_drv *psr) +{
if (psr->state == PSR_DISABLE)
return;
mutex_lock(&psr->state_mutex);
psr->state = PSR_DISABLE;
psr->set(psr->encoder, false);
mutex_unlock(&psr->state_mutex);
+}
+static void psr_flush_handler(unsigned long data) +{
struct psr_drv *psr = (struct psr_drv *)data;
if (!psr || psr->state != PSR_FLUSH)
return;
psr_enable(psr);
+}
+/**
- rockchip_drm_psr_enable - enable the encoder PSR which bind to given CRTC
- @crtc: CRTC to obtain the PSR encoder
- Returns:
- Zero on success, negative errno on failure.
- */
+int rockchip_drm_psr_enable(struct drm_crtc *crtc) +{
struct psr_drv *psr = find_psr_by_crtc(crtc);
if (IS_ERR(psr))
return PTR_ERR(psr);
psr_enable(psr);
return 0;
+} +EXPORT_SYMBOL(rockchip_drm_psr_enable);
+/**
- rockchip_drm_psr_disable - disable the encoder PSR which bind to given CRTC
- @crtc: CRTC to obtain the PSR encoder
- Returns:
- Zero on success, negative errno on failure.
- */
+int rockchip_drm_psr_disable(struct drm_crtc *crtc) +{
struct psr_drv *psr = find_psr_by_crtc(crtc);
if (IS_ERR(psr))
return PTR_ERR(psr);
psr_disable(psr);
return 0;
+} +EXPORT_SYMBOL(rockchip_drm_psr_disable);
+/**
- rockchip_drm_psr_flush - force to flush all registered PSR encoders
- Disable the PSR function for all registered encoders, and then enable the
- PSR function back after 5 second. If encoder PSR state have been changed
s/5 second/PSR_FLUSH_TIMEOUT/
Done
- during flush time, then keep the state no change after flush timeout.
- Returns:
- Zero on success, negative errno on failure.
- */
+void rockchip_drm_psr_flush(void) +{
struct psr_drv *psr;
mutex_lock(&psr_list_mutex);
list_for_each_entry(psr, &psr_list, list) {
if (psr->state == PSR_DISABLE)
continue;
mod_timer(&psr->flush_timer,
round_jiffies_up(jiffies + PSR_FLUSH_TIMEOUT));
psr_disable(psr);
psr->state = PSR_FLUSH;
This is set outside of state_mutex, is that intentional?
No, I just don't want to create another interface like psr_flush(). But it's a bad idea to let 'state' change outside the protect of 'state_mutex'.
Thanks, - Yakir
}
mutex_unlock(&psr_list_mutex);
+} +EXPORT_SYMBOL(rockchip_drm_psr_flush);
+/**
- rockchip_drm_psr_register - register encoder to psr driver
- @encoder: encoder that obtain the PSR function
- @psr_set: call back to set PSR state
- Returns:
- Zero on success, negative errno on failure.
- */
+int rockchip_drm_psr_register(struct drm_encoder *encoder,
int (*psr_set)(struct drm_encoder *, bool enable))
+{
struct psr_drv *psr;
if (!encoder || !psr_set)
return -EINVAL;
psr = kzalloc(sizeof(struct psr_drv), GFP_KERNEL);
if (!psr)
return -ENOMEM;
setup_timer(&psr->flush_timer, psr_flush_handler, (unsigned long)psr);
mutex_init(&psr->state_mutex);
psr->state = PSR_DISABLE;
psr->encoder = encoder;
psr->set = psr_set;
mutex_lock(&psr_list_mutex);
list_add_tail(&psr->list, &psr_list);
mutex_unlock(&psr_list_mutex);
return 0;
+} +EXPORT_SYMBOL(rockchip_drm_psr_register);
+/**
- rockchip_drm_psr_unregister - unregister encoder to psr driver
- @encoder: encoder that obtain the PSR function
- @psr_set: call back to set PSR state
- Returns:
- Zero on success, negative errno on failure.
- */
+void rockchip_drm_psr_unregister(struct drm_encoder *encoder) +{
struct psr_drv *psr;
mutex_lock(&psr_list_mutex);
list_for_each_entry(psr, &psr_list, list) {
if (psr->encoder == encoder) {
del_timer(&psr->flush_timer);
list_del(&psr->list);
kfree(psr);
}
}
mutex_unlock(&psr_list_mutex);
+} +EXPORT_SYMBOL(rockchip_drm_psr_unregister); diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_psr.h b/drivers/gpu/drm/rockchip/rockchip_drm_psr.h new file mode 100644 index 0000000..622f605 --- /dev/null +++ b/drivers/gpu/drm/rockchip/rockchip_drm_psr.h @@ -0,0 +1,12 @@ +#ifndef __ROCKCHIP_DRM_PSR___ +#define __ROCKCHIP_DRM_PSR___
+void rockchip_drm_psr_flush(void); +int rockchip_drm_psr_enable(struct drm_crtc *crtc); +int rockchip_drm_psr_disable(struct drm_crtc *crtc);
+int rockchip_drm_psr_register(struct drm_encoder *encoder,
int (*psr_set)(struct drm_encoder *, bool enable));
+void rockchip_drm_psr_unregister(struct drm_encoder *encoder);
+#endif /* __ROCKCHIP_DRM_PSR__ */ diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c index cd3cac5..3c6dfc5 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c @@ -34,6 +34,7 @@ #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"
#define __REG_SET_RELAXED(x, off, mask, shift, v, write_mask) \ @@ -121,6 +122,9 @@ struct vop { /* protected by dev->event_lock */ struct drm_pending_vblank_event *event;
bool psr_enabled;
struct delayed_work psr_work;
struct completion line_flag_completion; const struct vop_data *data;
@@ -911,6 +915,16 @@ static const struct drm_plane_funcs vop_plane_funcs = { .atomic_destroy_state = vop_atomic_plane_destroy_state, };
+static void vop_psr_work(struct work_struct *work) +{
struct vop *vop = container_of(work, typeof(*vop), psr_work.work);
if (vop->psr_enabled)
rockchip_drm_psr_enable(&vop->crtc);
else
rockchip_drm_psr_disable(&vop->crtc);
+}
- static int vop_crtc_enable_vblank(struct drm_crtc *crtc) { struct vop *vop = to_vop(crtc);
@@ -925,6 +939,9 @@ static int vop_crtc_enable_vblank(struct drm_crtc *crtc)
spin_unlock_irqrestore(&vop->irq_lock, flags);
vop->psr_enabled = false;
schedule_delayed_work(&vop->psr_work, msecs_to_jiffies(10));
}return 0;
@@ -941,6 +958,9 @@ static void vop_crtc_disable_vblank(struct drm_crtc *crtc) VOP_INTR_SET_TYPE(vop, enable, FS_INTR, 0);
spin_unlock_irqrestore(&vop->irq_lock, flags);
vop->psr_enabled = true;
schedule_delayed_work(&vop->psr_work, msecs_to_jiffies(10));
}
static void vop_crtc_wait_for_update(struct drm_crtc *crtc)
@@ -1582,6 +1602,10 @@ static int vop_bind(struct device *dev, struct device *master, void *data) return ret;
pm_runtime_enable(&pdev->dev);
vop->psr_enabled = false;
INIT_DELAYED_WORK(&vop->psr_work, vop_psr_work);
}return 0;
-- 1.9.1
On Fri, Jul 01, 2016 at 02:00:00PM -0400, Sean Paul wrote:
On Fri, Jul 1, 2016 at 5:19 AM, Yakir Yang ykk@rock-chips.com wrote:
The PSR driver have exported four symbols for specific device driver:
- rockchip_drm_psr_register()
- rockchip_drm_psr_unregister()
- rockchip_drm_psr_enable()
- rockchip_drm_psr_disable()
- rockchip_drm_psr_flush()
Encoder driver should call the register/unregister interfaces to hook itself into common PSR driver, encoder have implement the 'psr_set' callback which use the set PSR state in hardware side.
Crtc driver would call the enable/disable interfaces when vblank is enable/disable, after that the common PSR driver would call the encoder registered callback to set the PSR state.
This feels overly complicated. It seems like you could cut out a bunch of code by just coding the psr functions into vop and analogix_dp-rockchip. I suppose the only reason to keep it abstracted would be if you plan on supporting psr in a different encoder or crtc in rockchip, or if you're planning on moving this into drm core.
Agreed on the layers of indirection. Also, you end up with 3 delayed timers in total: - defio timer from fbdev emulation - timer in this abstraction - delayed work in the psr backend driver
I'd cut out at least the middle one.
But since this seems to correctly use the ->dirty callback it gets my Ack either way ;-)
Cheers, Daniel
Perhaps others will disagree with this sentiment and this is the right thing to do.
Fb driver would call the flush interface in 'fb->dirty' callback, this helper function would force all PSR enabled encoders to exit from PSR for 3 seconds.
Signed-off-by: Yakir Yang ykk@rock-chips.com
Changes in v3:
- split the psr flow into an common abstracted PSR driver
- implement the 'fb->dirty' callback function (Daniel)
- avoid to use notify to acqiure for vact event (Daniel)
- remove psr_active() callback which introduce in v2
Changes in v2: None
drivers/gpu/drm/rockchip/Makefile | 2 +- drivers/gpu/drm/rockchip/rockchip_drm_fb.c | 12 ++ drivers/gpu/drm/rockchip/rockchip_drm_psr.c | 200 ++++++++++++++++++++++++++++ drivers/gpu/drm/rockchip/rockchip_drm_psr.h | 12 ++ drivers/gpu/drm/rockchip/rockchip_drm_vop.c | 24 ++++ 5 files changed, 249 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_psr.c create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_psr.h
diff --git a/drivers/gpu/drm/rockchip/Makefile b/drivers/gpu/drm/rockchip/Makefile index 05d0713..9746365 100644 --- a/drivers/gpu/drm/rockchip/Makefile +++ b/drivers/gpu/drm/rockchip/Makefile @@ -3,7 +3,7 @@ # Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher.
rockchipdrm-y := rockchip_drm_drv.o rockchip_drm_fb.o \
rockchip_drm_gem.o rockchip_drm_vop.o
rockchip_drm_gem.o rockchip_drm_psr.o rockchip_drm_vop.o
rockchipdrm-$(CONFIG_DRM_FBDEV_EMULATION) += rockchip_drm_fbdev.o
obj-$(CONFIG_ROCKCHIP_ANALOGIX_DP) += analogix_dp-rockchip.o diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fb.c b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c index 20f12bc..0fec18f 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_fb.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c @@ -21,6 +21,7 @@
#include "rockchip_drm_drv.h" #include "rockchip_drm_gem.h" +#include "rockchip_drm_psr.h"
#define to_rockchip_fb(x) container_of(x, struct rockchip_drm_fb, fb)
@@ -66,9 +67,20 @@ static int rockchip_drm_fb_create_handle(struct drm_framebuffer *fb, rockchip_fb->obj[0], handle); }
+static int rockchip_drm_fb_dirty(struct drm_framebuffer *fb,
struct drm_file *file,
unsigned int flags, unsigned int color,
struct drm_clip_rect *clips,
unsigned int num_clips)
+{
rockchip_drm_psr_flush();
return 0;
+}
static const struct drm_framebuffer_funcs rockchip_drm_fb_funcs = { .destroy = rockchip_drm_fb_destroy, .create_handle = rockchip_drm_fb_create_handle,
.dirty = rockchip_drm_fb_dirty,
};
static struct rockchip_drm_fb * diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_psr.c b/drivers/gpu/drm/rockchip/rockchip_drm_psr.c new file mode 100644 index 0000000..c044443 --- /dev/null +++ b/drivers/gpu/drm/rockchip/rockchip_drm_psr.c @@ -0,0 +1,200 @@ +#include <drm/drm_crtc_helper.h>
+#include "rockchip_drm_psr.h"
+#define PSR_FLUSH_TIMEOUT msecs_to_jiffies(3000) /* 3 seconds */
+static LIST_HEAD(psr_list); +static DEFINE_MUTEX(psr_list_mutex);
I'm not crazy about these globals. Perhaps you can initialize them with the rockchip driver and tuck them in a driver-level struct (rockchip_drm_private or something).
+enum psr_state {
PSR_FLUSH,
PSR_ENABLE,
PSR_DISABLE,
+};
+struct psr_drv {
struct list_head list;
enum psr_state state;
struct mutex state_mutex;
struct timer_list flush_timer;
struct drm_encoder *encoder;
int (*set)(struct drm_encoder *encoder, bool enable);
+};
+static struct psr_drv *find_psr_by_crtc(struct drm_crtc *crtc) +{
struct psr_drv *psr;
mutex_lock(&psr_list_mutex);
list_for_each_entry(psr, &psr_list, list) {
if (psr->encoder->crtc == crtc) {
mutex_unlock(&psr_list_mutex);
return psr;
}
}
mutex_unlock(&psr_list_mutex);
return ERR_PTR(-ENODEV);
+}
+static void psr_enable(struct psr_drv *psr) +{
if (psr->state == PSR_ENABLE)
return;
Should you be worried about races by accessing this outside of the state mutex?
mutex_lock(&psr->state_mutex);
psr->state = PSR_ENABLE;
psr->set(psr->encoder, true);
mutex_unlock(&psr->state_mutex);
+}
+static void psr_disable(struct psr_drv *psr) +{
if (psr->state == PSR_DISABLE)
return;
mutex_lock(&psr->state_mutex);
psr->state = PSR_DISABLE;
psr->set(psr->encoder, false);
mutex_unlock(&psr->state_mutex);
+}
+static void psr_flush_handler(unsigned long data) +{
struct psr_drv *psr = (struct psr_drv *)data;
if (!psr || psr->state != PSR_FLUSH)
return;
psr_enable(psr);
+}
+/**
- rockchip_drm_psr_enable - enable the encoder PSR which bind to given CRTC
- @crtc: CRTC to obtain the PSR encoder
- Returns:
- Zero on success, negative errno on failure.
- */
+int rockchip_drm_psr_enable(struct drm_crtc *crtc) +{
struct psr_drv *psr = find_psr_by_crtc(crtc);
if (IS_ERR(psr))
return PTR_ERR(psr);
psr_enable(psr);
return 0;
+} +EXPORT_SYMBOL(rockchip_drm_psr_enable);
+/**
- rockchip_drm_psr_disable - disable the encoder PSR which bind to given CRTC
- @crtc: CRTC to obtain the PSR encoder
- Returns:
- Zero on success, negative errno on failure.
- */
+int rockchip_drm_psr_disable(struct drm_crtc *crtc) +{
struct psr_drv *psr = find_psr_by_crtc(crtc);
if (IS_ERR(psr))
return PTR_ERR(psr);
psr_disable(psr);
return 0;
+} +EXPORT_SYMBOL(rockchip_drm_psr_disable);
+/**
- rockchip_drm_psr_flush - force to flush all registered PSR encoders
- Disable the PSR function for all registered encoders, and then enable the
- PSR function back after 5 second. If encoder PSR state have been changed
s/5 second/PSR_FLUSH_TIMEOUT/
- during flush time, then keep the state no change after flush timeout.
- Returns:
- Zero on success, negative errno on failure.
- */
+void rockchip_drm_psr_flush(void) +{
struct psr_drv *psr;
mutex_lock(&psr_list_mutex);
list_for_each_entry(psr, &psr_list, list) {
if (psr->state == PSR_DISABLE)
continue;
mod_timer(&psr->flush_timer,
round_jiffies_up(jiffies + PSR_FLUSH_TIMEOUT));
psr_disable(psr);
psr->state = PSR_FLUSH;
This is set outside of state_mutex, is that intentional?
}
mutex_unlock(&psr_list_mutex);
+} +EXPORT_SYMBOL(rockchip_drm_psr_flush);
+/**
- rockchip_drm_psr_register - register encoder to psr driver
- @encoder: encoder that obtain the PSR function
- @psr_set: call back to set PSR state
- Returns:
- Zero on success, negative errno on failure.
- */
+int rockchip_drm_psr_register(struct drm_encoder *encoder,
int (*psr_set)(struct drm_encoder *, bool enable))
+{
struct psr_drv *psr;
if (!encoder || !psr_set)
return -EINVAL;
psr = kzalloc(sizeof(struct psr_drv), GFP_KERNEL);
if (!psr)
return -ENOMEM;
setup_timer(&psr->flush_timer, psr_flush_handler, (unsigned long)psr);
mutex_init(&psr->state_mutex);
psr->state = PSR_DISABLE;
psr->encoder = encoder;
psr->set = psr_set;
mutex_lock(&psr_list_mutex);
list_add_tail(&psr->list, &psr_list);
mutex_unlock(&psr_list_mutex);
return 0;
+} +EXPORT_SYMBOL(rockchip_drm_psr_register);
+/**
- rockchip_drm_psr_unregister - unregister encoder to psr driver
- @encoder: encoder that obtain the PSR function
- @psr_set: call back to set PSR state
- Returns:
- Zero on success, negative errno on failure.
- */
+void rockchip_drm_psr_unregister(struct drm_encoder *encoder) +{
struct psr_drv *psr;
mutex_lock(&psr_list_mutex);
list_for_each_entry(psr, &psr_list, list) {
if (psr->encoder == encoder) {
del_timer(&psr->flush_timer);
list_del(&psr->list);
kfree(psr);
}
}
mutex_unlock(&psr_list_mutex);
+} +EXPORT_SYMBOL(rockchip_drm_psr_unregister); diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_psr.h b/drivers/gpu/drm/rockchip/rockchip_drm_psr.h new file mode 100644 index 0000000..622f605 --- /dev/null +++ b/drivers/gpu/drm/rockchip/rockchip_drm_psr.h @@ -0,0 +1,12 @@ +#ifndef __ROCKCHIP_DRM_PSR___ +#define __ROCKCHIP_DRM_PSR___
+void rockchip_drm_psr_flush(void); +int rockchip_drm_psr_enable(struct drm_crtc *crtc); +int rockchip_drm_psr_disable(struct drm_crtc *crtc);
+int rockchip_drm_psr_register(struct drm_encoder *encoder,
int (*psr_set)(struct drm_encoder *, bool enable));
+void rockchip_drm_psr_unregister(struct drm_encoder *encoder);
+#endif /* __ROCKCHIP_DRM_PSR__ */ diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c index cd3cac5..3c6dfc5 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c @@ -34,6 +34,7 @@ #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"
#define __REG_SET_RELAXED(x, off, mask, shift, v, write_mask) \ @@ -121,6 +122,9 @@ struct vop { /* protected by dev->event_lock */ struct drm_pending_vblank_event *event;
bool psr_enabled;
struct delayed_work psr_work;
struct completion line_flag_completion; const struct vop_data *data;
@@ -911,6 +915,16 @@ static const struct drm_plane_funcs vop_plane_funcs = { .atomic_destroy_state = vop_atomic_plane_destroy_state, };
+static void vop_psr_work(struct work_struct *work) +{
struct vop *vop = container_of(work, typeof(*vop), psr_work.work);
if (vop->psr_enabled)
rockchip_drm_psr_enable(&vop->crtc);
else
rockchip_drm_psr_disable(&vop->crtc);
+}
static int vop_crtc_enable_vblank(struct drm_crtc *crtc) { struct vop *vop = to_vop(crtc); @@ -925,6 +939,9 @@ static int vop_crtc_enable_vblank(struct drm_crtc *crtc)
spin_unlock_irqrestore(&vop->irq_lock, flags);
vop->psr_enabled = false;
schedule_delayed_work(&vop->psr_work, msecs_to_jiffies(10));
return 0;
}
@@ -941,6 +958,9 @@ static void vop_crtc_disable_vblank(struct drm_crtc *crtc) VOP_INTR_SET_TYPE(vop, enable, FS_INTR, 0);
spin_unlock_irqrestore(&vop->irq_lock, flags);
vop->psr_enabled = true;
schedule_delayed_work(&vop->psr_work, msecs_to_jiffies(10));
}
static void vop_crtc_wait_for_update(struct drm_crtc *crtc) @@ -1582,6 +1602,10 @@ static int vop_bind(struct device *dev, struct device *master, void *data) return ret;
pm_runtime_enable(&pdev->dev);
vop->psr_enabled = false;
INIT_DELAYED_WORK(&vop->psr_work, vop_psr_work);
return 0;
}
-- 1.9.1
On Tue, Jul 12, 2016 at 9:38 PM, Daniel Vetter daniel@ffwll.ch wrote:
On Fri, Jul 01, 2016 at 02:00:00PM -0400, Sean Paul wrote:
On Fri, Jul 1, 2016 at 5:19 AM, Yakir Yang ykk@rock-chips.com wrote:
The PSR driver have exported four symbols for specific device driver:
- rockchip_drm_psr_register()
- rockchip_drm_psr_unregister()
- rockchip_drm_psr_enable()
- rockchip_drm_psr_disable()
- rockchip_drm_psr_flush()
Encoder driver should call the register/unregister interfaces to hook itself into common PSR driver, encoder have implement the 'psr_set' callback which use the set PSR state in hardware side.
Crtc driver would call the enable/disable interfaces when vblank is enable/disable, after that the common PSR driver would call the encoder registered callback to set the PSR state.
This feels overly complicated. It seems like you could cut out a bunch of code by just coding the psr functions into vop and analogix_dp-rockchip. I suppose the only reason to keep it abstracted would be if you plan on supporting psr in a different encoder or crtc in rockchip, or if you're planning on moving this into drm core.
Agreed on the layers of indirection. Also, you end up with 3 delayed timers in total:
- defio timer from fbdev emulation
- timer in this abstraction
- delayed work in the psr backend driver
Maybe I'm missing something obvious (don't know how the PSR is implemented in hardware or in other drivers), but why do we need all these timers? Couldn't we just trigger a fake page flip on fb dirty call?
Best regards, Tomasz
On Wed, Jul 13, 2016 at 12:10:13AM +0900, Tomasz Figa wrote:
On Tue, Jul 12, 2016 at 9:38 PM, Daniel Vetter daniel@ffwll.ch wrote:
On Fri, Jul 01, 2016 at 02:00:00PM -0400, Sean Paul wrote:
On Fri, Jul 1, 2016 at 5:19 AM, Yakir Yang ykk@rock-chips.com wrote:
The PSR driver have exported four symbols for specific device driver:
- rockchip_drm_psr_register()
- rockchip_drm_psr_unregister()
- rockchip_drm_psr_enable()
- rockchip_drm_psr_disable()
- rockchip_drm_psr_flush()
Encoder driver should call the register/unregister interfaces to hook itself into common PSR driver, encoder have implement the 'psr_set' callback which use the set PSR state in hardware side.
Crtc driver would call the enable/disable interfaces when vblank is enable/disable, after that the common PSR driver would call the encoder registered callback to set the PSR state.
This feels overly complicated. It seems like you could cut out a bunch of code by just coding the psr functions into vop and analogix_dp-rockchip. I suppose the only reason to keep it abstracted would be if you plan on supporting psr in a different encoder or crtc in rockchip, or if you're planning on moving this into drm core.
Agreed on the layers of indirection. Also, you end up with 3 delayed timers in total:
- defio timer from fbdev emulation
- timer in this abstraction
- delayed work in the psr backend driver
Maybe I'm missing something obvious (don't know how the PSR is implemented in hardware or in other drivers), but why do we need all these timers? Couldn't we just trigger a fake page flip on fb dirty call?
Page flips are ratelimited to vrefresh, and not all drivers support it. The other issue is that dirty ioctl supports dirty rectangles, but page flip is still a full-screen upload. We discussed adding a dirty rectangle to the kms properties to fix this gap, but until that's done I don't think it makes much sense to remap dirty to an (atomic) page flip. One small technicality is that dirty works on framebuffers, but page flips (especially atomic ones) work on crtcs. That makes the remapping a notch more tricky.
But in principle, fully agreed. And once we have atomic dirty rectangles we could type up a small helper that drivers can plug into the dirty ioctl. Since new properties requires new userspace we could just track the dirty rectangle internally to get this helper landed, if there's interested for that. I'll certainly be happy to review patches. -Daniel
Daniel,
On 07/12/2016 08:38 PM, Daniel Vetter wrote:
On Fri, Jul 01, 2016 at 02:00:00PM -0400, Sean Paul wrote:
On Fri, Jul 1, 2016 at 5:19 AM, Yakir Yang ykk@rock-chips.com wrote:
The PSR driver have exported four symbols for specific device driver:
- rockchip_drm_psr_register()
- rockchip_drm_psr_unregister()
- rockchip_drm_psr_enable()
- rockchip_drm_psr_disable()
- rockchip_drm_psr_flush()
Encoder driver should call the register/unregister interfaces to hook itself into common PSR driver, encoder have implement the 'psr_set' callback which use the set PSR state in hardware side.
Crtc driver would call the enable/disable interfaces when vblank is enable/disable, after that the common PSR driver would call the encoder registered callback to set the PSR state.
This feels overly complicated. It seems like you could cut out a bunch of code by just coding the psr functions into vop and analogix_dp-rockchip. I suppose the only reason to keep it abstracted would be if you plan on supporting psr in a different encoder or crtc in rockchip, or if you're planning on moving this into drm core.
Agreed on the layers of indirection. Also, you end up with 3 delayed timers in total:
- defio timer from fbdev emulation
- timer in this abstraction
- delayed work in the psr backend driver
I'd cut out at least the middle one.
But since this seems to correctly use the ->dirty callback it gets my Ack either way ;-)
Aha, thanks :-D
- Yakir
Cheers, Daniel
Perhaps others will disagree with this sentiment and this is the right thing to do.
Fb driver would call the flush interface in 'fb->dirty' callback, this helper function would force all PSR enabled encoders to exit from PSR for 3 seconds.
Signed-off-by: Yakir Yang ykk@rock-chips.com
Changes in v3:
- split the psr flow into an common abstracted PSR driver
- implement the 'fb->dirty' callback function (Daniel)
- avoid to use notify to acqiure for vact event (Daniel)
- remove psr_active() callback which introduce in v2
Changes in v2: None
drivers/gpu/drm/rockchip/Makefile | 2 +- drivers/gpu/drm/rockchip/rockchip_drm_fb.c | 12 ++ drivers/gpu/drm/rockchip/rockchip_drm_psr.c | 200 ++++++++++++++++++++++++++++ drivers/gpu/drm/rockchip/rockchip_drm_psr.h | 12 ++ drivers/gpu/drm/rockchip/rockchip_drm_vop.c | 24 ++++ 5 files changed, 249 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_psr.c create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_psr.h
diff --git a/drivers/gpu/drm/rockchip/Makefile b/drivers/gpu/drm/rockchip/Makefile index 05d0713..9746365 100644 --- a/drivers/gpu/drm/rockchip/Makefile +++ b/drivers/gpu/drm/rockchip/Makefile @@ -3,7 +3,7 @@ # Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher.
rockchipdrm-y := rockchip_drm_drv.o rockchip_drm_fb.o \
rockchip_drm_gem.o rockchip_drm_vop.o
rockchip_drm_gem.o rockchip_drm_psr.o rockchip_drm_vop.o
rockchipdrm-$(CONFIG_DRM_FBDEV_EMULATION) += rockchip_drm_fbdev.o
obj-$(CONFIG_ROCKCHIP_ANALOGIX_DP) += analogix_dp-rockchip.o
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fb.c b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c index 20f12bc..0fec18f 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_fb.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c @@ -21,6 +21,7 @@
#include "rockchip_drm_drv.h" #include "rockchip_drm_gem.h" +#include "rockchip_drm_psr.h"
#define to_rockchip_fb(x) container_of(x, struct rockchip_drm_fb, fb)
@@ -66,9 +67,20 @@ static int rockchip_drm_fb_create_handle(struct drm_framebuffer *fb, rockchip_fb->obj[0], handle); }
+static int rockchip_drm_fb_dirty(struct drm_framebuffer *fb,
struct drm_file *file,
unsigned int flags, unsigned int color,
struct drm_clip_rect *clips,
unsigned int num_clips)
+{
rockchip_drm_psr_flush();
return 0;
+}
static const struct drm_framebuffer_funcs rockchip_drm_fb_funcs = { .destroy = rockchip_drm_fb_destroy, .create_handle = rockchip_drm_fb_create_handle,
.dirty = rockchip_drm_fb_dirty,
};
static struct rockchip_drm_fb *
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_psr.c b/drivers/gpu/drm/rockchip/rockchip_drm_psr.c new file mode 100644 index 0000000..c044443 --- /dev/null +++ b/drivers/gpu/drm/rockchip/rockchip_drm_psr.c @@ -0,0 +1,200 @@ +#include <drm/drm_crtc_helper.h>
+#include "rockchip_drm_psr.h"
+#define PSR_FLUSH_TIMEOUT msecs_to_jiffies(3000) /* 3 seconds */
+static LIST_HEAD(psr_list); +static DEFINE_MUTEX(psr_list_mutex);
I'm not crazy about these globals. Perhaps you can initialize them with the rockchip driver and tuck them in a driver-level struct (rockchip_drm_private or something).
+enum psr_state {
PSR_FLUSH,
PSR_ENABLE,
PSR_DISABLE,
+};
+struct psr_drv {
struct list_head list;
enum psr_state state;
struct mutex state_mutex;
struct timer_list flush_timer;
struct drm_encoder *encoder;
int (*set)(struct drm_encoder *encoder, bool enable);
+};
+static struct psr_drv *find_psr_by_crtc(struct drm_crtc *crtc) +{
struct psr_drv *psr;
mutex_lock(&psr_list_mutex);
list_for_each_entry(psr, &psr_list, list) {
if (psr->encoder->crtc == crtc) {
mutex_unlock(&psr_list_mutex);
return psr;
}
}
mutex_unlock(&psr_list_mutex);
return ERR_PTR(-ENODEV);
+}
+static void psr_enable(struct psr_drv *psr) +{
if (psr->state == PSR_ENABLE)
return;
Should you be worried about races by accessing this outside of the state mutex?
mutex_lock(&psr->state_mutex);
psr->state = PSR_ENABLE;
psr->set(psr->encoder, true);
mutex_unlock(&psr->state_mutex);
+}
+static void psr_disable(struct psr_drv *psr) +{
if (psr->state == PSR_DISABLE)
return;
mutex_lock(&psr->state_mutex);
psr->state = PSR_DISABLE;
psr->set(psr->encoder, false);
mutex_unlock(&psr->state_mutex);
+}
+static void psr_flush_handler(unsigned long data) +{
struct psr_drv *psr = (struct psr_drv *)data;
if (!psr || psr->state != PSR_FLUSH)
return;
psr_enable(psr);
+}
+/**
- rockchip_drm_psr_enable - enable the encoder PSR which bind to given CRTC
- @crtc: CRTC to obtain the PSR encoder
- Returns:
- Zero on success, negative errno on failure.
- */
+int rockchip_drm_psr_enable(struct drm_crtc *crtc) +{
struct psr_drv *psr = find_psr_by_crtc(crtc);
if (IS_ERR(psr))
return PTR_ERR(psr);
psr_enable(psr);
return 0;
+} +EXPORT_SYMBOL(rockchip_drm_psr_enable);
+/**
- rockchip_drm_psr_disable - disable the encoder PSR which bind to given CRTC
- @crtc: CRTC to obtain the PSR encoder
- Returns:
- Zero on success, negative errno on failure.
- */
+int rockchip_drm_psr_disable(struct drm_crtc *crtc) +{
struct psr_drv *psr = find_psr_by_crtc(crtc);
if (IS_ERR(psr))
return PTR_ERR(psr);
psr_disable(psr);
return 0;
+} +EXPORT_SYMBOL(rockchip_drm_psr_disable);
+/**
- rockchip_drm_psr_flush - force to flush all registered PSR encoders
- Disable the PSR function for all registered encoders, and then enable the
- PSR function back after 5 second. If encoder PSR state have been changed
s/5 second/PSR_FLUSH_TIMEOUT/
- during flush time, then keep the state no change after flush timeout.
- Returns:
- Zero on success, negative errno on failure.
- */
+void rockchip_drm_psr_flush(void) +{
struct psr_drv *psr;
mutex_lock(&psr_list_mutex);
list_for_each_entry(psr, &psr_list, list) {
if (psr->state == PSR_DISABLE)
continue;
mod_timer(&psr->flush_timer,
round_jiffies_up(jiffies + PSR_FLUSH_TIMEOUT));
psr_disable(psr);
psr->state = PSR_FLUSH;
This is set outside of state_mutex, is that intentional?
}
mutex_unlock(&psr_list_mutex);
+} +EXPORT_SYMBOL(rockchip_drm_psr_flush);
+/**
- rockchip_drm_psr_register - register encoder to psr driver
- @encoder: encoder that obtain the PSR function
- @psr_set: call back to set PSR state
- Returns:
- Zero on success, negative errno on failure.
- */
+int rockchip_drm_psr_register(struct drm_encoder *encoder,
int (*psr_set)(struct drm_encoder *, bool enable))
+{
struct psr_drv *psr;
if (!encoder || !psr_set)
return -EINVAL;
psr = kzalloc(sizeof(struct psr_drv), GFP_KERNEL);
if (!psr)
return -ENOMEM;
setup_timer(&psr->flush_timer, psr_flush_handler, (unsigned long)psr);
mutex_init(&psr->state_mutex);
psr->state = PSR_DISABLE;
psr->encoder = encoder;
psr->set = psr_set;
mutex_lock(&psr_list_mutex);
list_add_tail(&psr->list, &psr_list);
mutex_unlock(&psr_list_mutex);
return 0;
+} +EXPORT_SYMBOL(rockchip_drm_psr_register);
+/**
- rockchip_drm_psr_unregister - unregister encoder to psr driver
- @encoder: encoder that obtain the PSR function
- @psr_set: call back to set PSR state
- Returns:
- Zero on success, negative errno on failure.
- */
+void rockchip_drm_psr_unregister(struct drm_encoder *encoder) +{
struct psr_drv *psr;
mutex_lock(&psr_list_mutex);
list_for_each_entry(psr, &psr_list, list) {
if (psr->encoder == encoder) {
del_timer(&psr->flush_timer);
list_del(&psr->list);
kfree(psr);
}
}
mutex_unlock(&psr_list_mutex);
+} +EXPORT_SYMBOL(rockchip_drm_psr_unregister); diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_psr.h b/drivers/gpu/drm/rockchip/rockchip_drm_psr.h new file mode 100644 index 0000000..622f605 --- /dev/null +++ b/drivers/gpu/drm/rockchip/rockchip_drm_psr.h @@ -0,0 +1,12 @@ +#ifndef __ROCKCHIP_DRM_PSR___ +#define __ROCKCHIP_DRM_PSR___
+void rockchip_drm_psr_flush(void); +int rockchip_drm_psr_enable(struct drm_crtc *crtc); +int rockchip_drm_psr_disable(struct drm_crtc *crtc);
+int rockchip_drm_psr_register(struct drm_encoder *encoder,
int (*psr_set)(struct drm_encoder *, bool enable));
+void rockchip_drm_psr_unregister(struct drm_encoder *encoder);
+#endif /* __ROCKCHIP_DRM_PSR__ */ diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c index cd3cac5..3c6dfc5 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c @@ -34,6 +34,7 @@ #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"
#define __REG_SET_RELAXED(x, off, mask, shift, v, write_mask) \ @@ -121,6 +122,9 @@ struct vop { /* protected by dev->event_lock */ struct drm_pending_vblank_event *event;
bool psr_enabled;
struct delayed_work psr_work;
struct completion line_flag_completion; const struct vop_data *data;
@@ -911,6 +915,16 @@ static const struct drm_plane_funcs vop_plane_funcs = { .atomic_destroy_state = vop_atomic_plane_destroy_state, };
+static void vop_psr_work(struct work_struct *work) +{
struct vop *vop = container_of(work, typeof(*vop), psr_work.work);
if (vop->psr_enabled)
rockchip_drm_psr_enable(&vop->crtc);
else
rockchip_drm_psr_disable(&vop->crtc);
+}
- static int vop_crtc_enable_vblank(struct drm_crtc *crtc) { struct vop *vop = to_vop(crtc);
@@ -925,6 +939,9 @@ static int vop_crtc_enable_vblank(struct drm_crtc *crtc)
spin_unlock_irqrestore(&vop->irq_lock, flags);
vop->psr_enabled = false;
schedule_delayed_work(&vop->psr_work, msecs_to_jiffies(10));
}return 0;
@@ -941,6 +958,9 @@ static void vop_crtc_disable_vblank(struct drm_crtc *crtc) VOP_INTR_SET_TYPE(vop, enable, FS_INTR, 0);
spin_unlock_irqrestore(&vop->irq_lock, flags);
vop->psr_enabled = true;
schedule_delayed_work(&vop->psr_work, msecs_to_jiffies(10));
}
static void vop_crtc_wait_for_update(struct drm_crtc *crtc)
@@ -1582,6 +1602,10 @@ static int vop_bind(struct device *dev, struct device *master, void *data) return ret;
pm_runtime_enable(&pdev->dev);
vop->psr_enabled = false;
INIT_DELAYED_WORK(&vop->psr_work, vop_psr_work);
}return 0;
-- 1.9.1
The full name of PSR is Panel Self Refresh, panel device could refresh itself with the hardware framebuffer in panel, this would make lots of sense to save the power consumption.
This patch have exported two symbols for platform driver to implement the PSR function in hardware side: - analogix_dp_active_psr() - analogix_dp_inactive_psr()
Signed-off-by: Yakir Yang ykk@rock-chips.com --- Changes in v3: - split analogix_dp_enable_psr(), make it more clearly analogix_dp_detect_sink_psr() analogix_dp_enable_sink_psr() - remove some nosie register setting comments
Changes in v2: - introduce in v2, splite the common Analogix DP changes out
drivers/gpu/drm/bridge/analogix/analogix_dp_core.c | 64 ++++++++++++++++++++++ drivers/gpu/drm/bridge/analogix/analogix_dp_core.h | 4 ++ drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c | 54 ++++++++++++++++++ drivers/gpu/drm/bridge/analogix/analogix_dp_reg.h | 28 ++++++++++ include/drm/bridge/analogix_dp.h | 3 + 5 files changed, 153 insertions(+)
diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c index 32715da..b557097 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c @@ -97,6 +97,66 @@ static int analogix_dp_detect_hpd(struct analogix_dp_device *dp) return 0; }
+int analogix_dp_active_psr(struct device *dev) +{ + struct analogix_dp_device *dp = dev_get_drvdata(dev); + + if (!dp->psr_support) + return -EINVAL; + + analogix_dp_send_psr_spd(dp, EDP_VSC_PSR_STATE_ACTIVE | + EDP_VSC_PSR_CRC_VALUES_VALID); + return 0; +} +EXPORT_SYMBOL_GPL(analogix_dp_active_psr); + +int analogix_dp_inactive_psr(struct device *dev) +{ + struct analogix_dp_device *dp = dev_get_drvdata(dev); + + if (!dp->psr_support) + return -EINVAL; + + analogix_dp_send_psr_spd(dp, 0); + return 0; +} +EXPORT_SYMBOL_GPL(analogix_dp_inactive_psr); + +static bool analogix_dp_detect_sink_psr(struct analogix_dp_device *dp) +{ + unsigned char psr_version; + + analogix_dp_read_byte_from_dpcd(dp, DP_PSR_SUPPORT, &psr_version); + dev_info(dp->dev, "Panel PSR version : %x\n", psr_version); + + return (psr_version & DP_PSR_IS_SUPPORTED) ? true : false; +} + +static int analogix_dp_enable_sink_psr(struct analogix_dp_device *dp) +{ + unsigned char psr_en; + + /* Disable psr function */ + analogix_dp_read_byte_from_dpcd(dp, DP_PSR_EN_CFG, &psr_en); + psr_en &= ~DP_PSR_ENABLE; + analogix_dp_write_byte_to_dpcd(dp, DP_PSR_EN_CFG, psr_en); + + /* Main-Link transmitter remains active during PSR active states */ + analogix_dp_read_byte_from_dpcd(dp, DP_PSR_EN_CFG, &psr_en); + psr_en = DP_PSR_MAIN_LINK_ACTIVE | DP_PSR_CRC_VERIFICATION; + analogix_dp_write_byte_to_dpcd(dp, DP_PSR_EN_CFG, psr_en); + + /* Enable psr function */ + analogix_dp_read_byte_from_dpcd(dp, DP_PSR_EN_CFG, &psr_en); + psr_en = DP_PSR_ENABLE | DP_PSR_MAIN_LINK_ACTIVE | + DP_PSR_CRC_VERIFICATION; + analogix_dp_write_byte_to_dpcd(dp, DP_PSR_EN_CFG, psr_en); + + analogix_dp_enable_psr_crc(dp); + + return 0; +} + static unsigned char analogix_dp_calc_edid_check_sum(unsigned char *edid_data) { int i; @@ -921,6 +981,10 @@ static void analogix_dp_commit(struct analogix_dp_device *dp)
/* Enable video */ analogix_dp_start_video(dp); + + dp->psr_support = analogix_dp_detect_sink_psr(dp); + if (dp->psr_support) + analogix_dp_enable_sink_psr(dp); }
int analogix_dp_get_modes(struct drm_connector *connector) diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h index b456380..6ca5dde 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h @@ -177,6 +177,7 @@ struct analogix_dp_device { int hpd_gpio; bool force_hpd; unsigned char edid[EDID_BLOCK_LENGTH * 2]; + bool psr_support;
struct analogix_dp_plat_data *plat_data; }; @@ -278,4 +279,7 @@ int analogix_dp_is_video_stream_on(struct analogix_dp_device *dp); void analogix_dp_config_video_slave_mode(struct analogix_dp_device *dp); void analogix_dp_enable_scrambling(struct analogix_dp_device *dp); void analogix_dp_disable_scrambling(struct analogix_dp_device *dp); +void analogix_dp_enable_psr_crc(struct analogix_dp_device *dp); +void analogix_dp_send_psr_spd(struct analogix_dp_device *dp, int db1); + #endif /* _ANALOGIX_DP_CORE_H */ diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c index 48030f0..e8372c7 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c @@ -1322,3 +1322,57 @@ void analogix_dp_disable_scrambling(struct analogix_dp_device *dp) reg |= SCRAMBLING_DISABLE; writel(reg, dp->reg_base + ANALOGIX_DP_TRAINING_PTN_SET); } + +void analogix_dp_enable_psr_crc(struct analogix_dp_device *dp) +{ + writel(PSR_VID_CRC_FLUSH | PSR_VID_CRC_ENABLE, + dp->reg_base + ANALOGIX_DP_CRC_CON); + + usleep_range(10, 20); + + writel(PSR_VID_CRC_ENABLE, dp->reg_base + ANALOGIX_DP_CRC_CON); +} + +void analogix_dp_send_psr_spd(struct analogix_dp_device *dp, int db1) +{ + unsigned int val; + + /* don't send info frame */ + val = readl(dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL); + val &= ~IF_EN; + writel(val, dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL); + + /* configure single frame update mode */ + writel(0x3, dp->reg_base + ANALOGIX_DP_PSR_FRAME_UPDATE_CTRL); + + /* configure VSC HB0 ~ HB3 */ + writel(0x00, dp->reg_base + ANALOGIX_DP_SPD_HB0); + writel(0x07, dp->reg_base + ANALOGIX_DP_SPD_HB1); + writel(0x02, dp->reg_base + ANALOGIX_DP_SPD_HB2); + writel(0x08, dp->reg_base + ANALOGIX_DP_SPD_HB3); + + /* configure VSC HB0 ~ HB3 */ + writel(0x00, dp->reg_base + ANALOGIX_DP_SPD_PB0); + writel(0x16, dp->reg_base + ANALOGIX_DP_SPD_PB1); + writel(0xCE, dp->reg_base + ANALOGIX_DP_SPD_PB2); + writel(0x5D, dp->reg_base + ANALOGIX_DP_SPD_PB3); + + /* configure DB0 / DB1 values */ + writel(0x00, dp->reg_base + ANALOGIX_DP_VSC_SHADOW_DB0); + writel(db1, dp->reg_base + ANALOGIX_DP_VSC_SHADOW_DB1); + + /* set reuse spd inforframe */ + val = readl(dp->reg_base + ANALOGIX_DP_VIDEO_CTL_3); + val |= REUSE_SPD_EN; + writel(val, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_3); + + /* mark info frame update */ + val = readl(dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL); + val = (val | IF_UP) & ~IF_EN; + writel(val, dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL); + + /* send info frame */ + val = readl(dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL); + val |= IF_EN; + writel(val, dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL); +} diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.h b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.h index cdcc6c5..a2698e4 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.h +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.h @@ -22,6 +22,8 @@ #define ANALOGIX_DP_VIDEO_CTL_8 0x3C #define ANALOGIX_DP_VIDEO_CTL_10 0x44
+#define ANALOGIX_DP_SPDIF_AUDIO_CTL_0 0xD8 + #define ANALOGIX_DP_PLL_REG_1 0xfc #define ANALOGIX_DP_PLL_REG_2 0x9e4 #define ANALOGIX_DP_PLL_REG_3 0x9e8 @@ -30,6 +32,21 @@
#define ANALOGIX_DP_PD 0x12c
+#define ANALOGIX_DP_IF_TYPE 0x244 +#define ANALOGIX_DP_IF_PKT_DB1 0x254 +#define ANALOGIX_DP_IF_PKT_DB2 0x258 +#define ANALOGIX_DP_SPD_HB0 0x2F8 +#define ANALOGIX_DP_SPD_HB1 0x2FC +#define ANALOGIX_DP_SPD_HB2 0x300 +#define ANALOGIX_DP_SPD_HB3 0x304 +#define ANALOGIX_DP_SPD_PB0 0x308 +#define ANALOGIX_DP_SPD_PB1 0x30C +#define ANALOGIX_DP_SPD_PB2 0x310 +#define ANALOGIX_DP_SPD_PB3 0x314 +#define ANALOGIX_DP_PSR_FRAME_UPDATE_CTRL 0x318 +#define ANALOGIX_DP_VSC_SHADOW_DB0 0x31C +#define ANALOGIX_DP_VSC_SHADOW_DB1 0x320 + #define ANALOGIX_DP_LANE_MAP 0x35C
#define ANALOGIX_DP_ANALOG_CTL_1 0x370 @@ -103,6 +120,8 @@
#define ANALOGIX_DP_SOC_GENERAL_CTL 0x800
+#define ANALOGIX_DP_CRC_CON 0x890 + /* ANALOGIX_DP_TX_SW_RESET */ #define RESET_DP_TX (0x1 << 0)
@@ -151,6 +170,7 @@ #define VID_CHK_UPDATE_TYPE_SHIFT (4) #define VID_CHK_UPDATE_TYPE_1 (0x1 << 4) #define VID_CHK_UPDATE_TYPE_0 (0x0 << 4) +#define REUSE_SPD_EN (0x1 << 3)
/* ANALOGIX_DP_VIDEO_CTL_8 */ #define VID_HRES_TH(x) (((x) & 0xf) << 4) @@ -376,4 +396,12 @@ #define VIDEO_MODE_SLAVE_MODE (0x1 << 0) #define VIDEO_MODE_MASTER_MODE (0x0 << 0)
+/* ANALOGIX_DP_PKT_SEND_CTL */ +#define IF_UP (0x1 << 4) +#define IF_EN (0x1 << 0) + +/* ANALOGIX_DP_CRC_CON */ +#define PSR_VID_CRC_FLUSH (0x1 << 2) +#define PSR_VID_CRC_ENABLE (0x1 << 0) + #endif /* _ANALOGIX_DP_REG_H */ diff --git a/include/drm/bridge/analogix_dp.h b/include/drm/bridge/analogix_dp.h index 261b86d..183a336 100644 --- a/include/drm/bridge/analogix_dp.h +++ b/include/drm/bridge/analogix_dp.h @@ -38,6 +38,9 @@ struct analogix_dp_plat_data { struct drm_connector *); };
+int analogix_dp_active_psr(struct device *dev); +int analogix_dp_inactive_psr(struct device *dev); + int analogix_dp_resume(struct device *dev); int analogix_dp_suspend(struct device *dev);
On Fri, Jul 1, 2016 at 5:19 AM, Yakir Yang ykk@rock-chips.com wrote:
The full name of PSR is Panel Self Refresh, panel device could refresh itself with the hardware framebuffer in panel, this would make lots of sense to save the power consumption.
This patch have exported two symbols for platform driver to implement the PSR function in hardware side:
- analogix_dp_active_psr()
- analogix_dp_inactive_psr()
Signed-off-by: Yakir Yang ykk@rock-chips.com
Changes in v3:
- split analogix_dp_enable_psr(), make it more clearly analogix_dp_detect_sink_psr() analogix_dp_enable_sink_psr()
- remove some nosie register setting comments
Changes in v2:
- introduce in v2, splite the common Analogix DP changes out
drivers/gpu/drm/bridge/analogix/analogix_dp_core.c | 64 ++++++++++++++++++++++ drivers/gpu/drm/bridge/analogix/analogix_dp_core.h | 4 ++ drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c | 54 ++++++++++++++++++ drivers/gpu/drm/bridge/analogix/analogix_dp_reg.h | 28 ++++++++++ include/drm/bridge/analogix_dp.h | 3 + 5 files changed, 153 insertions(+)
diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c index 32715da..b557097 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c @@ -97,6 +97,66 @@ static int analogix_dp_detect_hpd(struct analogix_dp_device *dp) return 0; }
+int analogix_dp_active_psr(struct device *dev) +{
struct analogix_dp_device *dp = dev_get_drvdata(dev);
if (!dp->psr_support)
return -EINVAL;
analogix_dp_send_psr_spd(dp, EDP_VSC_PSR_STATE_ACTIVE |
EDP_VSC_PSR_CRC_VALUES_VALID);
return 0;
+} +EXPORT_SYMBOL_GPL(analogix_dp_active_psr);
+int analogix_dp_inactive_psr(struct device *dev) +{
struct analogix_dp_device *dp = dev_get_drvdata(dev);
if (!dp->psr_support)
return -EINVAL;
analogix_dp_send_psr_spd(dp, 0);
return 0;
+} +EXPORT_SYMBOL_GPL(analogix_dp_inactive_psr);
+static bool analogix_dp_detect_sink_psr(struct analogix_dp_device *dp) +{
unsigned char psr_version;
analogix_dp_read_byte_from_dpcd(dp, DP_PSR_SUPPORT, &psr_version);
dev_info(dp->dev, "Panel PSR version : %x\n", psr_version);
This info message is likely to be spammy since it's printed everytime the panel toggle on. Perhaps downgrade to debug level.
return (psr_version & DP_PSR_IS_SUPPORTED) ? true : false;
+}
+static int analogix_dp_enable_sink_psr(struct analogix_dp_device *dp)
Return type is int, but the function never fails and you don't check the return value when calling it. Seems like this should be void.
+{
unsigned char psr_en;
/* Disable psr function */
analogix_dp_read_byte_from_dpcd(dp, DP_PSR_EN_CFG, &psr_en);
psr_en &= ~DP_PSR_ENABLE;
analogix_dp_write_byte_to_dpcd(dp, DP_PSR_EN_CFG, psr_en);
/* Main-Link transmitter remains active during PSR active states */
analogix_dp_read_byte_from_dpcd(dp, DP_PSR_EN_CFG, &psr_en);
psr_en = DP_PSR_MAIN_LINK_ACTIVE | DP_PSR_CRC_VERIFICATION;
Why read psr_en if you're just going to overwrite it? Perhaps you meant |= here.
analogix_dp_write_byte_to_dpcd(dp, DP_PSR_EN_CFG, psr_en);
/* Enable psr function */
analogix_dp_read_byte_from_dpcd(dp, DP_PSR_EN_CFG, &psr_en);
psr_en = DP_PSR_ENABLE | DP_PSR_MAIN_LINK_ACTIVE |
DP_PSR_CRC_VERIFICATION;
Again, no need to read if you're just overwriting.
analogix_dp_write_byte_to_dpcd(dp, DP_PSR_EN_CFG, psr_en);
analogix_dp_enable_psr_crc(dp);
return 0;
+}
static unsigned char analogix_dp_calc_edid_check_sum(unsigned char *edid_data) { int i; @@ -921,6 +981,10 @@ static void analogix_dp_commit(struct analogix_dp_device *dp)
/* Enable video */ analogix_dp_start_video(dp);
dp->psr_support = analogix_dp_detect_sink_psr(dp);
if (dp->psr_support)
analogix_dp_enable_sink_psr(dp);
}
int analogix_dp_get_modes(struct drm_connector *connector) diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h index b456380..6ca5dde 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h @@ -177,6 +177,7 @@ struct analogix_dp_device { int hpd_gpio; bool force_hpd; unsigned char edid[EDID_BLOCK_LENGTH * 2];
bool psr_support; struct analogix_dp_plat_data *plat_data;
}; @@ -278,4 +279,7 @@ int analogix_dp_is_video_stream_on(struct analogix_dp_device *dp); void analogix_dp_config_video_slave_mode(struct analogix_dp_device *dp); void analogix_dp_enable_scrambling(struct analogix_dp_device *dp); void analogix_dp_disable_scrambling(struct analogix_dp_device *dp); +void analogix_dp_enable_psr_crc(struct analogix_dp_device *dp); +void analogix_dp_send_psr_spd(struct analogix_dp_device *dp, int db1);
#endif /* _ANALOGIX_DP_CORE_H */ diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c index 48030f0..e8372c7 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c @@ -1322,3 +1322,57 @@ void analogix_dp_disable_scrambling(struct analogix_dp_device *dp) reg |= SCRAMBLING_DISABLE; writel(reg, dp->reg_base + ANALOGIX_DP_TRAINING_PTN_SET); }
+void analogix_dp_enable_psr_crc(struct analogix_dp_device *dp) +{
writel(PSR_VID_CRC_FLUSH | PSR_VID_CRC_ENABLE,
dp->reg_base + ANALOGIX_DP_CRC_CON);
usleep_range(10, 20);
Is this sleep arbitrary, or documented somewhere? Could you add a comment explaining how this was arrived at?
writel(PSR_VID_CRC_ENABLE, dp->reg_base + ANALOGIX_DP_CRC_CON);
+}
+void analogix_dp_send_psr_spd(struct analogix_dp_device *dp, int db1) +{
unsigned int val;
/* don't send info frame */
val = readl(dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL);
val &= ~IF_EN;
writel(val, dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL);
/* configure single frame update mode */
writel(0x3, dp->reg_base + ANALOGIX_DP_PSR_FRAME_UPDATE_CTRL);
/* configure VSC HB0 ~ HB3 */
writel(0x00, dp->reg_base + ANALOGIX_DP_SPD_HB0);
writel(0x07, dp->reg_base + ANALOGIX_DP_SPD_HB1);
writel(0x02, dp->reg_base + ANALOGIX_DP_SPD_HB2);
writel(0x08, dp->reg_base + ANALOGIX_DP_SPD_HB3);
/* configure VSC HB0 ~ HB3 */
writel(0x00, dp->reg_base + ANALOGIX_DP_SPD_PB0);
writel(0x16, dp->reg_base + ANALOGIX_DP_SPD_PB1);
writel(0xCE, dp->reg_base + ANALOGIX_DP_SPD_PB2);
writel(0x5D, dp->reg_base + ANALOGIX_DP_SPD_PB3);
/* configure DB0 / DB1 values */
writel(0x00, dp->reg_base + ANALOGIX_DP_VSC_SHADOW_DB0);
Lots of hardcoded values here. I think this could be cleaned up.
writel(db1, dp->reg_base + ANALOGIX_DP_VSC_SHADOW_DB1);
/* set reuse spd inforframe */
val = readl(dp->reg_base + ANALOGIX_DP_VIDEO_CTL_3);
val |= REUSE_SPD_EN;
writel(val, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_3);
/* mark info frame update */
val = readl(dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL);
val = (val | IF_UP) & ~IF_EN;
writel(val, dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL);
/* send info frame */
val = readl(dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL);
val |= IF_EN;
writel(val, dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL);
+} diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.h b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.h index cdcc6c5..a2698e4 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.h +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.h @@ -22,6 +22,8 @@ #define ANALOGIX_DP_VIDEO_CTL_8 0x3C #define ANALOGIX_DP_VIDEO_CTL_10 0x44
+#define ANALOGIX_DP_SPDIF_AUDIO_CTL_0 0xD8
#define ANALOGIX_DP_PLL_REG_1 0xfc #define ANALOGIX_DP_PLL_REG_2 0x9e4 #define ANALOGIX_DP_PLL_REG_3 0x9e8 @@ -30,6 +32,21 @@
#define ANALOGIX_DP_PD 0x12c
+#define ANALOGIX_DP_IF_TYPE 0x244 +#define ANALOGIX_DP_IF_PKT_DB1 0x254 +#define ANALOGIX_DP_IF_PKT_DB2 0x258 +#define ANALOGIX_DP_SPD_HB0 0x2F8 +#define ANALOGIX_DP_SPD_HB1 0x2FC +#define ANALOGIX_DP_SPD_HB2 0x300 +#define ANALOGIX_DP_SPD_HB3 0x304 +#define ANALOGIX_DP_SPD_PB0 0x308 +#define ANALOGIX_DP_SPD_PB1 0x30C +#define ANALOGIX_DP_SPD_PB2 0x310 +#define ANALOGIX_DP_SPD_PB3 0x314 +#define ANALOGIX_DP_PSR_FRAME_UPDATE_CTRL 0x318 +#define ANALOGIX_DP_VSC_SHADOW_DB0 0x31C +#define ANALOGIX_DP_VSC_SHADOW_DB1 0x320
#define ANALOGIX_DP_LANE_MAP 0x35C
#define ANALOGIX_DP_ANALOG_CTL_1 0x370 @@ -103,6 +120,8 @@
#define ANALOGIX_DP_SOC_GENERAL_CTL 0x800
+#define ANALOGIX_DP_CRC_CON 0x890
/* ANALOGIX_DP_TX_SW_RESET */ #define RESET_DP_TX (0x1 << 0)
@@ -151,6 +170,7 @@ #define VID_CHK_UPDATE_TYPE_SHIFT (4) #define VID_CHK_UPDATE_TYPE_1 (0x1 << 4) #define VID_CHK_UPDATE_TYPE_0 (0x0 << 4) +#define REUSE_SPD_EN (0x1 << 3)
/* ANALOGIX_DP_VIDEO_CTL_8 */ #define VID_HRES_TH(x) (((x) & 0xf) << 4) @@ -376,4 +396,12 @@ #define VIDEO_MODE_SLAVE_MODE (0x1 << 0) #define VIDEO_MODE_MASTER_MODE (0x0 << 0)
+/* ANALOGIX_DP_PKT_SEND_CTL */ +#define IF_UP (0x1 << 4) +#define IF_EN (0x1 << 0)
+/* ANALOGIX_DP_CRC_CON */ +#define PSR_VID_CRC_FLUSH (0x1 << 2) +#define PSR_VID_CRC_ENABLE (0x1 << 0)
#endif /* _ANALOGIX_DP_REG_H */ diff --git a/include/drm/bridge/analogix_dp.h b/include/drm/bridge/analogix_dp.h index 261b86d..183a336 100644 --- a/include/drm/bridge/analogix_dp.h +++ b/include/drm/bridge/analogix_dp.h @@ -38,6 +38,9 @@ struct analogix_dp_plat_data { struct drm_connector *); };
+int analogix_dp_active_psr(struct device *dev); +int analogix_dp_inactive_psr(struct device *dev);
Why active/inactive instead of enable/disable, which is used everywhere else?
int analogix_dp_resume(struct device *dev); int analogix_dp_suspend(struct device *dev);
-- 1.9.1
Sean,
Thanks for your review.
On 07/02/2016 03:46 AM, Sean Paul wrote:
On Fri, Jul 1, 2016 at 5:19 AM, Yakir Yang ykk@rock-chips.com wrote:
The full name of PSR is Panel Self Refresh, panel device could refresh itself with the hardware framebuffer in panel, this would make lots of sense to save the power consumption.
This patch have exported two symbols for platform driver to implement the PSR function in hardware side:
- analogix_dp_active_psr()
- analogix_dp_inactive_psr()
Signed-off-by: Yakir Yang ykk@rock-chips.com
Changes in v3:
- split analogix_dp_enable_psr(), make it more clearly analogix_dp_detect_sink_psr() analogix_dp_enable_sink_psr()
- remove some nosie register setting comments
Changes in v2:
introduce in v2, splite the common Analogix DP changes out
drivers/gpu/drm/bridge/analogix/analogix_dp_core.c | 64 ++++++++++++++++++++++ drivers/gpu/drm/bridge/analogix/analogix_dp_core.h | 4 ++ drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c | 54 ++++++++++++++++++ drivers/gpu/drm/bridge/analogix/analogix_dp_reg.h | 28 ++++++++++ include/drm/bridge/analogix_dp.h | 3 + 5 files changed, 153 insertions(+)
diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c index 32715da..b557097 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c @@ -97,6 +97,66 @@ static int analogix_dp_detect_hpd(struct analogix_dp_device *dp) return 0; }
+int analogix_dp_active_psr(struct device *dev) +{
struct analogix_dp_device *dp = dev_get_drvdata(dev);
if (!dp->psr_support)
return -EINVAL;
analogix_dp_send_psr_spd(dp, EDP_VSC_PSR_STATE_ACTIVE |
EDP_VSC_PSR_CRC_VALUES_VALID);
return 0;
+} +EXPORT_SYMBOL_GPL(analogix_dp_active_psr);
+int analogix_dp_inactive_psr(struct device *dev) +{
struct analogix_dp_device *dp = dev_get_drvdata(dev);
if (!dp->psr_support)
return -EINVAL;
analogix_dp_send_psr_spd(dp, 0);
return 0;
+} +EXPORT_SYMBOL_GPL(analogix_dp_inactive_psr);
+static bool analogix_dp_detect_sink_psr(struct analogix_dp_device *dp) +{
unsigned char psr_version;
analogix_dp_read_byte_from_dpcd(dp, DP_PSR_SUPPORT, &psr_version);
dev_info(dp->dev, "Panel PSR version : %x\n", psr_version);
This info message is likely to be spammy since it's printed everytime the panel toggle on. Perhaps downgrade to debug level.
Okay, done.
return (psr_version & DP_PSR_IS_SUPPORTED) ? true : false;
+}
+static int analogix_dp_enable_sink_psr(struct analogix_dp_device *dp)
Return type is int, but the function never fails and you don't check the return value when calling it. Seems like this should be void.
Done.
+{
unsigned char psr_en;
/* Disable psr function */
analogix_dp_read_byte_from_dpcd(dp, DP_PSR_EN_CFG, &psr_en);
psr_en &= ~DP_PSR_ENABLE;
analogix_dp_write_byte_to_dpcd(dp, DP_PSR_EN_CFG, psr_en);
/* Main-Link transmitter remains active during PSR active states */
analogix_dp_read_byte_from_dpcd(dp, DP_PSR_EN_CFG, &psr_en);
psr_en = DP_PSR_MAIN_LINK_ACTIVE | DP_PSR_CRC_VERIFICATION;
Why read psr_en if you're just going to overwrite it? Perhaps you meant |= here.
Yes, it's my mistaken, no need to read the DP_PSR_EN_CFG, just configure it directly is enough.
analogix_dp_write_byte_to_dpcd(dp, DP_PSR_EN_CFG, psr_en);
/* Enable psr function */
analogix_dp_read_byte_from_dpcd(dp, DP_PSR_EN_CFG, &psr_en);
psr_en = DP_PSR_ENABLE | DP_PSR_MAIN_LINK_ACTIVE |
DP_PSR_CRC_VERIFICATION;
Again, no need to read if you're just overwriting.
Yes, ditto
analogix_dp_write_byte_to_dpcd(dp, DP_PSR_EN_CFG, psr_en);
analogix_dp_enable_psr_crc(dp);
return 0;
+}
- static unsigned char analogix_dp_calc_edid_check_sum(unsigned char *edid_data) { int i;
@@ -921,6 +981,10 @@ static void analogix_dp_commit(struct analogix_dp_device *dp)
/* Enable video */ analogix_dp_start_video(dp);
dp->psr_support = analogix_dp_detect_sink_psr(dp);
if (dp->psr_support)
analogix_dp_enable_sink_psr(dp);
}
int analogix_dp_get_modes(struct drm_connector *connector)
diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h index b456380..6ca5dde 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h @@ -177,6 +177,7 @@ struct analogix_dp_device { int hpd_gpio; bool force_hpd; unsigned char edid[EDID_BLOCK_LENGTH * 2];
bool psr_support; struct analogix_dp_plat_data *plat_data;
};
@@ -278,4 +279,7 @@ int analogix_dp_is_video_stream_on(struct analogix_dp_device *dp); void analogix_dp_config_video_slave_mode(struct analogix_dp_device *dp); void analogix_dp_enable_scrambling(struct analogix_dp_device *dp); void analogix_dp_disable_scrambling(struct analogix_dp_device *dp); +void analogix_dp_enable_psr_crc(struct analogix_dp_device *dp); +void analogix_dp_send_psr_spd(struct analogix_dp_device *dp, int db1);
- #endif /* _ANALOGIX_DP_CORE_H */
diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c index 48030f0..e8372c7 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c @@ -1322,3 +1322,57 @@ void analogix_dp_disable_scrambling(struct analogix_dp_device *dp) reg |= SCRAMBLING_DISABLE; writel(reg, dp->reg_base + ANALOGIX_DP_TRAINING_PTN_SET); }
+void analogix_dp_enable_psr_crc(struct analogix_dp_device *dp) +{
writel(PSR_VID_CRC_FLUSH | PSR_VID_CRC_ENABLE,
dp->reg_base + ANALOGIX_DP_CRC_CON);
usleep_range(10, 20);
Is this sleep arbitrary, or documented somewhere? Could you add a comment explaining how this was arrived at?
Yes, this sleep is arbitrary, there is no document about the
writel(PSR_VID_CRC_ENABLE, dp->reg_base + ANALOGIX_DP_CRC_CON);
+}
+void analogix_dp_send_psr_spd(struct analogix_dp_device *dp, int db1) +{
unsigned int val;
/* don't send info frame */
val = readl(dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL);
val &= ~IF_EN;
writel(val, dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL);
/* configure single frame update mode */
writel(0x3, dp->reg_base + ANALOGIX_DP_PSR_FRAME_UPDATE_CTRL);
/* configure VSC HB0 ~ HB3 */
writel(0x00, dp->reg_base + ANALOGIX_DP_SPD_HB0);
writel(0x07, dp->reg_base + ANALOGIX_DP_SPD_HB1);
writel(0x02, dp->reg_base + ANALOGIX_DP_SPD_HB2);
writel(0x08, dp->reg_base + ANALOGIX_DP_SPD_HB3);
/* configure VSC HB0 ~ HB3 */
writel(0x00, dp->reg_base + ANALOGIX_DP_SPD_PB0);
writel(0x16, dp->reg_base + ANALOGIX_DP_SPD_PB1);
writel(0xCE, dp->reg_base + ANALOGIX_DP_SPD_PB2);
writel(0x5D, dp->reg_base + ANALOGIX_DP_SPD_PB3);
/* configure DB0 / DB1 values */
writel(0x00, dp->reg_base + ANALOGIX_DP_VSC_SHADOW_DB0);
Lots of hardcoded values here. I think this could be cleaned up.
For "HB0~HB3", "PB0~PB3" and "DB1", I don't understand very well. Those seems to be a kind of head number, I got those magic values from our IC side. So I think those should be okay to keep the hardcode values here :-D
But for the "0x3" in "ANALOGIX_DP_PSR_FRAME_UPDATE_CTRL", I would fix it with suitable macro.
writel(db1, dp->reg_base + ANALOGIX_DP_VSC_SHADOW_DB1);
/* set reuse spd inforframe */
val = readl(dp->reg_base + ANALOGIX_DP_VIDEO_CTL_3);
val |= REUSE_SPD_EN;
writel(val, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_3);
/* mark info frame update */
val = readl(dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL);
val = (val | IF_UP) & ~IF_EN;
writel(val, dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL);
/* send info frame */
val = readl(dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL);
val |= IF_EN;
writel(val, dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL);
+} diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.h b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.h index cdcc6c5..a2698e4 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.h +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.h @@ -22,6 +22,8 @@ #define ANALOGIX_DP_VIDEO_CTL_8 0x3C #define ANALOGIX_DP_VIDEO_CTL_10 0x44
+#define ANALOGIX_DP_SPDIF_AUDIO_CTL_0 0xD8
- #define ANALOGIX_DP_PLL_REG_1 0xfc #define ANALOGIX_DP_PLL_REG_2 0x9e4 #define ANALOGIX_DP_PLL_REG_3 0x9e8
@@ -30,6 +32,21 @@
#define ANALOGIX_DP_PD 0x12c
+#define ANALOGIX_DP_IF_TYPE 0x244 +#define ANALOGIX_DP_IF_PKT_DB1 0x254 +#define ANALOGIX_DP_IF_PKT_DB2 0x258 +#define ANALOGIX_DP_SPD_HB0 0x2F8 +#define ANALOGIX_DP_SPD_HB1 0x2FC +#define ANALOGIX_DP_SPD_HB2 0x300 +#define ANALOGIX_DP_SPD_HB3 0x304 +#define ANALOGIX_DP_SPD_PB0 0x308 +#define ANALOGIX_DP_SPD_PB1 0x30C +#define ANALOGIX_DP_SPD_PB2 0x310 +#define ANALOGIX_DP_SPD_PB3 0x314 +#define ANALOGIX_DP_PSR_FRAME_UPDATE_CTRL 0x318 +#define ANALOGIX_DP_VSC_SHADOW_DB0 0x31C +#define ANALOGIX_DP_VSC_SHADOW_DB1 0x320
#define ANALOGIX_DP_LANE_MAP 0x35C
#define ANALOGIX_DP_ANALOG_CTL_1 0x370
@@ -103,6 +120,8 @@
#define ANALOGIX_DP_SOC_GENERAL_CTL 0x800
+#define ANALOGIX_DP_CRC_CON 0x890
- /* ANALOGIX_DP_TX_SW_RESET */ #define RESET_DP_TX (0x1 << 0)
@@ -151,6 +170,7 @@ #define VID_CHK_UPDATE_TYPE_SHIFT (4) #define VID_CHK_UPDATE_TYPE_1 (0x1 << 4) #define VID_CHK_UPDATE_TYPE_0 (0x0 << 4) +#define REUSE_SPD_EN (0x1 << 3)
/* ANALOGIX_DP_VIDEO_CTL_8 */ #define VID_HRES_TH(x) (((x) & 0xf) << 4) @@ -376,4 +396,12 @@ #define VIDEO_MODE_SLAVE_MODE (0x1 << 0) #define VIDEO_MODE_MASTER_MODE (0x0 << 0)
+/* ANALOGIX_DP_PKT_SEND_CTL */ +#define IF_UP (0x1 << 4) +#define IF_EN (0x1 << 0)
+/* ANALOGIX_DP_CRC_CON */ +#define PSR_VID_CRC_FLUSH (0x1 << 2) +#define PSR_VID_CRC_ENABLE (0x1 << 0)
- #endif /* _ANALOGIX_DP_REG_H */
diff --git a/include/drm/bridge/analogix_dp.h b/include/drm/bridge/analogix_dp.h index 261b86d..183a336 100644 --- a/include/drm/bridge/analogix_dp.h +++ b/include/drm/bridge/analogix_dp.h @@ -38,6 +38,9 @@ struct analogix_dp_plat_data { struct drm_connector *); };
+int analogix_dp_active_psr(struct device *dev); +int analogix_dp_inactive_psr(struct device *dev);
Why active/inactive instead of enable/disable, which is used everywhere else?
Done
Thanks, - Yakir
- int analogix_dp_resume(struct device *dev); int analogix_dp_suspend(struct device *dev);
-- 1.9.1
On 07/08/2016 10:26 AM, Yakir Yang wrote:
Sean,
Thanks for your review.
On 07/02/2016 03:46 AM, Sean Paul wrote:
On Fri, Jul 1, 2016 at 5:19 AM, Yakir Yang ykk@rock-chips.com wrote:
The full name of PSR is Panel Self Refresh, panel device could refresh itself with the hardware framebuffer in panel, this would make lots of sense to save the power consumption.
This patch have exported two symbols for platform driver to implement the PSR function in hardware side:
- analogix_dp_active_psr()
- analogix_dp_inactive_psr()
Signed-off-by: Yakir Yang ykk@rock-chips.com
Changes in v3:
- split analogix_dp_enable_psr(), make it more clearly analogix_dp_detect_sink_psr() analogix_dp_enable_sink_psr()
- remove some nosie register setting comments
Changes in v2:
introduce in v2, splite the common Analogix DP changes out
drivers/gpu/drm/bridge/analogix/analogix_dp_core.c | 64
++++++++++++++++++++++ drivers/gpu/drm/bridge/analogix/analogix_dp_core.h | 4 ++ drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c | 54 ++++++++++++++++++ drivers/gpu/drm/bridge/analogix/analogix_dp_reg.h | 28 ++++++++++ include/drm/bridge/analogix_dp.h | 3 + 5 files changed, 153 insertions(+)
diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c index 32715da..b557097 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c @@ -97,6 +97,66 @@ static int analogix_dp_detect_hpd(struct analogix_dp_device *dp) return 0; }
+int analogix_dp_active_psr(struct device *dev) +{
struct analogix_dp_device *dp = dev_get_drvdata(dev);
if (!dp->psr_support)
return -EINVAL;
analogix_dp_send_psr_spd(dp, EDP_VSC_PSR_STATE_ACTIVE |
- EDP_VSC_PSR_CRC_VALUES_VALID);
return 0;
+} +EXPORT_SYMBOL_GPL(analogix_dp_active_psr);
+int analogix_dp_inactive_psr(struct device *dev) +{
struct analogix_dp_device *dp = dev_get_drvdata(dev);
if (!dp->psr_support)
return -EINVAL;
analogix_dp_send_psr_spd(dp, 0);
return 0;
+} +EXPORT_SYMBOL_GPL(analogix_dp_inactive_psr);
+static bool analogix_dp_detect_sink_psr(struct analogix_dp_device *dp) +{
unsigned char psr_version;
analogix_dp_read_byte_from_dpcd(dp, DP_PSR_SUPPORT,
&psr_version);
dev_info(dp->dev, "Panel PSR version : %x\n", psr_version);
This info message is likely to be spammy since it's printed everytime the panel toggle on. Perhaps downgrade to debug level.
Okay, done.
return (psr_version & DP_PSR_IS_SUPPORTED) ? true : false;
+}
+static int analogix_dp_enable_sink_psr(struct analogix_dp_device *dp)
Return type is int, but the function never fails and you don't check the return value when calling it. Seems like this should be void.
Done.
+{
unsigned char psr_en;
/* Disable psr function */
analogix_dp_read_byte_from_dpcd(dp, DP_PSR_EN_CFG, &psr_en);
psr_en &= ~DP_PSR_ENABLE;
analogix_dp_write_byte_to_dpcd(dp, DP_PSR_EN_CFG, psr_en);
/* Main-Link transmitter remains active during PSR active
states */
analogix_dp_read_byte_from_dpcd(dp, DP_PSR_EN_CFG, &psr_en);
psr_en = DP_PSR_MAIN_LINK_ACTIVE | DP_PSR_CRC_VERIFICATION;
Why read psr_en if you're just going to overwrite it? Perhaps you meant |= here.
Yes, it's my mistaken, no need to read the DP_PSR_EN_CFG, just configure it directly is enough.
- analogix_dp_write_byte_to_dpcd(dp, DP_PSR_EN_CFG, psr_en);
/* Enable psr function */
analogix_dp_read_byte_from_dpcd(dp, DP_PSR_EN_CFG, &psr_en);
psr_en = DP_PSR_ENABLE | DP_PSR_MAIN_LINK_ACTIVE |
DP_PSR_CRC_VERIFICATION;
Again, no need to read if you're just overwriting.
Yes, ditto
- analogix_dp_write_byte_to_dpcd(dp, DP_PSR_EN_CFG, psr_en);
analogix_dp_enable_psr_crc(dp);
return 0;
+}
- static unsigned char analogix_dp_calc_edid_check_sum(unsigned char
*edid_data) { int i; @@ -921,6 +981,10 @@ static void analogix_dp_commit(struct analogix_dp_device *dp)
/* Enable video */ analogix_dp_start_video(dp);
dp->psr_support = analogix_dp_detect_sink_psr(dp);
if (dp->psr_support)
analogix_dp_enable_sink_psr(dp);
}
int analogix_dp_get_modes(struct drm_connector *connector)
diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h index b456380..6ca5dde 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h @@ -177,6 +177,7 @@ struct analogix_dp_device { int hpd_gpio; bool force_hpd; unsigned char edid[EDID_BLOCK_LENGTH * 2];
bool psr_support; struct analogix_dp_plat_data *plat_data;
};
@@ -278,4 +279,7 @@ int analogix_dp_is_video_stream_on(struct analogix_dp_device *dp); void analogix_dp_config_video_slave_mode(struct analogix_dp_device *dp); void analogix_dp_enable_scrambling(struct analogix_dp_device *dp); void analogix_dp_disable_scrambling(struct analogix_dp_device *dp); +void analogix_dp_enable_psr_crc(struct analogix_dp_device *dp); +void analogix_dp_send_psr_spd(struct analogix_dp_device *dp, int db1);
- #endif /* _ANALOGIX_DP_CORE_H */
diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c index 48030f0..e8372c7 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c @@ -1322,3 +1322,57 @@ void analogix_dp_disable_scrambling(struct analogix_dp_device *dp) reg |= SCRAMBLING_DISABLE; writel(reg, dp->reg_base + ANALOGIX_DP_TRAINING_PTN_SET); }
+void analogix_dp_enable_psr_crc(struct analogix_dp_device *dp) +{
writel(PSR_VID_CRC_FLUSH | PSR_VID_CRC_ENABLE,
dp->reg_base + ANALOGIX_DP_CRC_CON);
usleep_range(10, 20);
Is this sleep arbitrary, or documented somewhere? Could you add a comment explaining how this was arrived at?
Yes, this sleep is arbitrary, there is no document about the
Oh, implement reply. I double check the datesheet, oh, i make a mistaken here. The CRC_FLUSH wasn't a trigger, but a enable gate.
PSR Video CRC flush enable. The PSR video CRC value is initialized at
every v-sync leading edge.
writel(PSR_VID_CRC_ENABLE, dp->reg_base + ANALOGIX_DP_CRC_CON);
So I need to delete this register setting.
Thanks, - Yakir
+}
+void analogix_dp_send_psr_spd(struct analogix_dp_device *dp, int db1) +{
unsigned int val;
/* don't send info frame */
val = readl(dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL);
val &= ~IF_EN;
writel(val, dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL);
/* configure single frame update mode */
writel(0x3, dp->reg_base + ANALOGIX_DP_PSR_FRAME_UPDATE_CTRL);
/* configure VSC HB0 ~ HB3 */
writel(0x00, dp->reg_base + ANALOGIX_DP_SPD_HB0);
writel(0x07, dp->reg_base + ANALOGIX_DP_SPD_HB1);
writel(0x02, dp->reg_base + ANALOGIX_DP_SPD_HB2);
writel(0x08, dp->reg_base + ANALOGIX_DP_SPD_HB3);
/* configure VSC HB0 ~ HB3 */
writel(0x00, dp->reg_base + ANALOGIX_DP_SPD_PB0);
writel(0x16, dp->reg_base + ANALOGIX_DP_SPD_PB1);
writel(0xCE, dp->reg_base + ANALOGIX_DP_SPD_PB2);
writel(0x5D, dp->reg_base + ANALOGIX_DP_SPD_PB3);
/* configure DB0 / DB1 values */
writel(0x00, dp->reg_base + ANALOGIX_DP_VSC_SHADOW_DB0);
Lots of hardcoded values here. I think this could be cleaned up.
For "HB0~HB3", "PB0~PB3" and "DB1", I don't understand very well. Those seems to be a kind of head number, I got those magic values from our IC side. So I think those should be okay to keep the hardcode values here :-D
But for the "0x3" in "ANALOGIX_DP_PSR_FRAME_UPDATE_CTRL", I would fix it with suitable macro.
writel(db1, dp->reg_base + ANALOGIX_DP_VSC_SHADOW_DB1);
/* set reuse spd inforframe */
val = readl(dp->reg_base + ANALOGIX_DP_VIDEO_CTL_3);
val |= REUSE_SPD_EN;
writel(val, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_3);
/* mark info frame update */
val = readl(dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL);
val = (val | IF_UP) & ~IF_EN;
writel(val, dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL);
/* send info frame */
val = readl(dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL);
val |= IF_EN;
writel(val, dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL);
+} diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.h b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.h index cdcc6c5..a2698e4 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.h +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.h @@ -22,6 +22,8 @@ #define ANALOGIX_DP_VIDEO_CTL_8 0x3C #define ANALOGIX_DP_VIDEO_CTL_10 0x44
+#define ANALOGIX_DP_SPDIF_AUDIO_CTL_0 0xD8
- #define ANALOGIX_DP_PLL_REG_1 0xfc #define ANALOGIX_DP_PLL_REG_2 0x9e4 #define ANALOGIX_DP_PLL_REG_3 0x9e8
@@ -30,6 +32,21 @@
#define ANALOGIX_DP_PD 0x12c
+#define ANALOGIX_DP_IF_TYPE 0x244 +#define ANALOGIX_DP_IF_PKT_DB1 0x254 +#define ANALOGIX_DP_IF_PKT_DB2 0x258 +#define ANALOGIX_DP_SPD_HB0 0x2F8 +#define ANALOGIX_DP_SPD_HB1 0x2FC +#define ANALOGIX_DP_SPD_HB2 0x300 +#define ANALOGIX_DP_SPD_HB3 0x304 +#define ANALOGIX_DP_SPD_PB0 0x308 +#define ANALOGIX_DP_SPD_PB1 0x30C +#define ANALOGIX_DP_SPD_PB2 0x310 +#define ANALOGIX_DP_SPD_PB3 0x314 +#define ANALOGIX_DP_PSR_FRAME_UPDATE_CTRL 0x318 +#define ANALOGIX_DP_VSC_SHADOW_DB0 0x31C +#define ANALOGIX_DP_VSC_SHADOW_DB1 0x320
#define ANALOGIX_DP_LANE_MAP 0x35C
#define ANALOGIX_DP_ANALOG_CTL_1 0x370
@@ -103,6 +120,8 @@
#define ANALOGIX_DP_SOC_GENERAL_CTL 0x800
+#define ANALOGIX_DP_CRC_CON 0x890
- /* ANALOGIX_DP_TX_SW_RESET */ #define RESET_DP_TX (0x1 << 0)
@@ -151,6 +170,7 @@ #define VID_CHK_UPDATE_TYPE_SHIFT (4) #define VID_CHK_UPDATE_TYPE_1 (0x1 << 4) #define VID_CHK_UPDATE_TYPE_0 (0x0 << 4) +#define REUSE_SPD_EN (0x1 << 3)
/* ANALOGIX_DP_VIDEO_CTL_8 */ #define VID_HRES_TH(x) (((x) & 0xf) << 4) @@ -376,4 +396,12 @@ #define VIDEO_MODE_SLAVE_MODE (0x1 << 0) #define VIDEO_MODE_MASTER_MODE (0x0 << 0)
+/* ANALOGIX_DP_PKT_SEND_CTL */ +#define IF_UP (0x1 << 4) +#define IF_EN (0x1 << 0)
+/* ANALOGIX_DP_CRC_CON */ +#define PSR_VID_CRC_FLUSH (0x1 << 2) +#define PSR_VID_CRC_ENABLE (0x1 << 0)
- #endif /* _ANALOGIX_DP_REG_H */
diff --git a/include/drm/bridge/analogix_dp.h b/include/drm/bridge/analogix_dp.h index 261b86d..183a336 100644 --- a/include/drm/bridge/analogix_dp.h +++ b/include/drm/bridge/analogix_dp.h @@ -38,6 +38,9 @@ struct analogix_dp_plat_data { struct drm_connector *); };
+int analogix_dp_active_psr(struct device *dev); +int analogix_dp_inactive_psr(struct device *dev);
Why active/inactive instead of enable/disable, which is used everywhere else?
Done
Thanks,
- Yakir
- int analogix_dp_resume(struct device *dev); int analogix_dp_suspend(struct device *dev);
-- 1.9.1
On Thu, Jul 7, 2016 at 7:26 PM, Yakir Yang ykk@rock-chips.com wrote:
Sean,
Thanks for your review.
On 07/02/2016 03:46 AM, Sean Paul wrote:
On Fri, Jul 1, 2016 at 5:19 AM, Yakir Yang ykk@rock-chips.com wrote:
The full name of PSR is Panel Self Refresh, panel device could refresh itself with the hardware framebuffer in panel, this would make lots of sense to save the power consumption.
This patch have exported two symbols for platform driver to implement the PSR function in hardware side:
- analogix_dp_active_psr()
- analogix_dp_inactive_psr()
Signed-off-by: Yakir Yang ykk@rock-chips.com
Changes in v3:
- split analogix_dp_enable_psr(), make it more clearly analogix_dp_detect_sink_psr() analogix_dp_enable_sink_psr()
- remove some nosie register setting comments
Changes in v2:
introduce in v2, splite the common Analogix DP changes out
drivers/gpu/drm/bridge/analogix/analogix_dp_core.c | 64
++++++++++++++++++++++ drivers/gpu/drm/bridge/analogix/analogix_dp_core.h | 4 ++ drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c | 54 ++++++++++++++++++ drivers/gpu/drm/bridge/analogix/analogix_dp_reg.h | 28 ++++++++++ include/drm/bridge/analogix_dp.h | 3 + 5 files changed, 153 insertions(+)
diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c index 32715da..b557097 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c @@ -97,6 +97,66 @@ static int analogix_dp_detect_hpd(struct analogix_dp_device *dp) return 0; }
+int analogix_dp_active_psr(struct device *dev) +{
struct analogix_dp_device *dp = dev_get_drvdata(dev);
if (!dp->psr_support)
return -EINVAL;
analogix_dp_send_psr_spd(dp, EDP_VSC_PSR_STATE_ACTIVE |
EDP_VSC_PSR_CRC_VALUES_VALID);
return 0;
+} +EXPORT_SYMBOL_GPL(analogix_dp_active_psr);
+int analogix_dp_inactive_psr(struct device *dev) +{
struct analogix_dp_device *dp = dev_get_drvdata(dev);
if (!dp->psr_support)
return -EINVAL;
analogix_dp_send_psr_spd(dp, 0);
return 0;
+} +EXPORT_SYMBOL_GPL(analogix_dp_inactive_psr);
+static bool analogix_dp_detect_sink_psr(struct analogix_dp_device *dp) +{
unsigned char psr_version;
analogix_dp_read_byte_from_dpcd(dp, DP_PSR_SUPPORT,
&psr_version);
dev_info(dp->dev, "Panel PSR version : %x\n", psr_version);
This info message is likely to be spammy since it's printed everytime the panel toggle on. Perhaps downgrade to debug level.
Okay, done.
return (psr_version & DP_PSR_IS_SUPPORTED) ? true : false;
+}
+static int analogix_dp_enable_sink_psr(struct analogix_dp_device *dp)
Return type is int, but the function never fails and you don't check the return value when calling it. Seems like this should be void.
Done.
+{
unsigned char psr_en;
/* Disable psr function */
analogix_dp_read_byte_from_dpcd(dp, DP_PSR_EN_CFG, &psr_en);
psr_en &= ~DP_PSR_ENABLE;
analogix_dp_write_byte_to_dpcd(dp, DP_PSR_EN_CFG, psr_en);
/* Main-Link transmitter remains active during PSR active states
*/
analogix_dp_read_byte_from_dpcd(dp, DP_PSR_EN_CFG, &psr_en);
psr_en = DP_PSR_MAIN_LINK_ACTIVE | DP_PSR_CRC_VERIFICATION;
Why read psr_en if you're just going to overwrite it? Perhaps you meant |= here.
Yes, it's my mistaken, no need to read the DP_PSR_EN_CFG, just configure it directly is enough.
analogix_dp_write_byte_to_dpcd(dp, DP_PSR_EN_CFG, psr_en);
/* Enable psr function */
analogix_dp_read_byte_from_dpcd(dp, DP_PSR_EN_CFG, &psr_en);
psr_en = DP_PSR_ENABLE | DP_PSR_MAIN_LINK_ACTIVE |
DP_PSR_CRC_VERIFICATION;
Again, no need to read if you're just overwriting.
Yes, ditto
analogix_dp_write_byte_to_dpcd(dp, DP_PSR_EN_CFG, psr_en);
analogix_dp_enable_psr_crc(dp);
return 0;
+}
- static unsigned char analogix_dp_calc_edid_check_sum(unsigned char
*edid_data) { int i; @@ -921,6 +981,10 @@ static void analogix_dp_commit(struct analogix_dp_device *dp)
/* Enable video */ analogix_dp_start_video(dp);
dp->psr_support = analogix_dp_detect_sink_psr(dp);
if (dp->psr_support)
analogix_dp_enable_sink_psr(dp);
}
int analogix_dp_get_modes(struct drm_connector *connector)
diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h index b456380..6ca5dde 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h @@ -177,6 +177,7 @@ struct analogix_dp_device { int hpd_gpio; bool force_hpd; unsigned char edid[EDID_BLOCK_LENGTH * 2];
bool psr_support; struct analogix_dp_plat_data *plat_data;
};
@@ -278,4 +279,7 @@ int analogix_dp_is_video_stream_on(struct analogix_dp_device *dp); void analogix_dp_config_video_slave_mode(struct analogix_dp_device *dp); void analogix_dp_enable_scrambling(struct analogix_dp_device *dp); void analogix_dp_disable_scrambling(struct analogix_dp_device *dp); +void analogix_dp_enable_psr_crc(struct analogix_dp_device *dp); +void analogix_dp_send_psr_spd(struct analogix_dp_device *dp, int db1);
- #endif /* _ANALOGIX_DP_CORE_H */
diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c index 48030f0..e8372c7 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c @@ -1322,3 +1322,57 @@ void analogix_dp_disable_scrambling(struct analogix_dp_device *dp) reg |= SCRAMBLING_DISABLE; writel(reg, dp->reg_base + ANALOGIX_DP_TRAINING_PTN_SET); }
+void analogix_dp_enable_psr_crc(struct analogix_dp_device *dp) +{
writel(PSR_VID_CRC_FLUSH | PSR_VID_CRC_ENABLE,
dp->reg_base + ANALOGIX_DP_CRC_CON);
usleep_range(10, 20);
Is this sleep arbitrary, or documented somewhere? Could you add a comment explaining how this was arrived at?
Yes, this sleep is arbitrary, there is no document about the
Ok, so I assume we actually need the delay for things to work? If not, delete the delay. If we need it, please add a comment saying the wait is required and the value was chosen via trial and error.
writel(PSR_VID_CRC_ENABLE, dp->reg_base + ANALOGIX_DP_CRC_CON);
+}
+void analogix_dp_send_psr_spd(struct analogix_dp_device *dp, int db1) +{
unsigned int val;
/* don't send info frame */
val = readl(dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL);
val &= ~IF_EN;
writel(val, dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL);
/* configure single frame update mode */
writel(0x3, dp->reg_base + ANALOGIX_DP_PSR_FRAME_UPDATE_CTRL);
/* configure VSC HB0 ~ HB3 */
writel(0x00, dp->reg_base + ANALOGIX_DP_SPD_HB0);
writel(0x07, dp->reg_base + ANALOGIX_DP_SPD_HB1);
writel(0x02, dp->reg_base + ANALOGIX_DP_SPD_HB2);
writel(0x08, dp->reg_base + ANALOGIX_DP_SPD_HB3);
/* configure VSC HB0 ~ HB3 */
writel(0x00, dp->reg_base + ANALOGIX_DP_SPD_PB0);
writel(0x16, dp->reg_base + ANALOGIX_DP_SPD_PB1);
writel(0xCE, dp->reg_base + ANALOGIX_DP_SPD_PB2);
writel(0x5D, dp->reg_base + ANALOGIX_DP_SPD_PB3);
/* configure DB0 / DB1 values */
writel(0x00, dp->reg_base + ANALOGIX_DP_VSC_SHADOW_DB0);
Lots of hardcoded values here. I think this could be cleaned up.
For "HB0~HB3", "PB0~PB3" and "DB1", I don't understand very well. Those seems to be a kind of head number, I got those magic values from our IC side. So I think those should be okay to keep the hardcode values here :-D
Hmm. Well, I'd probably pull them out into some HBX_MAGIC_VALUE define, but I suppose it's fine to keep them as-is. Please at least add a comment above explaining that they're magic values provided by your IC team.
But for the "0x3" in "ANALOGIX_DP_PSR_FRAME_UPDATE_CTRL", I would fix it with suitable macro.
writel(db1, dp->reg_base + ANALOGIX_DP_VSC_SHADOW_DB1);
/* set reuse spd inforframe */
val = readl(dp->reg_base + ANALOGIX_DP_VIDEO_CTL_3);
val |= REUSE_SPD_EN;
writel(val, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_3);
/* mark info frame update */
val = readl(dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL);
val = (val | IF_UP) & ~IF_EN;
writel(val, dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL);
/* send info frame */
val = readl(dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL);
val |= IF_EN;
writel(val, dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL);
+} diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.h b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.h index cdcc6c5..a2698e4 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.h +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.h @@ -22,6 +22,8 @@ #define ANALOGIX_DP_VIDEO_CTL_8 0x3C #define ANALOGIX_DP_VIDEO_CTL_10 0x44
+#define ANALOGIX_DP_SPDIF_AUDIO_CTL_0 0xD8
- #define ANALOGIX_DP_PLL_REG_1 0xfc #define ANALOGIX_DP_PLL_REG_2 0x9e4 #define ANALOGIX_DP_PLL_REG_3 0x9e8
@@ -30,6 +32,21 @@
#define ANALOGIX_DP_PD 0x12c
+#define ANALOGIX_DP_IF_TYPE 0x244 +#define ANALOGIX_DP_IF_PKT_DB1 0x254 +#define ANALOGIX_DP_IF_PKT_DB2 0x258 +#define ANALOGIX_DP_SPD_HB0 0x2F8 +#define ANALOGIX_DP_SPD_HB1 0x2FC +#define ANALOGIX_DP_SPD_HB2 0x300 +#define ANALOGIX_DP_SPD_HB3 0x304 +#define ANALOGIX_DP_SPD_PB0 0x308 +#define ANALOGIX_DP_SPD_PB1 0x30C +#define ANALOGIX_DP_SPD_PB2 0x310 +#define ANALOGIX_DP_SPD_PB3 0x314 +#define ANALOGIX_DP_PSR_FRAME_UPDATE_CTRL 0x318 +#define ANALOGIX_DP_VSC_SHADOW_DB0 0x31C +#define ANALOGIX_DP_VSC_SHADOW_DB1 0x320
#define ANALOGIX_DP_LANE_MAP 0x35C
#define ANALOGIX_DP_ANALOG_CTL_1 0x370
@@ -103,6 +120,8 @@
#define ANALOGIX_DP_SOC_GENERAL_CTL 0x800
+#define ANALOGIX_DP_CRC_CON 0x890
- /* ANALOGIX_DP_TX_SW_RESET */ #define RESET_DP_TX (0x1 << 0)
@@ -151,6 +170,7 @@ #define VID_CHK_UPDATE_TYPE_SHIFT (4) #define VID_CHK_UPDATE_TYPE_1 (0x1 << 4) #define VID_CHK_UPDATE_TYPE_0 (0x0 << 4) +#define REUSE_SPD_EN (0x1 << 3)
/* ANALOGIX_DP_VIDEO_CTL_8 */ #define VID_HRES_TH(x) (((x) & 0xf) << 4) @@ -376,4 +396,12 @@ #define VIDEO_MODE_SLAVE_MODE (0x1 << 0) #define VIDEO_MODE_MASTER_MODE (0x0 << 0)
+/* ANALOGIX_DP_PKT_SEND_CTL */ +#define IF_UP (0x1 << 4) +#define IF_EN (0x1 << 0)
+/* ANALOGIX_DP_CRC_CON */ +#define PSR_VID_CRC_FLUSH (0x1 << 2) +#define PSR_VID_CRC_ENABLE (0x1 << 0)
- #endif /* _ANALOGIX_DP_REG_H */
diff --git a/include/drm/bridge/analogix_dp.h b/include/drm/bridge/analogix_dp.h index 261b86d..183a336 100644 --- a/include/drm/bridge/analogix_dp.h +++ b/include/drm/bridge/analogix_dp.h @@ -38,6 +38,9 @@ struct analogix_dp_plat_data { struct drm_connector *); };
+int analogix_dp_active_psr(struct device *dev); +int analogix_dp_inactive_psr(struct device *dev);
Why active/inactive instead of enable/disable, which is used everywhere else?
Done
Thanks,
- Yakir
- int analogix_dp_resume(struct device *dev); int analogix_dp_suspend(struct device *dev);
-- 1.9.1
Sean,
On 07/12/2016 11:29 PM, Sean Paul wrote:
On Thu, Jul 7, 2016 at 7:26 PM, Yakir Yang ykk@rock-chips.com wrote:
Sean,
Thanks for your review.
On 07/02/2016 03:46 AM, Sean Paul wrote:
On Fri, Jul 1, 2016 at 5:19 AM, Yakir Yang ykk@rock-chips.com wrote:
The full name of PSR is Panel Self Refresh, panel device could refresh itself with the hardware framebuffer in panel, this would make lots of sense to save the power consumption.
This patch have exported two symbols for platform driver to implement the PSR function in hardware side:
- analogix_dp_active_psr()
- analogix_dp_inactive_psr()
Signed-off-by: Yakir Yang ykk@rock-chips.com
Changes in v3:
- split analogix_dp_enable_psr(), make it more clearly analogix_dp_detect_sink_psr() analogix_dp_enable_sink_psr()
- remove some nosie register setting comments
Changes in v2:
introduce in v2, splite the common Analogix DP changes out
drivers/gpu/drm/bridge/analogix/analogix_dp_core.c | 64
++++++++++++++++++++++ drivers/gpu/drm/bridge/analogix/analogix_dp_core.h | 4 ++ drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c | 54 ++++++++++++++++++ drivers/gpu/drm/bridge/analogix/analogix_dp_reg.h | 28 ++++++++++ include/drm/bridge/analogix_dp.h | 3 + 5 files changed, 153 insertions(+)
diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c index 32715da..b557097 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c @@ -97,6 +97,66 @@ static int analogix_dp_detect_hpd(struct analogix_dp_device *dp) return 0; }
+int analogix_dp_active_psr(struct device *dev) +{
struct analogix_dp_device *dp = dev_get_drvdata(dev);
if (!dp->psr_support)
return -EINVAL;
analogix_dp_send_psr_spd(dp, EDP_VSC_PSR_STATE_ACTIVE |
EDP_VSC_PSR_CRC_VALUES_VALID);
return 0;
+} +EXPORT_SYMBOL_GPL(analogix_dp_active_psr);
+int analogix_dp_inactive_psr(struct device *dev) +{
struct analogix_dp_device *dp = dev_get_drvdata(dev);
if (!dp->psr_support)
return -EINVAL;
analogix_dp_send_psr_spd(dp, 0);
return 0;
+} +EXPORT_SYMBOL_GPL(analogix_dp_inactive_psr);
+static bool analogix_dp_detect_sink_psr(struct analogix_dp_device *dp) +{
unsigned char psr_version;
analogix_dp_read_byte_from_dpcd(dp, DP_PSR_SUPPORT,
&psr_version);
dev_info(dp->dev, "Panel PSR version : %x\n", psr_version);
This info message is likely to be spammy since it's printed everytime the panel toggle on. Perhaps downgrade to debug level.
Okay, done.
return (psr_version & DP_PSR_IS_SUPPORTED) ? true : false;
+}
+static int analogix_dp_enable_sink_psr(struct analogix_dp_device *dp)
Return type is int, but the function never fails and you don't check the return value when calling it. Seems like this should be void.
Done.
+{
unsigned char psr_en;
/* Disable psr function */
analogix_dp_read_byte_from_dpcd(dp, DP_PSR_EN_CFG, &psr_en);
psr_en &= ~DP_PSR_ENABLE;
analogix_dp_write_byte_to_dpcd(dp, DP_PSR_EN_CFG, psr_en);
/* Main-Link transmitter remains active during PSR active states
*/
analogix_dp_read_byte_from_dpcd(dp, DP_PSR_EN_CFG, &psr_en);
psr_en = DP_PSR_MAIN_LINK_ACTIVE | DP_PSR_CRC_VERIFICATION;
Why read psr_en if you're just going to overwrite it? Perhaps you meant |= here.
Yes, it's my mistaken, no need to read the DP_PSR_EN_CFG, just configure it directly is enough.
analogix_dp_write_byte_to_dpcd(dp, DP_PSR_EN_CFG, psr_en);
/* Enable psr function */
analogix_dp_read_byte_from_dpcd(dp, DP_PSR_EN_CFG, &psr_en);
psr_en = DP_PSR_ENABLE | DP_PSR_MAIN_LINK_ACTIVE |
DP_PSR_CRC_VERIFICATION;
Again, no need to read if you're just overwriting.
Yes, ditto
analogix_dp_write_byte_to_dpcd(dp, DP_PSR_EN_CFG, psr_en);
analogix_dp_enable_psr_crc(dp);
return 0;
+}
- static unsigned char analogix_dp_calc_edid_check_sum(unsigned char
*edid_data) { int i; @@ -921,6 +981,10 @@ static void analogix_dp_commit(struct analogix_dp_device *dp)
/* Enable video */ analogix_dp_start_video(dp);
dp->psr_support = analogix_dp_detect_sink_psr(dp);
if (dp->psr_support)
analogix_dp_enable_sink_psr(dp);
}
int analogix_dp_get_modes(struct drm_connector *connector)
diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h index b456380..6ca5dde 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h @@ -177,6 +177,7 @@ struct analogix_dp_device { int hpd_gpio; bool force_hpd; unsigned char edid[EDID_BLOCK_LENGTH * 2];
bool psr_support; struct analogix_dp_plat_data *plat_data;
};
@@ -278,4 +279,7 @@ int analogix_dp_is_video_stream_on(struct analogix_dp_device *dp); void analogix_dp_config_video_slave_mode(struct analogix_dp_device *dp); void analogix_dp_enable_scrambling(struct analogix_dp_device *dp); void analogix_dp_disable_scrambling(struct analogix_dp_device *dp); +void analogix_dp_enable_psr_crc(struct analogix_dp_device *dp); +void analogix_dp_send_psr_spd(struct analogix_dp_device *dp, int db1);
- #endif /* _ANALOGIX_DP_CORE_H */
diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c index 48030f0..e8372c7 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c @@ -1322,3 +1322,57 @@ void analogix_dp_disable_scrambling(struct analogix_dp_device *dp) reg |= SCRAMBLING_DISABLE; writel(reg, dp->reg_base + ANALOGIX_DP_TRAINING_PTN_SET); }
+void analogix_dp_enable_psr_crc(struct analogix_dp_device *dp) +{
writel(PSR_VID_CRC_FLUSH | PSR_VID_CRC_ENABLE,
dp->reg_base + ANALOGIX_DP_CRC_CON);
usleep_range(10, 20);
Is this sleep arbitrary, or documented somewhere? Could you add a comment explaining how this was arrived at?
Yes, this sleep is arbitrary, there is no document about the
Ok, so I assume we actually need the delay for things to work? If not, delete the delay. If we need it, please add a comment saying the wait is required and the value was chosen via trial and error.
I should delete this delay, cause i found that PSR_VID_CRC_FLUSH not a trigger bit, but a enable gate. Here is the TRM explain:
PSR_VID_CRC_FLUSH: PSR Video CRC flush enable. The PSR video CRC value is initialized at every v-sync leading edge.
So I just need to set this flag after enable PSR CRC function, no need to remove it :-D
writel(PSR_VID_CRC_ENABLE, dp->reg_base + ANALOGIX_DP_CRC_CON);
+}
+void analogix_dp_send_psr_spd(struct analogix_dp_device *dp, int db1) +{
unsigned int val;
/* don't send info frame */
val = readl(dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL);
val &= ~IF_EN;
writel(val, dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL);
/* configure single frame update mode */
writel(0x3, dp->reg_base + ANALOGIX_DP_PSR_FRAME_UPDATE_CTRL);
/* configure VSC HB0 ~ HB3 */
writel(0x00, dp->reg_base + ANALOGIX_DP_SPD_HB0);
writel(0x07, dp->reg_base + ANALOGIX_DP_SPD_HB1);
writel(0x02, dp->reg_base + ANALOGIX_DP_SPD_HB2);
writel(0x08, dp->reg_base + ANALOGIX_DP_SPD_HB3);
/* configure VSC HB0 ~ HB3 */
writel(0x00, dp->reg_base + ANALOGIX_DP_SPD_PB0);
writel(0x16, dp->reg_base + ANALOGIX_DP_SPD_PB1);
writel(0xCE, dp->reg_base + ANALOGIX_DP_SPD_PB2);
writel(0x5D, dp->reg_base + ANALOGIX_DP_SPD_PB3);
/* configure DB0 / DB1 values */
writel(0x00, dp->reg_base + ANALOGIX_DP_VSC_SHADOW_DB0);
Lots of hardcoded values here. I think this could be cleaned up.
For "HB0~HB3", "PB0~PB3" and "DB1", I don't understand very well. Those seems to be a kind of head number, I got those magic values from our IC side. So I think those should be okay to keep the hardcode values here :-D
Hmm. Well, I'd probably pull them out into some HBX_MAGIC_VALUE define, but I suppose it's fine to keep them as-is. Please at least add a comment above explaining that they're magic values provided by your IC team.
Done ;)
Thanks, - Yakir
But for the "0x3" in "ANALOGIX_DP_PSR_FRAME_UPDATE_CTRL", I would fix it with suitable macro.
writel(db1, dp->reg_base + ANALOGIX_DP_VSC_SHADOW_DB1);
/* set reuse spd inforframe */
val = readl(dp->reg_base + ANALOGIX_DP_VIDEO_CTL_3);
val |= REUSE_SPD_EN;
writel(val, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_3);
/* mark info frame update */
val = readl(dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL);
val = (val | IF_UP) & ~IF_EN;
writel(val, dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL);
/* send info frame */
val = readl(dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL);
val |= IF_EN;
writel(val, dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL);
+} diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.h b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.h index cdcc6c5..a2698e4 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.h +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.h @@ -22,6 +22,8 @@ #define ANALOGIX_DP_VIDEO_CTL_8 0x3C #define ANALOGIX_DP_VIDEO_CTL_10 0x44
+#define ANALOGIX_DP_SPDIF_AUDIO_CTL_0 0xD8
- #define ANALOGIX_DP_PLL_REG_1 0xfc #define ANALOGIX_DP_PLL_REG_2 0x9e4 #define ANALOGIX_DP_PLL_REG_3 0x9e8
@@ -30,6 +32,21 @@
#define ANALOGIX_DP_PD 0x12c
+#define ANALOGIX_DP_IF_TYPE 0x244 +#define ANALOGIX_DP_IF_PKT_DB1 0x254 +#define ANALOGIX_DP_IF_PKT_DB2 0x258 +#define ANALOGIX_DP_SPD_HB0 0x2F8 +#define ANALOGIX_DP_SPD_HB1 0x2FC +#define ANALOGIX_DP_SPD_HB2 0x300 +#define ANALOGIX_DP_SPD_HB3 0x304 +#define ANALOGIX_DP_SPD_PB0 0x308 +#define ANALOGIX_DP_SPD_PB1 0x30C +#define ANALOGIX_DP_SPD_PB2 0x310 +#define ANALOGIX_DP_SPD_PB3 0x314 +#define ANALOGIX_DP_PSR_FRAME_UPDATE_CTRL 0x318 +#define ANALOGIX_DP_VSC_SHADOW_DB0 0x31C +#define ANALOGIX_DP_VSC_SHADOW_DB1 0x320
#define ANALOGIX_DP_LANE_MAP 0x35C
#define ANALOGIX_DP_ANALOG_CTL_1 0x370
@@ -103,6 +120,8 @@
#define ANALOGIX_DP_SOC_GENERAL_CTL 0x800
+#define ANALOGIX_DP_CRC_CON 0x890
- /* ANALOGIX_DP_TX_SW_RESET */ #define RESET_DP_TX (0x1 << 0)
@@ -151,6 +170,7 @@ #define VID_CHK_UPDATE_TYPE_SHIFT (4) #define VID_CHK_UPDATE_TYPE_1 (0x1 << 4) #define VID_CHK_UPDATE_TYPE_0 (0x0 << 4) +#define REUSE_SPD_EN (0x1 << 3)
/* ANALOGIX_DP_VIDEO_CTL_8 */ #define VID_HRES_TH(x) (((x) & 0xf) << 4) @@ -376,4 +396,12 @@ #define VIDEO_MODE_SLAVE_MODE (0x1 << 0) #define VIDEO_MODE_MASTER_MODE (0x0 << 0)
+/* ANALOGIX_DP_PKT_SEND_CTL */ +#define IF_UP (0x1 << 4) +#define IF_EN (0x1 << 0)
+/* ANALOGIX_DP_CRC_CON */ +#define PSR_VID_CRC_FLUSH (0x1 << 2) +#define PSR_VID_CRC_ENABLE (0x1 << 0)
- #endif /* _ANALOGIX_DP_REG_H */
diff --git a/include/drm/bridge/analogix_dp.h b/include/drm/bridge/analogix_dp.h index 261b86d..183a336 100644 --- a/include/drm/bridge/analogix_dp.h +++ b/include/drm/bridge/analogix_dp.h @@ -38,6 +38,9 @@ struct analogix_dp_plat_data { struct drm_connector *); };
+int analogix_dp_active_psr(struct device *dev); +int analogix_dp_inactive_psr(struct device *dev);
Why active/inactive instead of enable/disable, which is used everywhere else?
Done
Thanks,
- Yakir
- int analogix_dp_resume(struct device *dev); int analogix_dp_suspend(struct device *dev);
-- 1.9.1
Alway enable the PSR function for Rockchip analogix_dp driver. If panel don't support PSR, then the core analogix_dp would ignore this setting.
Signed-off-by: Yakir Yang ykk@rock-chips.com --- Changes in v3: - split the common psr logic into a seperate driver, make this to a simple sub-psr device driver.
Changes in v2: - remove vblank notify out (Daniel) - create a psr_active() callback in vop data struct.
drivers/gpu/drm/rockchip/analogix_dp-rockchip.c | 52 +++++++++++++++++++++++++ 1 file changed, 52 insertions(+)
diff --git a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c index e81e19a..80a60a6 100644 --- a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c +++ b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c @@ -32,6 +32,7 @@ #include <drm/bridge/analogix_dp.h>
#include "rockchip_drm_drv.h" +#include "rockchip_drm_psr.h" #include "rockchip_drm_vop.h"
#define RK3288_GRF_SOC_CON6 0x25c @@ -68,11 +69,53 @@ struct rockchip_dp_device { struct regmap *grf; struct reset_control *rst;
+ struct delayed_work psr_work; + unsigned int psr_state; + const struct rockchip_dp_chip_data *data;
struct analogix_dp_plat_data plat_data; };
+static int analogix_dp_psr_set(struct drm_encoder *encoder, bool enabled) +{ + struct rockchip_dp_device *dp = to_dp(encoder); + + dev_dbg(dp->dev, "%s PSR...\n", enabled ? "Entry" : "Exit"); + + if (enabled) + dp->psr_state = EDP_VSC_PSR_STATE_ACTIVE; + else + dp->psr_state = ~EDP_VSC_PSR_STATE_ACTIVE; + + schedule_delayed_work(&dp->psr_work, msecs_to_jiffies(10)); + + return 0; +} + +static void analogix_dp_psr_work(struct work_struct *work) +{ + struct rockchip_dp_device *dp = + container_of(work, typeof(*dp), psr_work.work); + struct drm_crtc *crtc = dp->encoder.crtc; + int psr_state = dp->psr_state; + int vact_end; + int ret; + + if (!crtc) + return; + + vact_end = crtc->mode.vtotal - crtc->mode.vsync_start + crtc->mode.vdisplay; + + ret = rockchip_drm_wait_line_flag(dp->encoder.crtc, vact_end, 100); + if (ret == 0) { + if (psr_state == EDP_VSC_PSR_STATE_ACTIVE) + analogix_dp_active_psr(dp->dev); + else + analogix_dp_inactive_psr(dp->dev); + } +} + static int rockchip_dp_pre_init(struct rockchip_dp_device *dp) { reset_control_assert(dp->rst); @@ -340,12 +383,21 @@ static int rockchip_dp_bind(struct device *dev, struct device *master, dp->plat_data.power_off = rockchip_dp_powerdown; dp->plat_data.get_modes = rockchip_dp_get_modes;
+ dp->psr_state = ~EDP_VSC_PSR_STATE_ACTIVE; + INIT_DELAYED_WORK(&dp->psr_work, analogix_dp_psr_work); + + rockchip_drm_psr_register(&dp->encoder, analogix_dp_psr_set); + return analogix_dp_bind(dev, dp->drm_dev, &dp->plat_data); }
static void rockchip_dp_unbind(struct device *dev, struct device *master, void *data) { + struct rockchip_dp_device *dp = dev_get_drvdata(dev); + + rockchip_drm_psr_unregister(&dp->encoder); + return analogix_dp_unbind(dev, master, data); }
On Fri, Jul 1, 2016 at 5:19 AM, Yakir Yang ykk@rock-chips.com wrote:
Alway enable the PSR function for Rockchip analogix_dp driver. If panel don't support PSR, then the core analogix_dp would ignore this setting.
Signed-off-by: Yakir Yang ykk@rock-chips.com
Changes in v3:
- split the common psr logic into a seperate driver, make this to a simple sub-psr device driver.
Changes in v2:
- remove vblank notify out (Daniel)
- create a psr_active() callback in vop data struct.
drivers/gpu/drm/rockchip/analogix_dp-rockchip.c | 52 +++++++++++++++++++++++++ 1 file changed, 52 insertions(+)
diff --git a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c index e81e19a..80a60a6 100644 --- a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c +++ b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c @@ -32,6 +32,7 @@ #include <drm/bridge/analogix_dp.h>
#include "rockchip_drm_drv.h" +#include "rockchip_drm_psr.h" #include "rockchip_drm_vop.h"
#define RK3288_GRF_SOC_CON6 0x25c @@ -68,11 +69,53 @@ struct rockchip_dp_device { struct regmap *grf; struct reset_control *rst;
struct delayed_work psr_work;
unsigned int psr_state;
const struct rockchip_dp_chip_data *data; struct analogix_dp_plat_data plat_data;
};
+static int analogix_dp_psr_set(struct drm_encoder *encoder, bool enabled)
Again, this function doesn't fail, but its return type is int. Fortunately you don't check the return in rockchip_drm_psr.c, so this also seems like a good void candidate.
+{
struct rockchip_dp_device *dp = to_dp(encoder);
dev_dbg(dp->dev, "%s PSR...\n", enabled ? "Entry" : "Exit");
if (enabled)
dp->psr_state = EDP_VSC_PSR_STATE_ACTIVE;
else
dp->psr_state = ~EDP_VSC_PSR_STATE_ACTIVE;
schedule_delayed_work(&dp->psr_work, msecs_to_jiffies(10));
Pull 10 out into a #define
return 0;
+}
+static void analogix_dp_psr_work(struct work_struct *work) +{
struct rockchip_dp_device *dp =
container_of(work, typeof(*dp), psr_work.work);
struct drm_crtc *crtc = dp->encoder.crtc;
int psr_state = dp->psr_state;
int vact_end;
int ret;
if (!crtc)
return;
vact_end = crtc->mode.vtotal - crtc->mode.vsync_start + crtc->mode.vdisplay;
ret = rockchip_drm_wait_line_flag(dp->encoder.crtc, vact_end, 100);
Pull 100 out into a #define
if (ret == 0) {
if (ret) { dev_err(... "line flag interrupt did not arrive"); return; }
if (psr_state == EDP_VSC_PSR_STATE_ACTIVE)
analogix_dp_active_psr(dp->dev);
else
analogix_dp_inactive_psr(dp->dev);
}
+}
static int rockchip_dp_pre_init(struct rockchip_dp_device *dp) { reset_control_assert(dp->rst); @@ -340,12 +383,21 @@ static int rockchip_dp_bind(struct device *dev, struct device *master, dp->plat_data.power_off = rockchip_dp_powerdown; dp->plat_data.get_modes = rockchip_dp_get_modes;
dp->psr_state = ~EDP_VSC_PSR_STATE_ACTIVE;
INIT_DELAYED_WORK(&dp->psr_work, analogix_dp_psr_work);
rockchip_drm_psr_register(&dp->encoder, analogix_dp_psr_set);
return analogix_dp_bind(dev, dp->drm_dev, &dp->plat_data);
}
static void rockchip_dp_unbind(struct device *dev, struct device *master, void *data) {
struct rockchip_dp_device *dp = dev_get_drvdata(dev);
rockchip_drm_psr_unregister(&dp->encoder);
return analogix_dp_unbind(dev, master, data);
}
-- 1.9.1
Sean,
On 07/02/2016 04:05 AM, Sean Paul wrote:
On Fri, Jul 1, 2016 at 5:19 AM, Yakir Yang ykk@rock-chips.com wrote:
Alway enable the PSR function for Rockchip analogix_dp driver. If panel don't support PSR, then the core analogix_dp would ignore this setting.
Signed-off-by: Yakir Yang ykk@rock-chips.com
Changes in v3:
- split the common psr logic into a seperate driver, make this to a simple sub-psr device driver.
Changes in v2:
remove vblank notify out (Daniel)
create a psr_active() callback in vop data struct.
drivers/gpu/drm/rockchip/analogix_dp-rockchip.c | 52 +++++++++++++++++++++++++ 1 file changed, 52 insertions(+)
diff --git a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c index e81e19a..80a60a6 100644 --- a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c +++ b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c @@ -32,6 +32,7 @@ #include <drm/bridge/analogix_dp.h>
#include "rockchip_drm_drv.h" +#include "rockchip_drm_psr.h" #include "rockchip_drm_vop.h"
#define RK3288_GRF_SOC_CON6 0x25c @@ -68,11 +69,53 @@ struct rockchip_dp_device { struct regmap *grf; struct reset_control *rst;
struct delayed_work psr_work;
unsigned int psr_state;
const struct rockchip_dp_chip_data *data; struct analogix_dp_plat_data plat_data;
};
+static int analogix_dp_psr_set(struct drm_encoder *encoder, bool enabled)
Again, this function doesn't fail, but its return type is int. Fortunately you don't check the return in rockchip_drm_psr.c, so this also seems like a good void candidate.
Okay, done.
+{
struct rockchip_dp_device *dp = to_dp(encoder);
dev_dbg(dp->dev, "%s PSR...\n", enabled ? "Entry" : "Exit");
if (enabled)
dp->psr_state = EDP_VSC_PSR_STATE_ACTIVE;
else
dp->psr_state = ~EDP_VSC_PSR_STATE_ACTIVE;
schedule_delayed_work(&dp->psr_work, msecs_to_jiffies(10));
Pull 10 out into a #define
Done
return 0;
+}
+static void analogix_dp_psr_work(struct work_struct *work) +{
struct rockchip_dp_device *dp =
container_of(work, typeof(*dp), psr_work.work);
struct drm_crtc *crtc = dp->encoder.crtc;
int psr_state = dp->psr_state;
int vact_end;
int ret;
if (!crtc)
return;
vact_end = crtc->mode.vtotal - crtc->mode.vsync_start + crtc->mode.vdisplay;
ret = rockchip_drm_wait_line_flag(dp->encoder.crtc, vact_end, 100);
Pull 100 out into a #define
Done.
if (ret == 0) {
if (ret) { dev_err(... "line flag interrupt did not arrive"); return; }
Done.
Thanks - Yakir
if (psr_state == EDP_VSC_PSR_STATE_ACTIVE)
analogix_dp_active_psr(dp->dev);
else
analogix_dp_inactive_psr(dp->dev);
}
+}
- static int rockchip_dp_pre_init(struct rockchip_dp_device *dp) { reset_control_assert(dp->rst);
@@ -340,12 +383,21 @@ static int rockchip_dp_bind(struct device *dev, struct device *master, dp->plat_data.power_off = rockchip_dp_powerdown; dp->plat_data.get_modes = rockchip_dp_get_modes;
dp->psr_state = ~EDP_VSC_PSR_STATE_ACTIVE;
INIT_DELAYED_WORK(&dp->psr_work, analogix_dp_psr_work);
rockchip_drm_psr_register(&dp->encoder, analogix_dp_psr_set);
return analogix_dp_bind(dev, dp->drm_dev, &dp->plat_data);
}
static void rockchip_dp_unbind(struct device *dev, struct device *master, void *data) {
struct rockchip_dp_device *dp = dev_get_drvdata(dev);
rockchip_drm_psr_unregister(&dp->encoder);
return analogix_dp_unbind(dev, master, data);
}
-- 1.9.1
dri-devel@lists.freedesktop.org