This patch set adds the Tegra dc one-shot support. The patch set is tested on Dalmore + Sharp lq101r1sx01.
Please be noticed that the patch #12 is not part of the feature, it's just used for testing.
Changes in v2: - Define one-shot flag in drm_display_mode->private_flags, according to Daniel's comments - Remove the "te-polarity" property in dts. The TE polarity is determined by panel so there is not necessary to define it in DTS. We can define it in panel driver. - Rebased the patch series on top of 6/29 linux-next. - Fix a bug in DSI suspend DPMS event, to wait DSI idle before suspend - Fix a bug in DSI driver to correct the DPMS state(patch #11)
Mark Zhang (12): drm: panel: Add a new private mode flag: DRM_PANEL_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: panel: Add DRM panel private mode flag: TE polarity drm: panel: Set TE polarity flag 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 drm/tegra: dsi: Set connector DPMS state when enable/disable JUST FOR TEST: Add one-shot trigger to update display
drivers/gpu/drm/drm_mipi_dsi.c | 36 +++++++ drivers/gpu/drm/panel/panel-sharp-lq101r1sx01.c | 35 +++++++ drivers/gpu/drm/tegra/dc.c | 128 +++++++++++++++++++++-- drivers/gpu/drm/tegra/dc.h | 5 + drivers/gpu/drm/tegra/drm.h | 4 + drivers/gpu/drm/tegra/dsi.c | 79 +++++++++++++- include/drm/drm_mipi_dsi.h | 2 + include/drm/drm_panel.h | 22 ++++ 8 files changed, 300 insertions(+), 11 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/drm/drm_panel.h | 2 ++ 1 file changed, 2 insertions(+)
diff --git a/include/drm/drm_panel.h b/include/drm/drm_panel.h index 13ff44b28893..4d51cb380c75 100644 --- a/include/drm/drm_panel.h +++ b/include/drm/drm_panel.h @@ -26,6 +26,8 @@
#include <linux/list.h>
+#define DRM_PANEL_FLAG_PREFER_ONE_SHOT (1 << 0) + struct drm_connector; struct drm_device; struct drm_panel;
On Wed, Jul 01, 2015 at 04:21:44PM +0800, Mark Zhang wrote:
This is a panel property, not a mode property. I think it would be much better to put this directly into the panel struct, or maybe the dsi sink device stuff or wherever. But mode really doesn't have anything to do how exactly the pixels get to the panel. -Daniel
On 07/01/2015 04:54 PM, Daniel Vetter wrote:
Yeah, we've talked about this in patch set v1. I think putting this in drm_display_mode->private_flags already makes things better. If we want to completely remove this in drm_display_mode, I think we need to create some mechanism which panel can use to notify crtc to enable the one-shot mode, or crtc is able to send out a query, if somebody answers, one-shot will be enabled.
Mark
-Daniel
Sharp lq101r1sx01 has internal framebuffer so it doesn't require crtc sending frames to it continuously.
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..da2cf7ab64c2 100644 --- a/drivers/gpu/drm/panel/panel-sharp-lq101r1sx01.c +++ b/drivers/gpu/drm/panel/panel-sharp-lq101r1sx01.c @@ -284,6 +284,7 @@ static const struct drm_display_mode default_mode = { .vsync_end = 1600 + 4 + 8, .vtotal = 1600 + 4 + 8 + 32, .vrefresh = 60, + .private_flags = DRM_PANEL_FLAG_PREFER_ONE_SHOT, };
static int sharp_panel_get_modes(struct drm_panel *panel)
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 da2cf7ab64c2..fd04c419190c 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 panel private mode flag: TE polarity high/low.
Signed-off-by: Mark Zhang markz@nvidia.com --- include/drm/drm_panel.h | 2 ++ 1 file changed, 2 insertions(+)
diff --git a/include/drm/drm_panel.h b/include/drm/drm_panel.h index 4d51cb380c75..e53f48aa070f 100644 --- a/include/drm/drm_panel.h +++ b/include/drm/drm_panel.h @@ -27,6 +27,8 @@ #include <linux/list.h>
#define DRM_PANEL_FLAG_PREFER_ONE_SHOT (1 << 0) +#define DRM_PANEL_FLAG_TE_POLARITY_HIGH (1 << 1) +#define DRM_PANEL_FLAG_TE_POLARITY_LOW (1 << 2)
struct drm_connector; struct drm_device;
Signed-off-by: Mark Zhang markz@nvidia.com --- drivers/gpu/drm/panel/panel-sharp-lq101r1sx01.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/panel/panel-sharp-lq101r1sx01.c b/drivers/gpu/drm/panel/panel-sharp-lq101r1sx01.c index fd04c419190c..e32f1449b067 100644 --- a/drivers/gpu/drm/panel/panel-sharp-lq101r1sx01.c +++ b/drivers/gpu/drm/panel/panel-sharp-lq101r1sx01.c @@ -291,7 +291,8 @@ static const struct drm_display_mode default_mode = { .vsync_end = 1600 + 4 + 8, .vtotal = 1600 + 4 + 8 + 32, .vrefresh = 60, - .private_flags = DRM_PANEL_FLAG_PREFER_ONE_SHOT, + .private_flags = DRM_PANEL_FLAG_PREFER_ONE_SHOT | + DRM_PANEL_FLAG_TE_POLARITY_LOW, };
static int sharp_panel_get_modes(struct drm_panel *panel)
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 | 33 +++++++++++++++++++++++++++------ drivers/gpu/drm/tegra/dc.h | 5 +++++ 2 files changed, 32 insertions(+), 6 deletions(-)
diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index a287e4fec865..2fdbed9b2b04 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -18,6 +18,7 @@ #include "drm.h" #include "gem.h"
+#include <drm/drm_panel.h> #include <drm/drm_atomic.h> #include <drm/drm_atomic_helper.h> #include <drm/drm_plane_helper.h> @@ -1248,10 +1249,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->private_flags & DRM_PANEL_FLAG_PREFER_ONE_SHOT) { + /* enable MSF & set MSF polarity */ + value = MSF_ENABLE | MSF_LSPI; + if (mode->private_flags & DRM_PANEL_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 | @@ -1339,6 +1356,9 @@ static irqreturn_t tegra_dc_irq(int irq, void *data) */ }
+ if (status & MSF_INT) + dev_dbg(dc->dev, "MSF_INT received.\n"); + return IRQ_HANDLED; }
@@ -1732,10 +1752,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 e53f48aa070f..77da292ad2fb 100644 --- a/include/drm/drm_panel.h +++ b/include/drm/drm_panel.h @@ -70,6 +70,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); @@ -89,6 +91,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
On 07/01/2015 01:51 PM, Mark Zhang wrote:
This we can do simply as: return mipi_dsi_dcs_write(dsi, MIPI_DCS_ENTER_IDLE_MODE, NULL, 0);
For this one also: return mipi_dsi_dcs_write(dsi, MIPI_DCS_EXIT_IDLE_MODE, NULL, 0);
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 e32f1449b067..64eb437ee7b3 100644 --- a/drivers/gpu/drm/panel/panel-sharp-lq101r1sx01.c +++ b/drivers/gpu/drm/panel/panel-sharp-lq101r1sx01.c @@ -89,6 +89,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); @@ -167,6 +179,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); @@ -318,6 +342,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 | 58 +++++++++++++++++++++++++++++++++ drivers/gpu/drm/tegra/drm.h | 3 ++ drivers/gpu/drm/tegra/dsi.c | 76 ++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 132 insertions(+), 5 deletions(-)
diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index 2fdbed9b2b04..24a91613c4f5 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -1089,6 +1089,39 @@ static int tegra_dc_wait_idle(struct tegra_dc *dc, unsigned long timeout) return -ETIMEDOUT; }
+static void tegra_dc_dpms(struct drm_crtc *crtc, int mode) +{ + struct tegra_dc *dc = to_tegra_dc(crtc); + int err; + unsigned long value; + + if (mode == DRM_MODE_DPMS_SUSPEND) { + 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. + */ + } + + if (mode == DRM_MODE_DPMS_STANDBY) { + /* + * TODO: Unpowergate dc if dc is powergated during DPMS SUSPEND + */ + err = clk_prepare_enable(dc->clk); + if (err < 0) { + dev_err(dc->dev, "failed to enable clock: %d\n", err); + return; + } + + 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); + } +} + static void tegra_crtc_disable(struct drm_crtc *crtc) { struct tegra_dc *dc = to_tegra_dc(crtc); @@ -1318,6 +1351,7 @@ static void tegra_crtc_atomic_flush(struct drm_crtc *crtc) }
static const struct drm_crtc_helper_funcs tegra_crtc_helper_funcs = { + .dpms = tegra_dc_dpms, .disable = tegra_crtc_disable, .mode_fixup = tegra_crtc_mode_fixup, .mode_set_nofb = tegra_crtc_mode_set_nofb, @@ -1331,6 +1365,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); @@ -1348,6 +1383,9 @@ static irqreturn_t tegra_dc_irq(int irq, void *data) */ drm_crtc_handle_vblank(&dc->base); tegra_dc_finish_page_flip(dc); + + if (mode->private_flags & DRM_PANEL_FLAG_PREFER_ONE_SHOT) + schedule_work(&dc->one_shot_work); }
if (status & (WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT)) { @@ -1901,6 +1939,25 @@ 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_dbg(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); +} + static int tegra_dc_probe(struct platform_device *pdev) { unsigned long flags = HOST1X_SYNCPT_CLIENT_MANAGED; @@ -1919,6 +1976,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..1d21f149d7f2 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,11 @@ tegra_dsi_connector_duplicate_state(struct drm_connector *connector) }
static const struct drm_connector_funcs tegra_dsi_connector_funcs = { - .dpms = tegra_dsi_connector_dpms, + /* + * drm_atomic_helper_connector_dpms only handles DPMS ON/OFF, + * so use drm_helper_connector_dpms instead. + */ + .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 +784,72 @@ 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; + unsigned long value; + int err; + + if (mode == DRM_MODE_DPMS_SUSPEND) { + /* + * Wait DSI idle first, otherwise we suspend the dsi & panel + * in the middle of a frame. + */ + err = tegra_dsi_wait_idle(dsi, 100); + if (err < 0) + dev_dbg(dsi->dev, "failed to idle DSI: %d\n", err); + + 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)
On Wed, Jul 01, 2015 at 04:21:53PM +0800, Mark Zhang wrote:
Nope, mixing legacy dpms handling into an atomic driver is a no-go. You need to use drm_atomic_helper_connector_dpms here. -Daniel
This patch fixes a bug when drm_helper_connector_dpms tries to switch connector/encoder/crtc DPMS status.
Signed-off-by: Mark Zhang markz@nvidia.com --- drivers/gpu/drm/tegra/dsi.c | 3 +++ 1 file changed, 3 insertions(+)
diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index 1d21f149d7f2..7917b7875496 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -898,6 +898,8 @@ static void tegra_dsi_encoder_mode_set(struct drm_encoder *encoder, if (output->panel) drm_panel_enable(output->panel);
+ output->connector.dpms = DRM_MODE_DPMS_ON; + return; }
@@ -937,6 +939,7 @@ static void tegra_dsi_encoder_disable(struct drm_encoder *encoder)
tegra_dsi_disable(dsi);
+ output->connector.dpms = DRM_MODE_DPMS_OFF; return; }
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 | 37 +++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/tegra/drm.h | 1 + 2 files changed, 38 insertions(+)
diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index 24a91613c4f5..4381691c73f7 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -1296,6 +1296,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; @@ -1958,6 +1960,40 @@ static void tegra_dc_one_shot_work(struct work_struct *work) drm_modeset_unlock_all(drm); }
+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; + + dc = container_of(work, struct tegra_dc, one_shot_trigger); + drm = dc->base.dev; + msleep(5000); + + if (first_trigger) { + dev_info(dc->dev, "First one-shot triggered.\n"); + 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 dc/dsi/panel.\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_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; @@ -1977,6 +2013,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;
On Wed, Jul 01, 2015 at 04:21:55PM +0800, Mark Zhang wrote:
This HACK adds a workqueue to refresh the display periodically. This is used just for testing.
->dirty is the drm hook you're looking for, it's meant to flush out any frontbuffer rendering. Generic kms clients using the dumb buffers (e.g. fedora boot splash) use this already.
And of course you need to upload a new frame every time an (atomic) flip happens too, but I guess you have that already. No need at all for a periodic upload hack like this.
Cheers, Daniel
On Wed, Jul 01, 2015 at 10:36:17AM +0200, Daniel Vetter wrote:
btw the nice thing with dirty is that it hands you the exact invalidation rectangle, which means you can minimize uploads. For atomic flips we plan to have the same, but it's not implemented yet.
Another problem is that currently the fbdev helper in drm_fb_helper.c doesn't support the dirty callback. But there's other drivers which need this too (e.g. i915 will gain a dirty callback soon) and qxl has all the code implemented already. So the only thing you need to do is move the qxl code into drm_fb_helper.c and adapt it to use the dirty callback instead of directly calling qxl code. Then you should be all set. Note that simply calling ->dirty from fbdev hooks doesn't work since a lot of those hooks are called from irq context (cursors and stuff) and hence you need a workqueue to do the actual dirty call.
Cheers, Daniel
On 07/01/2015 04:36 PM, Daniel Vetter wrote:
Oh... I did a grep in drm source and are you talking about "drm_framebuffer_funcs->dirty"? Yeah, that should work for me.. but that requires userspace sending IOCTL to trigger, right? Honestly I'm lazy so I created this HACK so that I don't need userspace to test.
Yep. Thanks Daniel.
Mark
On Wed, Jul 01, 2015 at 05:01:52PM +0800, Mark Zhang wrote:
Yeah userspace needs to send ioctl already after each drawing. Generic userspace does that already since it's required by qxl, udl, soon i915 and probably a few others too. fbdev emulation is more annyoing but there's code to move around in these drivers (qxl seems best to me as a starting point) too.
Imo without this you shouldn't merge one-shot, at least not enabled by default. -Daniel
On 07/01/2015 06:34 PM, Daniel Vetter wrote:
Alright, this makes sense. I have no idea about qxl, what I have now is an ubuntu running on Tegra114. So I'm wondering what I suppose to do is installing qemu on the ubuntu?
Mark
On Wed, Jul 01, 2015 at 08:43:01PM +0800, Mark Zhang wrote:
My suggestion is just to take the qxl code, move it to the fbdev emulation helper, make it generic and use it. If you want you can do a compile-tested patch on top to switch qxl over to the newly added helpers. No need to install/run qxl itself. Just that qxl seems to have the most complete solution for what you need. -Daniel
On 07/01/2015 10:55 PM, Daniel Vetter wrote:
On Wed, Jul 01, 2015 at 08:43:01PM +0800, Mark Zhang wrote:
On 07/01/2015 06:34 PM, Daniel Vetter wrote:
[...]
OK, thanks Daniel. I'm not quite familiar with the userspace, but I think this is the summary:
- For legacy FB support, we can use the "->dirty" fops, port the qxl codes to drm_fb_helper.c, just as you described. - For atomic page flip, the one-shot can be triggered when flipping the window/plane. - How about the front buffer rendering/drawing? I mean, if a drm userspace app requests a dumb buffer then draws on it, how can we trigger the one-shot to update the display? Do we need to add an additional IOCTL to do that?
Mark
-Daniel
On Fri, Jul 03, 2015 at 03:57:55PM +0800, Mark Zhang wrote:
Note that this is for legacy frontbuffer rendering only. All other screen updates (setplane, setcrtc, pageflip) will go through the atomic flip path, thanks to all the atomic helpers.
dumb buffer userspace is required to call dirtyfb ioctl. -Daniel
dri-devel@lists.freedesktop.org