This patch set adds the Tegra dc one-shot support. The patch set is tested on Dalmore + Sharp lq101r1sx01.
Basically this patch set is just for RFC. Some DRM files are changes, I guess maybe there are better solutions for that. Also, Thierry mentioned that "te-polarity" can be designed as a GPIO since not every display controller has a dedicate TE pin like Tegra. So I will try to create v2 after some comments are received.
Please be noticed that the patch #12 is not part of the feature, it's just used for testing.
Mark Zhang (12): drm: Add a new mode flag: DRM_MODE_FLAG_PREFER_ONE_SHOT drm: panel: Add one-shot flag to Sharp lq101r1sx01 driver drm: panel: Turn on TE(Tearing Effect) on Sharp lq101r1sx01 drm: Add DRM mode flag TE polarity dt: panel: Add property "te-polarity" drm: panel: Parse "te-polarity" in Sharp lq101r1sx01 driver drm/tegra: Set NC(Non-contiguous) mode to dc for one-shot drm/panel: Add panel func: idle/busy drm: dsi: Add "enter idle" & "exit idle" dcs functions drm: panel: Add idle/busy in Sharp lq101r1sx01 driver drm/tegra: Suspend dc/dsi/panel in one-shot mode JUST FOR TEST: Add one-shot trigger to update display
.../bindings/panel/sharp,lq101r1sx01.txt | 2 + arch/arm/boot/dts/tegra114-dalmore.dts | 2 + drivers/gpu/drm/drm_mipi_dsi.c | 36 ++++++ drivers/gpu/drm/panel/panel-sharp-lq101r1sx01.c | 51 ++++++++- drivers/gpu/drm/tegra/dc.c | 124 ++++++++++++++++++--- drivers/gpu/drm/tegra/dc.h | 5 + drivers/gpu/drm/tegra/drm.h | 4 + drivers/gpu/drm/tegra/dsi.c | 63 ++++++++++- include/drm/drm_mipi_dsi.h | 2 + include/drm/drm_panel.h | 18 +++ include/uapi/drm/drm_mode.h | 3 + 11 files changed, 290 insertions(+), 20 deletions(-)
Normally this flag is set by panel driver so that crtc can enable the "one-shot" mode(not scan frames continuously).
Signed-off-by: Mark Zhang markz@nvidia.com --- include/uapi/drm/drm_mode.h | 1 + 1 file changed, 1 insertion(+)
diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h index dbeba949462a..5447a338e893 100644 --- a/include/uapi/drm/drm_mode.h +++ b/include/uapi/drm/drm_mode.h @@ -72,6 +72,7 @@ #define DRM_MODE_FLAG_3D_L_DEPTH_GFX_GFX_DEPTH (6<<14) #define DRM_MODE_FLAG_3D_TOP_AND_BOTTOM (7<<14) #define DRM_MODE_FLAG_3D_SIDE_BY_SIDE_HALF (8<<14) +#define DRM_MODE_FLAG_PREFER_ONE_SHOT (1<<19)
/* DPMS flags */
On Mon, May 11, 2015 at 09:38:20AM +0800, Mark Zhang wrote:
Normally this flag is set by panel driver so that crtc can enable the "one-shot" mode(not scan frames continuously).
Signed-off-by: Mark Zhang markz@nvidia.com
include/uapi/drm/drm_mode.h | 1 + 1 file changed, 1 insertion(+)
diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h index dbeba949462a..5447a338e893 100644 --- a/include/uapi/drm/drm_mode.h +++ b/include/uapi/drm/drm_mode.h @@ -72,6 +72,7 @@ #define DRM_MODE_FLAG_3D_L_DEPTH_GFX_GFX_DEPTH (6<<14) #define DRM_MODE_FLAG_3D_TOP_AND_BOTTOM (7<<14) #define DRM_MODE_FLAG_3D_SIDE_BY_SIDE_HALF (8<<14) +#define DRM_MODE_FLAG_PREFER_ONE_SHOT (1<<19)
tbh this doesn't sound like a mode flag, but something which should be attached to the drm_panel. Especially since all the single-frame modes are highly sink/link specific. Why was this added here instead of to the drm_panel metadata? -Daniel
I just want to make things easier. If we adding this in panel's meta data, it will be harder to make crtc gets this, since normally encoder talks with panel and crtc talks with encoder. But yes, adding this in panel's metadata makes more sense so if there is a better way to do that, I'm happy to do the changes.
Mark ________________________________________ From: Daniel Vetter daniel.vetter@ffwll.ch on behalf of Daniel Vetter daniel@ffwll.ch Sent: Monday, May 11, 2015 5:27 PM To: Mark Zhang Cc: thierry.reding@gmail.com; linux-tegra@vger.kernel.org; dri-devel@lists.freedesktop.org Subject: Re: [RFC PATCH 01/12] drm: Add a new mode flag: DRM_MODE_FLAG_PREFER_ONE_SHOT
On Mon, May 11, 2015 at 09:38:20AM +0800, Mark Zhang wrote:
Normally this flag is set by panel driver so that crtc can enable the "one-shot" mode(not scan frames continuously).
Signed-off-by: Mark Zhang markz@nvidia.com
include/uapi/drm/drm_mode.h | 1 + 1 file changed, 1 insertion(+)
diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h index dbeba949462a..5447a338e893 100644 --- a/include/uapi/drm/drm_mode.h +++ b/include/uapi/drm/drm_mode.h @@ -72,6 +72,7 @@ #define DRM_MODE_FLAG_3D_L_DEPTH_GFX_GFX_DEPTH (6<<14) #define DRM_MODE_FLAG_3D_TOP_AND_BOTTOM (7<<14) #define DRM_MODE_FLAG_3D_SIDE_BY_SIDE_HALF (8<<14) +#define DRM_MODE_FLAG_PREFER_ONE_SHOT (1<<19)
tbh this doesn't sound like a mode flag, but something which should be attached to the drm_panel. Especially since all the single-frame modes are highly sink/link specific. Why was this added here instead of to the drm_panel metadata? -Daniel -- Daniel Vetter Software Engineer, Intel Corporation http://blog.ffwll.ch
On Mon, May 11, 2015 at 04:34:57PM +0000, Mark Zhang wrote:
I just want to make things easier. If we adding this in panel's meta data, it will be harder to make crtc gets this, since normally encoder talks with panel and crtc talks with encoder. But yes, adding this in panel's metadata makes more sense so if there is a better way to do that, I'm happy to do the changes.
Adding something to the userspace ABI (which you've done here) because the kernel-internals are designed in an awkward way right now is definitely the wrong thing to do. With atomic you can easily add a bool prefer_oneshot to drm_crtc_state to encode this. But I fear that with the plain crtc helpers this just doesn't work properly. You could add a driver-private internal in drm_display_mode->private_flags, but that might clash with drivers existing use of this field.
In any way, this is definitely not something to add to uapi headers. Hence Nacked-by: me.
Thanks, Daniel
Mark ________________________________________ From: Daniel Vetter daniel.vetter@ffwll.ch on behalf of Daniel Vetter daniel@ffwll.ch Sent: Monday, May 11, 2015 5:27 PM To: Mark Zhang Cc: thierry.reding@gmail.com; linux-tegra@vger.kernel.org; dri-devel@lists.freedesktop.org Subject: Re: [RFC PATCH 01/12] drm: Add a new mode flag: DRM_MODE_FLAG_PREFER_ONE_SHOT
On Mon, May 11, 2015 at 09:38:20AM +0800, Mark Zhang wrote:
Normally this flag is set by panel driver so that crtc can enable the "one-shot" mode(not scan frames continuously).
Signed-off-by: Mark Zhang markz@nvidia.com
include/uapi/drm/drm_mode.h | 1 + 1 file changed, 1 insertion(+)
diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h index dbeba949462a..5447a338e893 100644 --- a/include/uapi/drm/drm_mode.h +++ b/include/uapi/drm/drm_mode.h @@ -72,6 +72,7 @@ #define DRM_MODE_FLAG_3D_L_DEPTH_GFX_GFX_DEPTH (6<<14) #define DRM_MODE_FLAG_3D_TOP_AND_BOTTOM (7<<14) #define DRM_MODE_FLAG_3D_SIDE_BY_SIDE_HALF (8<<14) +#define DRM_MODE_FLAG_PREFER_ONE_SHOT (1<<19)
tbh this doesn't sound like a mode flag, but something which should be attached to the drm_panel. Especially since all the single-frame modes are highly sink/link specific. Why was this added here instead of to the drm_panel metadata?
-Daniel
Daniel Vetter Software Engineer, Intel Corporation http://blog.ffwll.ch
On Tue, May 12, 2015 at 08:35:58AM +0200, Daniel Vetter wrote:
On Mon, May 11, 2015 at 04:34:57PM +0000, Mark Zhang wrote:
I just want to make things easier. If we adding this in panel's meta data, it will be harder to make crtc gets this, since normally encoder talks with panel and crtc talks with encoder. But yes, adding this in panel's metadata makes more sense so if there is a better way to do that, I'm happy to do the changes.
Adding something to the userspace ABI (which you've done here) because the kernel-internals are designed in an awkward way right now is definitely the wrong thing to do. With atomic you can easily add a bool prefer_oneshot to drm_crtc_state to encode this. But I fear that with the plain crtc helpers this just doesn't work properly. You could add a driver-private internal in drm_display_mode->private_flags, but that might clash with drivers existing use of this field.
In any way, this is definitely not something to add to uapi headers. Hence Nacked-by: me.
Are there use-cases where one-shot mode is worse than continuous mode? I'm thinking games that run at full FPS and such. If so, exposing this to userspace is perhaps not a bad idea, albeit not via a mode flag. If userspace had a way to set the preference, it could do so depending on use-case.
Thierry
From: Daniel Vetter daniel.vetter@ffwll.ch on behalf of Daniel Vetter daniel@ffwll.ch Sent: Monday, May 11, 2015 5:27 PM To: Mark Zhang Cc: thierry.reding@gmail.com; linux-tegra@vger.kernel.org; dri-devel@lists.freedesktop.org Subject: Re: [RFC PATCH 01/12] drm: Add a new mode flag: DRM_MODE_FLAG_PREFER_ONE_SHOT
On Mon, May 11, 2015 at 09:38:20AM +0800, Mark Zhang wrote:
Normally this flag is set by panel driver so that crtc can enable the "one-shot" mode(not scan frames continuously).
Signed-off-by: Mark Zhang markz@nvidia.com
include/uapi/drm/drm_mode.h | 1 + 1 file changed, 1 insertion(+)
diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h index dbeba949462a..5447a338e893 100644 --- a/include/uapi/drm/drm_mode.h +++ b/include/uapi/drm/drm_mode.h @@ -72,6 +72,7 @@ #define DRM_MODE_FLAG_3D_L_DEPTH_GFX_GFX_DEPTH (6<<14) #define DRM_MODE_FLAG_3D_TOP_AND_BOTTOM (7<<14) #define DRM_MODE_FLAG_3D_SIDE_BY_SIDE_HALF (8<<14) +#define DRM_MODE_FLAG_PREFER_ONE_SHOT (1<<19)
tbh this doesn't sound like a mode flag, but something which should be attached to the drm_panel. Especially since all the single-frame modes are highly sink/link specific. Why was this added here instead of to the drm_panel metadata?
-Daniel
Daniel Vetter Software Engineer, Intel Corporation http://blog.ffwll.ch
-- Daniel Vetter Software Engineer, Intel Corporation http://blog.ffwll.ch
On Tue, May 12, 2015 at 10:23 AM, Thierry Reding thierry.reding@gmail.com wrote:
On Tue, May 12, 2015 at 08:35:58AM +0200, Daniel Vetter wrote:
On Mon, May 11, 2015 at 04:34:57PM +0000, Mark Zhang wrote:
I just want to make things easier. If we adding this in panel's meta data, it will be harder to make crtc gets this, since normally encoder talks with panel and crtc talks with encoder. But yes, adding this in panel's metadata makes more sense so if there is a better way to do that, I'm happy to do the changes.
Adding something to the userspace ABI (which you've done here) because the kernel-internals are designed in an awkward way right now is definitely the wrong thing to do. With atomic you can easily add a bool prefer_oneshot to drm_crtc_state to encode this. But I fear that with the plain crtc helpers this just doesn't work properly. You could add a driver-private internal in drm_display_mode->private_flags, but that might clash with drivers existing use of this field.
In any way, this is definitely not something to add to uapi headers. Hence Nacked-by: me.
Are there use-cases where one-shot mode is worse than continuous mode? I'm thinking games that run at full FPS and such. If so, exposing this to userspace is perhaps not a bad idea, albeit not via a mode flag. If userspace had a way to set the preference, it could do so depending on use-case.
If you have a case with jitter when the pageflips run at not-quite 60fps (just an example) then I think the kernel should cope with that by disabling oneshot mode on its own. At least that's what i915 does. Different story once we'll get variable refresh-rate support, but I think that's an entirely different beast.
Also we don't need this as a mode flag, an atomic prop seems better suited. Especially since the atomic prop avoids the mode_changed logic of the helpers (which we want, no need to flicker when updating such a hint), wheres anything that changes the mode will force a full modeset by default.
Also if you expose this to userspace, you need userspace to use it before merging.
drm_display_mode->private_flags still looks like the most suitable place. -Daniel
Sharp lq101r1sx01 has internal framebuffer so it doesn't require crtc sending frames to it continuously. So set the one-shot flag in the driver.
Signed-off-by: Mark Zhang markz@nvidia.com --- drivers/gpu/drm/panel/panel-sharp-lq101r1sx01.c | 1 + 1 file changed, 1 insertion(+)
diff --git a/drivers/gpu/drm/panel/panel-sharp-lq101r1sx01.c b/drivers/gpu/drm/panel/panel-sharp-lq101r1sx01.c index 3cce3ca19601..19a67d2598c0 100644 --- a/drivers/gpu/drm/panel/panel-sharp-lq101r1sx01.c +++ b/drivers/gpu/drm/panel/panel-sharp-lq101r1sx01.c @@ -283,6 +283,7 @@ static const struct drm_display_mode default_mode = { .vsync_start = 1600 + 4, .vsync_end = 1600 + 4 + 8, .vtotal = 1600 + 4 + 8 + 32, + .flags = DRM_MODE_FLAG_PREFER_ONE_SHOT, .vrefresh = 60, };
Signed-off-by: Mark Zhang markz@nvidia.com --- drivers/gpu/drm/panel/panel-sharp-lq101r1sx01.c | 7 +++++++ 1 file changed, 7 insertions(+)
diff --git a/drivers/gpu/drm/panel/panel-sharp-lq101r1sx01.c b/drivers/gpu/drm/panel/panel-sharp-lq101r1sx01.c index 19a67d2598c0..b69f88cd15b2 100644 --- a/drivers/gpu/drm/panel/panel-sharp-lq101r1sx01.c +++ b/drivers/gpu/drm/panel/panel-sharp-lq101r1sx01.c @@ -238,6 +238,13 @@ static int sharp_panel_prepare(struct drm_panel *panel) goto poweroff; }
+ err = mipi_dsi_dcs_set_tear_on(sharp->link1, + MIPI_DSI_DCS_TEAR_MODE_VBLANK); + if (err < 0) { + dev_err(panel->dev, "failed to turn on TE: %d\n", err); + goto poweroff; + } + err = mipi_dsi_dcs_set_display_on(sharp->link1); if (err < 0) { dev_err(panel->dev, "failed to set display on: %d\n", err);
Add 2 DRM mode flag: TE polarity high/low.
Signed-off-by: Mark Zhang markz@nvidia.com --- include/uapi/drm/drm_mode.h | 2 ++ 1 file changed, 2 insertions(+)
diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h index 5447a338e893..b577cdfb76df 100644 --- a/include/uapi/drm/drm_mode.h +++ b/include/uapi/drm/drm_mode.h @@ -73,6 +73,8 @@ #define DRM_MODE_FLAG_3D_TOP_AND_BOTTOM (7<<14) #define DRM_MODE_FLAG_3D_SIDE_BY_SIDE_HALF (8<<14) #define DRM_MODE_FLAG_PREFER_ONE_SHOT (1<<19) +#define DRM_MODE_FLAG_TE_POLARITY_HIGH (1<<20) +#define DRM_MODE_FLAG_TE_POLARITY_LOW (1<<21)
/* DPMS flags */
te-polarity indicates the polarity of panel's TE(Tearing Effect) signal. Normally the TE pin is connected to the host SoC. The display controller will send a new frame to panel when the TE signal is triggered.
Signed-off-by: Mark Zhang markz@nvidia.com --- Documentation/devicetree/bindings/panel/sharp,lq101r1sx01.txt | 2 ++ arch/arm/boot/dts/tegra114-dalmore.dts | 2 ++ 2 files changed, 4 insertions(+)
diff --git a/Documentation/devicetree/bindings/panel/sharp,lq101r1sx01.txt b/Documentation/devicetree/bindings/panel/sharp,lq101r1sx01.txt index f522bb8e47e1..680ebec9a927 100644 --- a/Documentation/devicetree/bindings/panel/sharp,lq101r1sx01.txt +++ b/Documentation/devicetree/bindings/panel/sharp,lq101r1sx01.txt @@ -23,6 +23,7 @@ Required properties (for DSI-LINK1 only): - link2: phandle to the DSI peripheral on the secondary link. Note that the presence of this property marks the containing node as DSI-LINK1. - power-supply: phandle of the regulator that provides the supply voltage +- te-polarity: indicates the TE(Tearing Effect) polarity. 0: Low, 1: High.
Optional properties (for DSI-LINK1 only): - backlight: phandle of the backlight device attached to the panel @@ -38,6 +39,7 @@ Example:
power-supply = <...>; backlight = <...>; + te-polarity = <0>; }; };
diff --git a/arch/arm/boot/dts/tegra114-dalmore.dts b/arch/arm/boot/dts/tegra114-dalmore.dts index 8b7aa0dcdc6e..fdb1cc4063a9 100644 --- a/arch/arm/boot/dts/tegra114-dalmore.dts +++ b/arch/arm/boot/dts/tegra114-dalmore.dts @@ -47,6 +47,8 @@
power-supply = <&avdd_lcd_reg>; backlight = <&backlight>; + + te-polarity = <0>; /* TE_POLARITY_LOW */ }; }; };
Signed-off-by: Mark Zhang markz@nvidia.com --- drivers/gpu/drm/panel/panel-sharp-lq101r1sx01.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/panel/panel-sharp-lq101r1sx01.c b/drivers/gpu/drm/panel/panel-sharp-lq101r1sx01.c index b69f88cd15b2..654575607864 100644 --- a/drivers/gpu/drm/panel/panel-sharp-lq101r1sx01.c +++ b/drivers/gpu/drm/panel/panel-sharp-lq101r1sx01.c @@ -27,6 +27,7 @@ struct sharp_panel {
struct backlight_device *backlight; struct regulator *supply; + u32 te_polarity;
bool prepared; bool enabled; @@ -280,7 +281,7 @@ static int sharp_panel_enable(struct drm_panel *panel) return 0; }
-static const struct drm_display_mode default_mode = { +static struct drm_display_mode default_mode = { .clock = 278000, .hdisplay = 2560, .hsync_start = 2560 + 128, @@ -297,6 +298,12 @@ static const struct drm_display_mode default_mode = { static int sharp_panel_get_modes(struct drm_panel *panel) { struct drm_display_mode *mode; + struct sharp_panel *sharp = to_sharp_panel(panel); + + if (sharp->te_polarity) + default_mode.flags |= DRM_MODE_FLAG_TE_POLARITY_HIGH; + else + default_mode.flags |= DRM_MODE_FLAG_TE_POLARITY_LOW;
mode = drm_mode_duplicate(panel->drm, &default_mode); if (!mode) { @@ -341,6 +348,14 @@ static int sharp_panel_add(struct sharp_panel *sharp) if (IS_ERR(sharp->supply)) return PTR_ERR(sharp->supply);
+ err = of_property_read_u32(sharp->link1->dev.of_node, + "te-polarity", &sharp->te_polarity); + if (err < 0) { + dev_err(&sharp->link1->dev, + "Read te-polarity failed: %d\n", err); + return err; + } + np = of_parse_phandle(sharp->link1->dev.of_node, "backlight", 0); if (np) { sharp->backlight = of_find_backlight_by_node(np);
If dc is about to work in one-shot mode, we need to set dc's scan mode to NC(Non-contiguous). There are 2 things which can make dc send frame again: - TE signal is received - Driver sets the NC_HOST_TRIG_ENABLE
Signed-off-by: Mark Zhang markz@nvidia.com --- drivers/gpu/drm/tegra/dc.c | 43 +++++++++++++++++++++++++++++-------------- drivers/gpu/drm/tegra/dc.h | 5 +++++ 2 files changed, 34 insertions(+), 14 deletions(-)
diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index a287e4fec865..b88c29322c6f 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -1248,10 +1248,26 @@ static void tegra_crtc_mode_set_nofb(struct drm_crtc *crtc) tegra_dc_writel(dc, value, DC_DISP_INTERLACE_CONTROL); }
- value = tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND); - value &= ~DISP_CTRL_MODE_MASK; - value |= DISP_CTRL_MODE_C_DISPLAY; - tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND); + if (mode->flags & DRM_MODE_FLAG_PREFER_ONE_SHOT) { + /* enable MSF & set MSF polarity */ + value = MSF_ENABLE | MSF_LSPI; + if (mode->flags & DRM_MODE_FLAG_TE_POLARITY_HIGH) + value |= MSF_POLARITY_HIGH; + else + value |= MSF_POLARITY_LOW; + tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND_OPTION0); + + /* set non-continuous mode */ + value = tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND); + value &= ~DISP_CTRL_MODE_MASK; + value |= DISP_CTRL_MODE_NC_DISPLAY; + tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND); + } else { + value = tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND); + value &= ~DISP_CTRL_MODE_MASK; + value |= DISP_CTRL_MODE_C_DISPLAY; + tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND); + }
value = tegra_dc_readl(dc, DC_CMD_DISPLAY_POWER_CONTROL); value |= PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE | @@ -1319,16 +1335,11 @@ static irqreturn_t tegra_dc_irq(int irq, void *data) status = tegra_dc_readl(dc, DC_CMD_INT_STATUS); tegra_dc_writel(dc, status, DC_CMD_INT_STATUS);
- if (status & FRAME_END_INT) { - /* - dev_dbg(dc->dev, "%s(): frame end\n", __func__); - */ - } + if (status & FRAME_END_INT) + dev_info(dc->dev, "%s(): frame end\n", __func__);
if (status & VBLANK_INT) { - /* - dev_dbg(dc->dev, "%s(): vertical blank\n", __func__); - */ + dev_info(dc->dev, "%s(): vertical blank\n", __func__); drm_crtc_handle_vblank(&dc->base); tegra_dc_finish_page_flip(dc); } @@ -1339,6 +1350,9 @@ static irqreturn_t tegra_dc_irq(int irq, void *data) */ }
+ if (status & MSF_INT) + dev_info(dc->dev, "MSF_INT received.\n"); + return IRQ_HANDLED; }
@@ -1732,10 +1746,11 @@ static int tegra_dc_init(struct host1x_client *client) WINDOW_B_THRESHOLD(1) | WINDOW_C_THRESHOLD(1); tegra_dc_writel(dc, value, DC_DISP_DISP_MEM_HIGH_PRIORITY_TIMER);
- value = VBLANK_INT | WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT; + value = VBLANK_INT | WIN_A_UF_INT | WIN_B_UF_INT | + WIN_C_UF_INT | MSF_INT; tegra_dc_writel(dc, value, DC_CMD_INT_ENABLE);
- value = WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT; + value = WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT | MSF_INT; tegra_dc_writel(dc, value, DC_CMD_INT_MASK);
if (dc->soc->supports_border_color) diff --git a/drivers/gpu/drm/tegra/dc.h b/drivers/gpu/drm/tegra/dc.h index 55792daabbb5..4a2d0fec5853 100644 --- a/drivers/gpu/drm/tegra/dc.h +++ b/drivers/gpu/drm/tegra/dc.h @@ -27,6 +27,10 @@ #define DC_CMD_CONT_SYNCPT_VSYNC 0x028 #define SYNCPT_VSYNC_ENABLE (1 << 8) #define DC_CMD_DISPLAY_COMMAND_OPTION0 0x031 +#define MSF_ENABLE (1 << 1) +#define MSF_LSPI (0 << 2) +#define MSF_POLARITY_HIGH (0 << 0) +#define MSF_POLARITY_LOW (1 << 0) #define DC_CMD_DISPLAY_COMMAND 0x032 #define DISP_CTRL_MODE_STOP (0 << 5) #define DISP_CTRL_MODE_C_DISPLAY (1 << 5) @@ -53,6 +57,7 @@ #define WIN_A_UF_INT (1 << 8) #define WIN_B_UF_INT (1 << 9) #define WIN_C_UF_INT (1 << 10) +#define MSF_INT (1 << 12) #define WIN_A_OF_INT (1 << 14) #define WIN_B_OF_INT (1 << 15) #define WIN_C_OF_INT (1 << 16)
The "idle" function of a drm panel is used to tell panel there are no more frames coming in and it should remain the last frame it gets. Normally this only makes sense for smart panels which has internal framebuffer.
The "busy" function is opposite to "idle".
Signed-off-by: Mark Zhang markz@nvidia.com --- include/drm/drm_panel.h | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+)
diff --git a/include/drm/drm_panel.h b/include/drm/drm_panel.h index 13ff44b28893..0025cd020c40 100644 --- a/include/drm/drm_panel.h +++ b/include/drm/drm_panel.h @@ -66,6 +66,8 @@ struct display_timing; * the panel. This is the job of the .unprepare() function. */ struct drm_panel_funcs { + int (*idle)(struct drm_panel *panel); + int (*busy)(struct drm_panel *panel); int (*disable)(struct drm_panel *panel); int (*unprepare)(struct drm_panel *panel); int (*prepare)(struct drm_panel *panel); @@ -85,6 +87,22 @@ struct drm_panel { struct list_head list; };
+static inline int drm_panel_idle(struct drm_panel *panel) +{ + if (panel && panel->funcs && panel->funcs->idle) + return panel->funcs->idle(panel); + + return panel ? -ENOSYS : -EINVAL; +} + +static inline int drm_panel_busy(struct drm_panel *panel) +{ + if (panel && panel->funcs && panel->funcs->busy) + return panel->funcs->busy(panel); + + return panel ? -ENOSYS : -EINVAL; +} + static inline int drm_panel_unprepare(struct drm_panel *panel) { if (panel && panel->funcs && panel->funcs->unprepare)
Signed-off-by: Mark Zhang markz@nvidia.com --- drivers/gpu/drm/drm_mipi_dsi.c | 36 ++++++++++++++++++++++++++++++++++++ include/drm/drm_mipi_dsi.h | 2 ++ 2 files changed, 38 insertions(+)
diff --git a/drivers/gpu/drm/drm_mipi_dsi.c b/drivers/gpu/drm/drm_mipi_dsi.c index 2d5ca8eec13a..9bc6ff75eb8f 100644 --- a/drivers/gpu/drm/drm_mipi_dsi.c +++ b/drivers/gpu/drm/drm_mipi_dsi.c @@ -862,6 +862,42 @@ int mipi_dsi_dcs_set_pixel_format(struct mipi_dsi_device *dsi, u8 format) } EXPORT_SYMBOL(mipi_dsi_dcs_set_pixel_format);
+/** + * mipi_dsi_dcs_enter_idle_mode() + * @dsi: DSI peripheral device + * + * Return: 0 on success or a negative error code on failure. + */ +int mipi_dsi_dcs_enter_idle_mode(struct mipi_dsi_device *dsi) +{ + ssize_t err; + + err = mipi_dsi_dcs_write(dsi, MIPI_DCS_ENTER_IDLE_MODE, NULL, 0); + if (err < 0) + return err; + + return 0; +} +EXPORT_SYMBOL(mipi_dsi_dcs_enter_idle_mode); + +/** + * mipi_dsi_dcs_exit_idle_mode() + * @dsi: DSI peripheral device + * + * Return: 0 on success or a negative error code on failure. + */ +int mipi_dsi_dcs_exit_idle_mode(struct mipi_dsi_device *dsi) +{ + ssize_t err; + + err = mipi_dsi_dcs_write(dsi, MIPI_DCS_EXIT_IDLE_MODE, NULL, 0); + if (err < 0) + return err; + + return 0; +} +EXPORT_SYMBOL(mipi_dsi_dcs_exit_idle_mode); + static int mipi_dsi_drv_probe(struct device *dev) { struct mipi_dsi_driver *drv = to_mipi_dsi_driver(dev->driver); diff --git a/include/drm/drm_mipi_dsi.h b/include/drm/drm_mipi_dsi.h index f1d8d0dbb4f1..d949a8ef389f 100644 --- a/include/drm/drm_mipi_dsi.h +++ b/include/drm/drm_mipi_dsi.h @@ -214,6 +214,8 @@ int mipi_dsi_dcs_set_tear_off(struct mipi_dsi_device *dsi); int mipi_dsi_dcs_set_tear_on(struct mipi_dsi_device *dsi, enum mipi_dsi_dcs_tear_mode mode); int mipi_dsi_dcs_set_pixel_format(struct mipi_dsi_device *dsi, u8 format); +int mipi_dsi_dcs_enter_idle_mode(struct mipi_dsi_device *dsi); +int mipi_dsi_dcs_exit_idle_mode(struct mipi_dsi_device *dsi);
/** * struct mipi_dsi_driver - DSI driver
Signed-off-by: Mark Zhang markz@nvidia.com --- drivers/gpu/drm/panel/panel-sharp-lq101r1sx01.c | 26 +++++++++++++++++++++++++ 1 file changed, 26 insertions(+)
diff --git a/drivers/gpu/drm/panel/panel-sharp-lq101r1sx01.c b/drivers/gpu/drm/panel/panel-sharp-lq101r1sx01.c index 654575607864..a0a7c80f23d6 100644 --- a/drivers/gpu/drm/panel/panel-sharp-lq101r1sx01.c +++ b/drivers/gpu/drm/panel/panel-sharp-lq101r1sx01.c @@ -90,6 +90,18 @@ static __maybe_unused int sharp_panel_read(struct sharp_panel *sharp, return err; }
+static int sharp_panel_idle(struct drm_panel *panel) +{ + struct sharp_panel *sharp = to_sharp_panel(panel); + int err = 0; + + err = mipi_dsi_dcs_enter_idle_mode(sharp->link1); + if (err < 0) + dev_err(panel->dev, "failed to enter idle: %d\n", err); + + return err; +} + static int sharp_panel_disable(struct drm_panel *panel) { struct sharp_panel *sharp = to_sharp_panel(panel); @@ -168,6 +180,18 @@ static int sharp_setup_symmetrical_split(struct mipi_dsi_device *left, return 0; }
+static int sharp_panel_busy(struct drm_panel *panel) +{ + struct sharp_panel *sharp = to_sharp_panel(panel); + int err = 0; + + err = mipi_dsi_dcs_exit_idle_mode(sharp->link1); + if (err < 0) + dev_err(panel->dev, "failed to exit idle: %d\n", err); + + return err; +} + static int sharp_panel_prepare(struct drm_panel *panel) { struct sharp_panel *sharp = to_sharp_panel(panel); @@ -324,6 +348,8 @@ static int sharp_panel_get_modes(struct drm_panel *panel) }
static const struct drm_panel_funcs sharp_panel_funcs = { + .idle = sharp_panel_idle, + .busy = sharp_panel_busy, .disable = sharp_panel_disable, .unprepare = sharp_panel_unprepare, .prepare = sharp_panel_prepare,
Signed-off-by: Mark Zhang markz@nvidia.com --- drivers/gpu/drm/tegra/dc.c | 34 ++++++++++++++++++++++++ drivers/gpu/drm/tegra/drm.h | 3 +++ drivers/gpu/drm/tegra/dsi.c | 63 +++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 95 insertions(+), 5 deletions(-)
diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index b88c29322c6f..b8231e2e3c92 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -1330,6 +1330,7 @@ static const struct drm_crtc_helper_funcs tegra_crtc_helper_funcs = { static irqreturn_t tegra_dc_irq(int irq, void *data) { struct tegra_dc *dc = data; + struct drm_display_mode *mode = &dc->base.state->adjusted_mode; unsigned long status;
status = tegra_dc_readl(dc, DC_CMD_INT_STATUS); @@ -1342,6 +1343,9 @@ static irqreturn_t tegra_dc_irq(int irq, void *data) dev_info(dc->dev, "%s(): vertical blank\n", __func__); drm_crtc_handle_vblank(&dc->base); tegra_dc_finish_page_flip(dc); + + if (mode->flags & DRM_MODE_FLAG_PREFER_ONE_SHOT) + schedule_work(&dc->one_shot_work); }
if (status & (WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT)) { @@ -1895,6 +1899,35 @@ static int tegra_dc_parse_dt(struct tegra_dc *dc) return 0; }
+static void tegra_dc_one_shot_work(struct work_struct *work) +{ + struct tegra_dc *dc; + struct drm_connector *connector; + struct drm_device *drm; + + dc = container_of(work, struct tegra_dc, one_shot_work); + drm = dc->base.dev; + + dev_info(dc->dev, "one-shot: Suspend encoder & connector.\n"); + drm_modeset_lock_all(drm); + list_for_each_entry(connector, &drm->mode_config.connector_list, head) { + if (connector->funcs->dpms) + connector->funcs->dpms(connector, + DRM_MODE_DPMS_SUSPEND); + } + drm_modeset_unlock_all(drm); + + dev_info(dc->dev, "one-shot: Suspend dc.\n"); + /* Stop dc since dc doesn't have dpms functions */ + tegra_dc_stop(dc); + clk_disable_unprepare(dc->clk); + + /* + * TODO: Powergate dc. This requires we re-init all stuffs + * next time we want to trigger the one-shot. + */ +} + static int tegra_dc_probe(struct platform_device *pdev) { unsigned long flags = HOST1X_SYNCPT_CLIENT_MANAGED; @@ -1913,6 +1946,7 @@ static int tegra_dc_probe(struct platform_device *pdev)
spin_lock_init(&dc->lock); INIT_LIST_HEAD(&dc->list); + INIT_WORK(&dc->one_shot_work, tegra_dc_one_shot_work); dc->dev = &pdev->dev; dc->soc = id->data;
diff --git a/drivers/gpu/drm/tegra/drm.h b/drivers/gpu/drm/tegra/drm.h index 659b2fcc986d..00daf427c831 100644 --- a/drivers/gpu/drm/tegra/drm.h +++ b/drivers/gpu/drm/tegra/drm.h @@ -12,6 +12,7 @@
#include <uapi/drm/tegra_drm.h> #include <linux/host1x.h> +#include <linux/workqueue.h>
#include <drm/drmP.h> #include <drm/drm_crtc_helper.h> @@ -130,6 +131,8 @@ struct tegra_dc { /* page-flip handling */ struct drm_pending_vblank_event *event;
+ struct work_struct one_shot_work; + const struct tegra_dc_soc_info *soc;
struct iommu_domain *domain; diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index ed970f622903..7e00279982b7 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -726,10 +726,6 @@ static void tegra_dsi_soft_reset(struct tegra_dsi *dsi) tegra_dsi_soft_reset(dsi->slave); }
-static void tegra_dsi_connector_dpms(struct drm_connector *connector, int mode) -{ -} - static void tegra_dsi_connector_reset(struct drm_connector *connector) { struct tegra_dsi_state *state; @@ -756,7 +752,7 @@ tegra_dsi_connector_duplicate_state(struct drm_connector *connector) }
static const struct drm_connector_funcs tegra_dsi_connector_funcs = { - .dpms = tegra_dsi_connector_dpms, + .dpms = drm_helper_connector_dpms, .reset = tegra_dsi_connector_reset, .detect = tegra_output_connector_detect, .fill_modes = drm_helper_probe_single_connector_modes, @@ -784,6 +780,63 @@ static const struct drm_encoder_funcs tegra_dsi_encoder_funcs = {
static void tegra_dsi_encoder_dpms(struct drm_encoder *encoder, int mode) { + struct tegra_output *output = encoder_to_output(encoder); + struct drm_crtc *crtc = encoder->crtc; + struct tegra_dc *dc = to_tegra_dc(encoder->crtc); + struct tegra_dsi *dsi = to_dsi(output); + struct tegra_dsi_state *state; + u32 value; + int err; + + if (mode == DRM_MODE_DPMS_SUSPEND) { + tegra_dsi_video_disable(dsi); + + if (dc) { + value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS); + value &= ~DSI_ENABLE; + tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS); + + tegra_dc_commit(dc); + } + + err = tegra_dsi_wait_idle(dsi, 100); + if (err < 0) + dev_dbg(dsi->dev, "failed to idle DSI: %d\n", err); + + tegra_dsi_soft_reset(dsi); + + if (output->panel) + drm_panel_idle(output->panel); + + tegra_dsi_disable(dsi); + } + + if (mode == DRM_MODE_DPMS_STANDBY) { + state = tegra_dsi_get_state(dsi); + + tegra_dsi_set_timeout(dsi, state->bclk, state->vrefresh); + + /* + * The D-PHY timing fields are expressed in byte-clock cycles, so + * multiply the period by 8. + */ + tegra_dsi_set_phy_timing(dsi, state->period * 8, &state->timing); + + if (output->panel) + drm_panel_busy(output->panel); + + tegra_dsi_configure(dsi, dc->pipe, &crtc->mode); + + /* enable display controller */ + value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS); + value |= DSI_ENABLE; + tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS); + + tegra_dc_commit(dc); + + /* enable DSI controller */ + tegra_dsi_enable(dsi); + } }
static void tegra_dsi_encoder_prepare(struct drm_encoder *encoder)
This HACK adds a workqueue to refresh the display periodically. This is used just for testing.
Signed-off-by: Mark Zhang markz@nvidia.com --- drivers/gpu/drm/tegra/dc.c | 47 +++++++++++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/tegra/drm.h | 1 + 2 files changed, 48 insertions(+)
diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index b8231e2e3c92..48bddc795995 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -1262,6 +1262,8 @@ static void tegra_crtc_mode_set_nofb(struct drm_crtc *crtc) value &= ~DISP_CTRL_MODE_MASK; value |= DISP_CTRL_MODE_NC_DISPLAY; tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND); + + schedule_work(&dc->one_shot_trigger); } else { value = tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND); value &= ~DISP_CTRL_MODE_MASK; @@ -1928,6 +1930,50 @@ static void tegra_dc_one_shot_work(struct work_struct *work) */ }
+static void tegra_dc_one_shot_trigger(struct work_struct *work) +{ + struct tegra_dc *dc; + struct drm_connector *connector; + struct drm_device *drm; + unsigned long update_mask = GENERAL_ACT_REQ | NC_HOST_TRIG; + static int first_trigger = 1; + int err; + unsigned long value; + + dc = container_of(work, struct tegra_dc, one_shot_trigger); + drm = dc->base.dev; + msleep(5000); + + if (first_trigger) { + tegra_dc_writel(dc, update_mask, DC_CMD_STATE_CONTROL); + first_trigger = 0; + schedule_work(&dc->one_shot_trigger); + return; + } + + dev_info(dc->dev, "one-shot: Wakeup dsi/panel.\n"); + err = clk_prepare_enable(dc->clk); + if (err < 0) + dev_err(dc->dev, "failed to enable clock: %d\n", err); + + value = tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND); + value &= ~DISP_CTRL_MODE_MASK; + value |= DISP_CTRL_MODE_NC_DISPLAY; + tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND); + + drm_modeset_lock_all(drm); + list_for_each_entry(connector, &drm->mode_config.connector_list, head) { + if (connector->funcs->dpms) + connector->funcs->dpms(connector, + DRM_MODE_DPMS_STANDBY); + } + drm_modeset_unlock_all(drm); + + /* Trigger the one-shot */ + tegra_dc_writel(dc, update_mask, DC_CMD_STATE_CONTROL); + schedule_work(&dc->one_shot_trigger); +} + static int tegra_dc_probe(struct platform_device *pdev) { unsigned long flags = HOST1X_SYNCPT_CLIENT_MANAGED; @@ -1947,6 +1993,7 @@ static int tegra_dc_probe(struct platform_device *pdev) spin_lock_init(&dc->lock); INIT_LIST_HEAD(&dc->list); INIT_WORK(&dc->one_shot_work, tegra_dc_one_shot_work); + INIT_WORK(&dc->one_shot_trigger, tegra_dc_one_shot_trigger); dc->dev = &pdev->dev; dc->soc = id->data;
diff --git a/drivers/gpu/drm/tegra/drm.h b/drivers/gpu/drm/tegra/drm.h index 00daf427c831..5d606cacb098 100644 --- a/drivers/gpu/drm/tegra/drm.h +++ b/drivers/gpu/drm/tegra/drm.h @@ -132,6 +132,7 @@ struct tegra_dc { struct drm_pending_vblank_event *event;
struct work_struct one_shot_work; + struct work_struct one_shot_trigger;
const struct tegra_dc_soc_info *soc;
dri-devel@lists.freedesktop.org