From 976cebc35bed0456a42bf96073a26f251d23b264 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Wed, 6 Aug 2014 09:14:28 +0200 Subject: drm/tegra: dsi: Make FIFO depths host parameters Rather than hardcoding them as macros, make the host and video FIFO depths parameters so that they can be more easily adjusted if a new generation of the Tegra SoC changes them. While at it, set the depth of the video FIFO to the correct value of 1920 *words* rather than *bytes*. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dsi.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'drivers/gpu/drm/tegra/dsi.c') diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index f7874458926a..584b771d8b2f 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -26,9 +26,6 @@ #include "dsi.h" #include "mipi-phy.h" -#define DSI_VIDEO_FIFO_DEPTH (1920 / 4) -#define DSI_HOST_FIFO_DEPTH 64 - struct tegra_dsi { struct host1x_client client; struct tegra_output output; @@ -54,6 +51,9 @@ struct tegra_dsi { struct regulator *vdd; bool enabled; + + unsigned int video_fifo_depth; + unsigned int host_fifo_depth; }; static inline struct tegra_dsi * @@ -467,7 +467,7 @@ static int tegra_output_dsi_enable(struct tegra_output *output) DSI_CONTROL_SOURCE(dc->pipe); tegra_dsi_writel(dsi, value, DSI_CONTROL); - tegra_dsi_writel(dsi, DSI_VIDEO_FIFO_DEPTH, DSI_MAX_THRESHOLD); + tegra_dsi_writel(dsi, dsi->video_fifo_depth, DSI_MAX_THRESHOLD); value = DSI_HOST_CONTROL_HS | DSI_HOST_CONTROL_CS | DSI_HOST_CONTROL_ECC; @@ -843,6 +843,8 @@ static int tegra_dsi_probe(struct platform_device *pdev) return -ENOMEM; dsi->output.dev = dsi->dev = &pdev->dev; + dsi->video_fifo_depth = 1920; + dsi->host_fifo_depth = 64; err = tegra_output_probe(&dsi->output); if (err < 0) -- cgit v1.2.3 From 183ef2883d060d69d17d63f7c56fbf1a5d65e3f9 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 13 Nov 2014 14:27:29 +0100 Subject: drm/tegra: dsi: Do not manage clock on enable/disable In preparation for supporting command mode panels, don't disable the clock when the output is disabled. The output will be enabled only after the panel has been programmed in command mode, so the clock must always remain on. As a side-effect, pad calibration now only needs to be done at driver probe time, since neither power nor controller state will go away before driver removal. While at it, use a 32-bit variable to store register content because the registers are 32-bit even on 64-bit Tegra. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dsi.c | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) (limited to 'drivers/gpu/drm/tegra/dsi.c') diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index 584b771d8b2f..c62f68071ade 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -456,12 +456,6 @@ static int tegra_output_dsi_enable(struct tegra_output *output) if (err < 0) return err; - err = clk_enable(dsi->clk); - if (err < 0) - return err; - - reset_control_deassert(dsi->rst); - value = DSI_CONTROL_CHANNEL(0) | DSI_CONTROL_FORMAT(format) | DSI_CONTROL_LANES(dsi->lanes - 1) | DSI_CONTROL_SOURCE(dc->pipe); @@ -576,8 +570,6 @@ static int tegra_output_dsi_disable(struct tegra_output *output) tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); } - clk_disable(dsi->clk); - dsi->enabled = false; return 0; @@ -695,7 +687,7 @@ static int tegra_dsi_pad_enable(struct tegra_dsi *dsi) static int tegra_dsi_pad_calibrate(struct tegra_dsi *dsi) { - unsigned long value; + u32 value; tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_0); tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_1); @@ -736,12 +728,6 @@ static int tegra_dsi_init(struct host1x_client *client) dev_err(dsi->dev, "debugfs setup failed: %d\n", err); } - err = tegra_dsi_pad_calibrate(dsi); - if (err < 0) { - dev_err(dsi->dev, "MIPI calibration failed: %d\n", err); - return err; - } - return 0; } @@ -863,6 +849,13 @@ static int tegra_dsi_probe(struct platform_device *pdev) if (IS_ERR(dsi->rst)) return PTR_ERR(dsi->rst); + err = reset_control_deassert(dsi->rst); + if (err < 0) { + dev_err(&pdev->dev, "failed to bring DSI out of reset: %d\n", + err); + return err; + } + dsi->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(dsi->clk)) { dev_err(&pdev->dev, "cannot get DSI clock\n"); @@ -926,6 +919,12 @@ static int tegra_dsi_probe(struct platform_device *pdev) if (IS_ERR(dsi->mipi)) return PTR_ERR(dsi->mipi); + err = tegra_dsi_pad_calibrate(dsi); + if (err < 0) { + dev_err(dsi->dev, "MIPI calibration failed: %d\n", err); + return err; + } + dsi->host.ops = &tegra_dsi_host_ops; dsi->host.dev = &pdev->dev; -- cgit v1.2.3 From 41a8e72e0e32eca7acbcf11d4c75cf5d21d0c825 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 13 Nov 2014 14:34:50 +0100 Subject: drm/tegra: dsi: Leave parent clock alone The common clock framework will take care of preparing and enabling the parent of the DSI clock automatically. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dsi.c | 7 ------- 1 file changed, 7 deletions(-) (limited to 'drivers/gpu/drm/tegra/dsi.c') diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index c62f68071ade..e817ee11f0ec 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -886,12 +886,6 @@ static int tegra_dsi_probe(struct platform_device *pdev) return PTR_ERR(dsi->clk_parent); } - err = clk_prepare_enable(dsi->clk_parent); - if (err < 0) { - dev_err(&pdev->dev, "cannot enable parent clock\n"); - return err; - } - dsi->vdd = devm_regulator_get(&pdev->dev, "avdd-dsi-csi"); if (IS_ERR(dsi->vdd)) { dev_err(&pdev->dev, "cannot get VDD supply\n"); @@ -966,7 +960,6 @@ static int tegra_dsi_remove(struct platform_device *pdev) tegra_mipi_free(dsi->mipi); regulator_disable(dsi->vdd); - clk_disable_unprepare(dsi->clk_parent); clk_disable_unprepare(dsi->clk_lp); clk_disable_unprepare(dsi->clk); reset_control_assert(dsi->rst); -- cgit v1.2.3 From ba3df9792207601762060a313d18efdd6ae83501 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 13 Nov 2014 14:54:01 +0100 Subject: drm/tegra: dsi: Mark connector hotpluggable DSI panels can always be hotplugged via the DSI bus' attach/detach infrastructure, so unconditionally mark the connector hotpluggable. While at it, also make sure that when a panel is detached the connector is marked unconnected before calling into the DRM hotplug helpers to reflect the correct state. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dsi.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'drivers/gpu/drm/tegra/dsi.c') diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index e817ee11f0ec..704267c39586 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -804,10 +804,10 @@ static int tegra_dsi_host_detach(struct mipi_dsi_host *host, struct tegra_output *output = &dsi->output; if (output->panel && &device->dev == output->panel->dev) { + output->panel = NULL; + if (output->connector.dev) drm_helper_hpd_irq_event(output->connector.dev); - - output->panel = NULL; } return 0; @@ -836,6 +836,8 @@ static int tegra_dsi_probe(struct platform_device *pdev) if (err < 0) return err; + dsi->output.connector.polled = DRM_CONNECTOR_POLL_HPD; + /* * Assume these values by default. When a DSI peripheral driver * attaches to the DSI host, the parameters will be taken from -- cgit v1.2.3 From d2d0a9d21285ace23b7e99c89a52ee9b5ceef792 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 13 Nov 2014 14:58:27 +0100 Subject: drm/tegra: dsi: Properly cleanup on probe failure The driver wasn't even attempting to do any cleanup when probing failed. Fix this by releasing any resources acquired up to the point of failure and putting the device back into the original state (reset, clocks off). Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dsi.c | 52 ++++++++++++++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 15 deletions(-) (limited to 'drivers/gpu/drm/tegra/dsi.c') diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index 704267c39586..5dae8053e8c9 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -861,64 +861,72 @@ static int tegra_dsi_probe(struct platform_device *pdev) dsi->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(dsi->clk)) { dev_err(&pdev->dev, "cannot get DSI clock\n"); - return PTR_ERR(dsi->clk); + err = PTR_ERR(dsi->clk); + goto reset; } err = clk_prepare_enable(dsi->clk); if (err < 0) { dev_err(&pdev->dev, "cannot enable DSI clock\n"); - return err; + goto reset; } dsi->clk_lp = devm_clk_get(&pdev->dev, "lp"); if (IS_ERR(dsi->clk_lp)) { dev_err(&pdev->dev, "cannot get low-power clock\n"); - return PTR_ERR(dsi->clk_lp); + err = PTR_ERR(dsi->clk_lp); + goto disable_clk; } err = clk_prepare_enable(dsi->clk_lp); if (err < 0) { dev_err(&pdev->dev, "cannot enable low-power clock\n"); - return err; + goto disable_clk; } dsi->clk_parent = devm_clk_get(&pdev->dev, "parent"); if (IS_ERR(dsi->clk_parent)) { dev_err(&pdev->dev, "cannot get parent clock\n"); - return PTR_ERR(dsi->clk_parent); + err = PTR_ERR(dsi->clk_parent); + goto disable_clk_lp; } dsi->vdd = devm_regulator_get(&pdev->dev, "avdd-dsi-csi"); if (IS_ERR(dsi->vdd)) { dev_err(&pdev->dev, "cannot get VDD supply\n"); - return PTR_ERR(dsi->vdd); + err = PTR_ERR(dsi->vdd); + goto disable_clk_lp; } err = regulator_enable(dsi->vdd); if (err < 0) { dev_err(&pdev->dev, "cannot enable VDD supply\n"); - return err; + goto disable_clk_lp; } err = tegra_dsi_setup_clocks(dsi); if (err < 0) { dev_err(&pdev->dev, "cannot setup clocks\n"); - return err; + goto disable_vdd; } regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); dsi->regs = devm_ioremap_resource(&pdev->dev, regs); - if (IS_ERR(dsi->regs)) - return PTR_ERR(dsi->regs); + if (IS_ERR(dsi->regs)) { + err = PTR_ERR(dsi->regs); + goto disable_vdd; + } dsi->mipi = tegra_mipi_request(&pdev->dev); - if (IS_ERR(dsi->mipi)) - return PTR_ERR(dsi->mipi); + if (IS_ERR(dsi->mipi)) { + err = PTR_ERR(dsi->mipi); + goto disable_vdd; + } err = tegra_dsi_pad_calibrate(dsi); if (err < 0) { dev_err(dsi->dev, "MIPI calibration failed: %d\n", err); - return err; + goto mipi_free; } dsi->host.ops = &tegra_dsi_host_ops; @@ -927,7 +935,7 @@ static int tegra_dsi_probe(struct platform_device *pdev) err = mipi_dsi_host_register(&dsi->host); if (err < 0) { dev_err(&pdev->dev, "failed to register DSI host: %d\n", err); - return err; + goto mipi_free; } INIT_LIST_HEAD(&dsi->client.list); @@ -938,12 +946,26 @@ static int tegra_dsi_probe(struct platform_device *pdev) if (err < 0) { dev_err(&pdev->dev, "failed to register host1x client: %d\n", err); - return err; + goto unregister; } platform_set_drvdata(pdev, dsi); return 0; + +unregister: + mipi_dsi_host_unregister(&dsi->host); +mipi_free: + tegra_mipi_free(dsi->mipi); +disable_vdd: + regulator_disable(dsi->vdd); +disable_clk_lp: + clk_disable_unprepare(dsi->clk_lp); +disable_clk: + clk_disable_unprepare(dsi->clk); +reset: + reset_control_assert(dsi->rst); + return err; } static int tegra_dsi_remove(struct platform_device *pdev) -- cgit v1.2.3 From 563eff1f989917779d8db4c5208e12adcbfcf655 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 13 Nov 2014 14:44:27 +0100 Subject: drm/tegra: dsi: Refactor in preparation for command mode For command mode panels, the DSI controller needs to be enabled and configured so that panel drivers can send commands prior to the video stream being enabled. Move code from the monolithic output enable/disable functions into smaller, reusable units to allow more fine-grained control over the controller state. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dsi.c | 100 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 81 insertions(+), 19 deletions(-) (limited to 'drivers/gpu/drm/tegra/dsi.c') diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index 5dae8053e8c9..b91d9e4016bc 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -426,20 +426,24 @@ static int tegra_dsi_get_format(enum mipi_dsi_pixel_format format, return 0; } -static int tegra_output_dsi_enable(struct tegra_output *output) +static void tegra_dsi_enable(struct tegra_dsi *dsi) +{ + u32 value; + + value = tegra_dsi_readl(dsi, DSI_POWER_CONTROL); + value |= DSI_POWER_CONTROL_ENABLE; + tegra_dsi_writel(dsi, value, DSI_POWER_CONTROL); +} + +static int tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe, + const struct drm_display_mode *mode) { - struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc); - struct drm_display_mode *mode = &dc->base.mode; unsigned int hact, hsw, hbp, hfp, i, mul, div; - struct tegra_dsi *dsi = to_dsi(output); enum tegra_dsi_format format; - unsigned long value; const u32 *pkt_seq; + u32 value; int err; - if (dsi->enabled) - return 0; - if (dsi->flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) { DRM_DEBUG_KMS("Non-burst video mode with sync pulses\n"); pkt_seq = pkt_seq_video_non_burst_sync_pulses; @@ -458,18 +462,19 @@ static int tegra_output_dsi_enable(struct tegra_output *output) value = DSI_CONTROL_CHANNEL(0) | DSI_CONTROL_FORMAT(format) | DSI_CONTROL_LANES(dsi->lanes - 1) | - DSI_CONTROL_SOURCE(dc->pipe); + DSI_CONTROL_SOURCE(pipe); tegra_dsi_writel(dsi, value, DSI_CONTROL); tegra_dsi_writel(dsi, dsi->video_fifo_depth, DSI_MAX_THRESHOLD); - value = DSI_HOST_CONTROL_HS | DSI_HOST_CONTROL_CS | - DSI_HOST_CONTROL_ECC; + value = DSI_HOST_CONTROL_HS; tegra_dsi_writel(dsi, value, DSI_HOST_CONTROL); value = tegra_dsi_readl(dsi, DSI_CONTROL); + if (dsi->flags & MIPI_DSI_CLOCK_NON_CONTINUOUS) value |= DSI_CONTROL_HS_CLK_CTRL; + value &= ~DSI_CONTROL_TX_TRIG(3); value &= ~DSI_CONTROL_DCS_ENABLE; value |= DSI_CONTROL_VIDEO_ENABLE; @@ -503,9 +508,27 @@ static int tegra_output_dsi_enable(struct tegra_output *output) tegra_dsi_writel(dsi, hfp, DSI_PKT_LEN_4_5); tegra_dsi_writel(dsi, 0x0f0f << 16, DSI_PKT_LEN_6_7); - /* set SOL delay */ + /* set SOL delay (for non-burst mode only) */ tegra_dsi_writel(dsi, 8 * mul / div, DSI_SOL_DELAY); + return 0; +} + +static int tegra_output_dsi_enable(struct tegra_output *output) +{ + struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc); + const struct drm_display_mode *mode = &dc->base.mode; + struct tegra_dsi *dsi = to_dsi(output); + u32 value; + int err; + + if (dsi->enabled) + return 0; + + err = tegra_dsi_configure(dsi, dc->pipe, mode); + if (err < 0) + return err; + /* enable display controller */ value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS); value |= DSI_ENABLE; @@ -525,28 +548,61 @@ static int tegra_output_dsi_enable(struct tegra_output *output) tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); /* enable DSI controller */ - value = tegra_dsi_readl(dsi, DSI_POWER_CONTROL); - value |= DSI_POWER_CONTROL_ENABLE; - tegra_dsi_writel(dsi, value, DSI_POWER_CONTROL); + tegra_dsi_enable(dsi); dsi->enabled = true; return 0; } +static int tegra_dsi_wait_idle(struct tegra_dsi *dsi, unsigned long timeout) +{ + u32 value; + + timeout = jiffies + msecs_to_jiffies(timeout); + + while (time_before(jiffies, timeout)) { + value = tegra_dsi_readl(dsi, DSI_STATUS); + if (value & DSI_STATUS_IDLE) + return 0; + + usleep_range(1000, 2000); + } + + return -ETIMEDOUT; +} + +static void tegra_dsi_video_disable(struct tegra_dsi *dsi) +{ + u32 value; + + value = tegra_dsi_readl(dsi, DSI_CONTROL); + value &= ~DSI_CONTROL_VIDEO_ENABLE; + tegra_dsi_writel(dsi, value, DSI_CONTROL); +} + +static void tegra_dsi_disable(struct tegra_dsi *dsi) +{ + u32 value; + + value = tegra_dsi_readl(dsi, DSI_POWER_CONTROL); + value &= ~DSI_POWER_CONTROL_ENABLE; + tegra_dsi_writel(dsi, value, DSI_POWER_CONTROL); + + usleep_range(5000, 10000); +} + static int tegra_output_dsi_disable(struct tegra_output *output) { struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc); struct tegra_dsi *dsi = to_dsi(output); unsigned long value; + int err; if (!dsi->enabled) return 0; - /* disable DSI controller */ - value = tegra_dsi_readl(dsi, DSI_POWER_CONTROL); - value &= ~DSI_POWER_CONTROL_ENABLE; - tegra_dsi_writel(dsi, value, DSI_POWER_CONTROL); + tegra_dsi_video_disable(dsi); /* * The following accesses registers of the display controller, so make @@ -570,6 +626,12 @@ static int tegra_output_dsi_disable(struct tegra_output *output) tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); } + err = tegra_dsi_wait_idle(dsi, 100); + if (err < 0) + dev_dbg(dsi->dev, "failed to idle DSI: %d\n", err); + + tegra_dsi_disable(dsi); + dsi->enabled = false; return 0; -- cgit v1.2.3 From 337b443d58e2d7d04d23ed07ff61b1243d5f9f2d Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 13 Nov 2014 15:02:46 +0100 Subject: drm/tegra: dsi: Add command mode support Add support for DC-driven command mode. This is a mode where the video stream sent by the display controller is packed into DCS command packets (write_memory_start and write_memory_continue) by the DSI controller. It can be used for panels with a remote framebuffer and is useful to save power when used with a dynamic refresh rate (not yet supported by the driver). Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dsi.c | 82 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 63 insertions(+), 19 deletions(-) (limited to 'drivers/gpu/drm/tegra/dsi.c') diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index b91d9e4016bc..50684a4aa4f0 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -318,6 +318,21 @@ static const u32 pkt_seq_video_non_burst_sync_events[NUM_PKT_SEQ] = { [11] = PKT_ID0(MIPI_DSI_BLANKING_PACKET) | PKT_LEN0(4), }; +static const u32 pkt_seq_command_mode[NUM_PKT_SEQ] = { + [ 0] = 0, + [ 1] = 0, + [ 2] = 0, + [ 3] = 0, + [ 4] = 0, + [ 5] = 0, + [ 6] = PKT_ID0(MIPI_DSI_DCS_LONG_WRITE) | PKT_LEN0(3) | PKT_LP, + [ 7] = 0, + [ 8] = 0, + [ 9] = 0, + [10] = PKT_ID0(MIPI_DSI_DCS_LONG_WRITE) | PKT_LEN0(5) | PKT_LP, + [11] = 0, +}; + static int tegra_dsi_set_phy_timing(struct tegra_dsi *dsi) { struct mipi_dphy_timing timing; @@ -447,9 +462,12 @@ static int tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe, if (dsi->flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) { DRM_DEBUG_KMS("Non-burst video mode with sync pulses\n"); pkt_seq = pkt_seq_video_non_burst_sync_pulses; - } else { + } else if (dsi->flags & MIPI_DSI_MODE_VIDEO) { DRM_DEBUG_KMS("Non-burst video mode with sync events\n"); pkt_seq = pkt_seq_video_non_burst_sync_events; + } else { + DRM_DEBUG_KMS("Command mode\n"); + pkt_seq = pkt_seq_command_mode; } err = tegra_dsi_get_muldiv(dsi->format, &mul, &div); @@ -476,7 +494,13 @@ static int tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe, value |= DSI_CONTROL_HS_CLK_CTRL; value &= ~DSI_CONTROL_TX_TRIG(3); - value &= ~DSI_CONTROL_DCS_ENABLE; + + /* enable DCS commands for command mode */ + if (dsi->flags & MIPI_DSI_MODE_VIDEO) + value &= ~DSI_CONTROL_DCS_ENABLE; + else + value |= DSI_CONTROL_DCS_ENABLE; + value |= DSI_CONTROL_VIDEO_ENABLE; value &= ~DSI_CONTROL_HOST_ENABLE; tegra_dsi_writel(dsi, value, DSI_CONTROL); @@ -488,28 +512,48 @@ static int tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe, for (i = 0; i < NUM_PKT_SEQ; i++) tegra_dsi_writel(dsi, pkt_seq[i], DSI_PKT_SEQ_0_LO + i); - /* horizontal active pixels */ - hact = mode->hdisplay * mul / div; + if (dsi->flags & MIPI_DSI_MODE_VIDEO) { + /* horizontal active pixels */ + hact = mode->hdisplay * mul / div; + + /* horizontal sync width */ + hsw = (mode->hsync_end - mode->hsync_start) * mul / div; + hsw -= 10; + + /* horizontal back porch */ + hbp = (mode->htotal - mode->hsync_end) * mul / div; + hbp -= 14; + + /* horizontal front porch */ + hfp = (mode->hsync_start - mode->hdisplay) * mul / div; + hfp -= 8; - /* horizontal sync width */ - hsw = (mode->hsync_end - mode->hsync_start) * mul / div; - hsw -= 10; + tegra_dsi_writel(dsi, hsw << 16 | 0, DSI_PKT_LEN_0_1); + tegra_dsi_writel(dsi, hact << 16 | hbp, DSI_PKT_LEN_2_3); + tegra_dsi_writel(dsi, hfp, DSI_PKT_LEN_4_5); + tegra_dsi_writel(dsi, 0x0f0f << 16, DSI_PKT_LEN_6_7); - /* horizontal back porch */ - hbp = (mode->htotal - mode->hsync_end) * mul / div; - hbp -= 14; + /* set SOL delay (for non-burst mode only) */ + tegra_dsi_writel(dsi, 8 * mul / div, DSI_SOL_DELAY); + } else { + u16 bytes; + + /* 1 byte (DCS command) + pixel data */ + bytes = 1 + mode->hdisplay * mul / div; - /* horizontal front porch */ - hfp = (mode->hsync_start - mode->hdisplay) * mul / div; - hfp -= 8; + tegra_dsi_writel(dsi, 0, DSI_PKT_LEN_0_1); + tegra_dsi_writel(dsi, bytes << 16, DSI_PKT_LEN_2_3); + tegra_dsi_writel(dsi, bytes << 16, DSI_PKT_LEN_4_5); + tegra_dsi_writel(dsi, 0, DSI_PKT_LEN_6_7); - tegra_dsi_writel(dsi, hsw << 16 | 0, DSI_PKT_LEN_0_1); - tegra_dsi_writel(dsi, hact << 16 | hbp, DSI_PKT_LEN_2_3); - tegra_dsi_writel(dsi, hfp, DSI_PKT_LEN_4_5); - tegra_dsi_writel(dsi, 0x0f0f << 16, DSI_PKT_LEN_6_7); + value = MIPI_DCS_WRITE_MEMORY_START << 8 | + MIPI_DCS_WRITE_MEMORY_CONTINUE; + tegra_dsi_writel(dsi, value, DSI_DCS_CMDS); - /* set SOL delay (for non-burst mode only) */ - tegra_dsi_writel(dsi, 8 * mul / div, DSI_SOL_DELAY); + value = 8 * mul / div; + + tegra_dsi_writel(dsi, value, DSI_SOL_DELAY); + } return 0; } -- cgit v1.2.3 From 3f6b406f7d716310c7a63648bbe6b2a4a30c3077 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 13 Nov 2014 14:50:33 +0100 Subject: drm/tegra: dsi: Split out tegra_dsi_set_timeout() In preparation for adding ganged-mode support, this commit splits out the tegra_dsi_set_timeout() function so that it can be reused for the slave DSI controller. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dsi.c | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) (limited to 'drivers/gpu/drm/tegra/dsi.c') diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index 50684a4aa4f0..46fcf62658cf 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -681,15 +681,35 @@ static int tegra_output_dsi_disable(struct tegra_output *output) return 0; } +static void tegra_dsi_set_timeout(struct tegra_dsi *dsi, unsigned long bclk, + unsigned int vrefresh) +{ + unsigned int timeout; + u32 value; + + /* one frame high-speed transmission timeout */ + timeout = (bclk / vrefresh) / 512; + value = DSI_TIMEOUT_LRX(0x2000) | DSI_TIMEOUT_HTX(timeout); + tegra_dsi_writel(dsi, value, DSI_TIMEOUT_0); + + /* 2 ms peripheral timeout for panel */ + timeout = 2 * bclk / 512 * 1000; + value = DSI_TIMEOUT_PR(timeout) | DSI_TIMEOUT_TA(0x2000); + tegra_dsi_writel(dsi, value, DSI_TIMEOUT_1); + + value = DSI_TALLY_TA(0) | DSI_TALLY_LRX(0) | DSI_TALLY_HTX(0); + tegra_dsi_writel(dsi, value, DSI_TO_TALLY); +} + static int tegra_output_dsi_setup_clock(struct tegra_output *output, struct clk *clk, unsigned long pclk, unsigned int *divp) { struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc); struct drm_display_mode *mode = &dc->base.mode; - unsigned int timeout, mul, div, vrefresh; struct tegra_dsi *dsi = to_dsi(output); - unsigned long bclk, plld, value; + unsigned int mul, div, vrefresh; + unsigned long bclk, plld; int err; err = tegra_dsi_get_muldiv(dsi->format, &mul, &div); @@ -744,19 +764,7 @@ static int tegra_output_dsi_setup_clock(struct tegra_output *output, * XXX: Move the below somewhere else so that we don't need to have * access to the vrefresh in this function? */ - - /* one frame high-speed transmission timeout */ - timeout = (bclk / vrefresh) / 512; - value = DSI_TIMEOUT_LRX(0x2000) | DSI_TIMEOUT_HTX(timeout); - tegra_dsi_writel(dsi, value, DSI_TIMEOUT_0); - - /* 2 ms peripheral timeout for panel */ - timeout = 2 * bclk / 512 * 1000; - value = DSI_TIMEOUT_PR(timeout) | DSI_TIMEOUT_TA(0x2000); - tegra_dsi_writel(dsi, value, DSI_TIMEOUT_1); - - value = DSI_TALLY_TA(0) | DSI_TALLY_LRX(0) | DSI_TALLY_HTX(0); - tegra_dsi_writel(dsi, value, DSI_TO_TALLY); + tegra_dsi_set_timeout(dsi, bclk, vrefresh); return 0; } -- cgit v1.2.3 From e94236cde4d519cdecd45e2435defba33abdc99f Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Tue, 7 Oct 2014 16:10:24 +0200 Subject: drm/tegra: dsi: Add ganged mode support Implement ganged mode support for the Tegra DSI driver. The DSI host controller to gang up with is specified via a phandle in the device tree and the resolved DSI host controller used for the programming of the ganged-mode registers. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dsi.c | 221 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 192 insertions(+), 29 deletions(-) (limited to 'drivers/gpu/drm/tegra/dsi.c') diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index 46fcf62658cf..66816104ba72 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -54,6 +55,10 @@ struct tegra_dsi { unsigned int video_fifo_depth; unsigned int host_fifo_depth; + + /* for ganged-mode support */ + struct tegra_dsi *master; + struct tegra_dsi *slave; }; static inline struct tegra_dsi * @@ -441,6 +446,18 @@ static int tegra_dsi_get_format(enum mipi_dsi_pixel_format format, return 0; } +static void tegra_dsi_ganged_enable(struct tegra_dsi *dsi, unsigned int start, + unsigned int size) +{ + u32 value; + + tegra_dsi_writel(dsi, start, DSI_GANGED_MODE_START); + tegra_dsi_writel(dsi, size << 16 | size, DSI_GANGED_MODE_SIZE); + + value = DSI_GANGED_MODE_CONTROL_ENABLE; + tegra_dsi_writel(dsi, value, DSI_GANGED_MODE_CONTROL); +} + static void tegra_dsi_enable(struct tegra_dsi *dsi) { u32 value; @@ -448,6 +465,20 @@ static void tegra_dsi_enable(struct tegra_dsi *dsi) value = tegra_dsi_readl(dsi, DSI_POWER_CONTROL); value |= DSI_POWER_CONTROL_ENABLE; tegra_dsi_writel(dsi, value, DSI_POWER_CONTROL); + + if (dsi->slave) + tegra_dsi_enable(dsi->slave); +} + +static unsigned int tegra_dsi_get_lanes(struct tegra_dsi *dsi) +{ + if (dsi->master) + return dsi->master->lanes + dsi->lanes; + + if (dsi->slave) + return dsi->lanes + dsi->slave->lanes; + + return dsi->lanes; } static int tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe, @@ -535,11 +566,20 @@ static int tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe, /* set SOL delay (for non-burst mode only) */ tegra_dsi_writel(dsi, 8 * mul / div, DSI_SOL_DELAY); + + /* TODO: implement ganged mode */ } else { u16 bytes; - /* 1 byte (DCS command) + pixel data */ - bytes = 1 + mode->hdisplay * mul / div; + if (dsi->master || dsi->slave) { + /* + * For ganged mode, assume symmetric left-right mode. + */ + bytes = 1 + (mode->hdisplay / 2) * mul / div; + } else { + /* 1 byte (DCS command) + pixel data */ + bytes = 1 + mode->hdisplay * mul / div; + } tegra_dsi_writel(dsi, 0, DSI_PKT_LEN_0_1); tegra_dsi_writel(dsi, bytes << 16, DSI_PKT_LEN_2_3); @@ -550,11 +590,42 @@ static int tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe, MIPI_DCS_WRITE_MEMORY_CONTINUE; tegra_dsi_writel(dsi, value, DSI_DCS_CMDS); - value = 8 * mul / div; + /* set SOL delay */ + if (dsi->master || dsi->slave) { + unsigned int lanes = tegra_dsi_get_lanes(dsi); + unsigned long delay, bclk, bclk_ganged; + + /* SOL to valid, valid to FIFO and FIFO write delay */ + delay = 4 + 4 + 2; + delay = DIV_ROUND_UP(delay * mul, div * lanes); + /* FIFO read delay */ + delay = delay + 6; + + bclk = DIV_ROUND_UP(mode->htotal * mul, div * lanes); + bclk_ganged = DIV_ROUND_UP(bclk * lanes / 2, lanes); + value = bclk - bclk_ganged + delay + 20; + } else { + /* TODO: revisit for non-ganged mode */ + value = 8 * mul / div; + } tegra_dsi_writel(dsi, value, DSI_SOL_DELAY); } + if (dsi->slave) { + err = tegra_dsi_configure(dsi->slave, pipe, mode); + if (err < 0) + return err; + + /* + * TODO: Support modes other than symmetrical left-right + * split. + */ + tegra_dsi_ganged_enable(dsi, 0, mode->hdisplay / 2); + tegra_dsi_ganged_enable(dsi->slave, mode->hdisplay / 2, + mode->hdisplay / 2); + } + return 0; } @@ -623,16 +694,34 @@ static void tegra_dsi_video_disable(struct tegra_dsi *dsi) value = tegra_dsi_readl(dsi, DSI_CONTROL); value &= ~DSI_CONTROL_VIDEO_ENABLE; tegra_dsi_writel(dsi, value, DSI_CONTROL); + + if (dsi->slave) + tegra_dsi_video_disable(dsi->slave); +} + +static void tegra_dsi_ganged_disable(struct tegra_dsi *dsi) +{ + tegra_dsi_writel(dsi, 0, DSI_GANGED_MODE_START); + tegra_dsi_writel(dsi, 0, DSI_GANGED_MODE_SIZE); + tegra_dsi_writel(dsi, 0, DSI_GANGED_MODE_CONTROL); } static void tegra_dsi_disable(struct tegra_dsi *dsi) { u32 value; + if (dsi->slave) { + tegra_dsi_ganged_disable(dsi->slave); + tegra_dsi_ganged_disable(dsi); + } + value = tegra_dsi_readl(dsi, DSI_POWER_CONTROL); value &= ~DSI_POWER_CONTROL_ENABLE; tegra_dsi_writel(dsi, value, DSI_POWER_CONTROL); + if (dsi->slave) + tegra_dsi_disable(dsi->slave); + usleep_range(5000, 10000); } @@ -699,6 +788,9 @@ static void tegra_dsi_set_timeout(struct tegra_dsi *dsi, unsigned long bclk, value = DSI_TALLY_TA(0) | DSI_TALLY_LRX(0) | DSI_TALLY_HTX(0); tegra_dsi_writel(dsi, value, DSI_TO_TALLY); + + if (dsi->slave) + tegra_dsi_set_timeout(dsi->slave, bclk, vrefresh); } static int tegra_output_dsi_setup_clock(struct tegra_output *output, @@ -708,20 +800,22 @@ static int tegra_output_dsi_setup_clock(struct tegra_output *output, struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc); struct drm_display_mode *mode = &dc->base.mode; struct tegra_dsi *dsi = to_dsi(output); - unsigned int mul, div, vrefresh; + unsigned int mul, div, vrefresh, lanes; unsigned long bclk, plld; int err; + lanes = tegra_dsi_get_lanes(dsi); + err = tegra_dsi_get_muldiv(dsi->format, &mul, &div); if (err < 0) return err; - DRM_DEBUG_KMS("mul: %u, div: %u, lanes: %u\n", mul, div, dsi->lanes); + DRM_DEBUG_KMS("mul: %u, div: %u, lanes: %u\n", mul, div, lanes); vrefresh = drm_mode_vrefresh(mode); DRM_DEBUG_KMS("vrefresh: %u\n", vrefresh); /* compute byte clock */ - bclk = (pclk * mul) / (div * dsi->lanes); + bclk = (pclk * mul) / (div * lanes); /* * Compute bit clock and round up to the next MHz. @@ -758,7 +852,7 @@ static int tegra_output_dsi_setup_clock(struct tegra_output *output, * not working properly otherwise. Perhaps the PLLs cannot generate * frequencies sufficiently high. */ - *divp = ((8 * mul) / (div * dsi->lanes)) - 2; + *divp = ((8 * mul) / (div * lanes)) - 2; /* * XXX: Move the below somewhere else so that we don't need to have @@ -826,14 +920,17 @@ static int tegra_dsi_init(struct host1x_client *client) struct tegra_dsi *dsi = host1x_client_to_dsi(client); int err; - dsi->output.type = TEGRA_OUTPUT_DSI; - dsi->output.dev = client->dev; - dsi->output.ops = &dsi_ops; - - err = tegra_output_init(drm, &dsi->output); - if (err < 0) { - dev_err(client->dev, "output setup failed: %d\n", err); - return err; + /* Gangsters must not register their own outputs. */ + if (!dsi->master) { + dsi->output.type = TEGRA_OUTPUT_DSI; + dsi->output.dev = client->dev; + dsi->output.ops = &dsi_ops; + + err = tegra_output_init(drm, &dsi->output); + if (err < 0) { + dev_err(client->dev, "output setup failed: %d\n", err); + return err; + } } if (IS_ENABLED(CONFIG_DEBUG_FS)) { @@ -856,16 +953,20 @@ static int tegra_dsi_exit(struct host1x_client *client) dev_err(dsi->dev, "debugfs cleanup failed: %d\n", err); } - err = tegra_output_disable(&dsi->output); - if (err < 0) { - dev_err(client->dev, "output failed to disable: %d\n", err); - return err; - } - - err = tegra_output_exit(&dsi->output); - if (err < 0) { - dev_err(client->dev, "output cleanup failed: %d\n", err); - return err; + if (!dsi->master) { + err = tegra_output_disable(&dsi->output); + if (err < 0) { + dev_err(client->dev, "output failed to disable: %d\n", + err); + return err; + } + + err = tegra_output_exit(&dsi->output); + if (err < 0) { + dev_err(client->dev, "output cleanup failed: %d\n", + err); + return err; + } } return 0; @@ -892,20 +993,58 @@ static int tegra_dsi_setup_clocks(struct tegra_dsi *dsi) return 0; } +static int tegra_dsi_ganged_setup(struct tegra_dsi *dsi) +{ + struct clk *parent; + int err; + + /* make sure both DSI controllers share the same PLL */ + parent = clk_get_parent(dsi->slave->clk); + if (!parent) + return -EINVAL; + + err = clk_set_parent(parent, dsi->clk_parent); + if (err < 0) + return err; + + return 0; +} + static int tegra_dsi_host_attach(struct mipi_dsi_host *host, struct mipi_dsi_device *device) { struct tegra_dsi *dsi = host_to_tegra(host); - struct tegra_output *output = &dsi->output; dsi->flags = device->mode_flags; dsi->format = device->format; dsi->lanes = device->lanes; - output->panel = of_drm_find_panel(device->dev.of_node); - if (output->panel) { - if (output->connector.dev) + if (dsi->slave) { + int err; + + dev_dbg(dsi->dev, "attaching dual-channel device %s\n", + dev_name(&device->dev)); + + err = tegra_dsi_ganged_setup(dsi); + if (err < 0) { + dev_err(dsi->dev, "failed to set up ganged mode: %d\n", + err); + return err; + } + } + + /* + * Slaves don't have a panel associated with them, so they provide + * merely the second channel. + */ + if (!dsi->master) { + struct tegra_output *output = &dsi->output; + + output->panel = of_drm_find_panel(device->dev.of_node); + if (output->panel && output->connector.dev) { + drm_panel_attach(output->panel, &output->connector); drm_helper_hpd_irq_event(output->connector.dev); + } } return 0; @@ -932,6 +1071,26 @@ static const struct mipi_dsi_host_ops tegra_dsi_host_ops = { .detach = tegra_dsi_host_detach, }; +static int tegra_dsi_ganged_probe(struct tegra_dsi *dsi) +{ + struct device_node *np; + + np = of_parse_phandle(dsi->dev->of_node, "nvidia,ganged-mode", 0); + if (np) { + struct platform_device *gangster = of_find_device_by_node(np); + + dsi->slave = platform_get_drvdata(gangster); + of_node_put(np); + + if (!dsi->slave) + return -EPROBE_DEFER; + + dsi->slave->master = dsi; + } + + return 0; +} + static int tegra_dsi_probe(struct platform_device *pdev) { struct tegra_dsi *dsi; @@ -946,6 +1105,10 @@ static int tegra_dsi_probe(struct platform_device *pdev) dsi->video_fifo_depth = 1920; dsi->host_fifo_depth = 64; + err = tegra_dsi_ganged_probe(dsi); + if (err < 0) + return err; + err = tegra_output_probe(&dsi->output); if (err < 0) return err; -- cgit v1.2.3 From 0fffdf6ca9926243db9ca17701fcdc0a38c67d48 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 7 Nov 2014 17:25:26 +0100 Subject: drm/tegra: dsi: Implement host transfers Add support for sending MIPI DSI command packets from the host to a peripheral. This is required for panels that need configuration before they accept video data. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dsi.c | 267 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 267 insertions(+) (limited to 'drivers/gpu/drm/tegra/dsi.c') diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index 66816104ba72..6646fa2adc38 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -993,6 +993,272 @@ static int tegra_dsi_setup_clocks(struct tegra_dsi *dsi) return 0; } +static const char * const error_report[16] = { + "SoT Error", + "SoT Sync Error", + "EoT Sync Error", + "Escape Mode Entry Command Error", + "Low-Power Transmit Sync Error", + "Peripheral Timeout Error", + "False Control Error", + "Contention Detected", + "ECC Error, single-bit", + "ECC Error, multi-bit", + "Checksum Error", + "DSI Data Type Not Recognized", + "DSI VC ID Invalid", + "Invalid Transmission Length", + "Reserved", + "DSI Protocol Violation", +}; + +static ssize_t tegra_dsi_read_response(struct tegra_dsi *dsi, + const struct mipi_dsi_msg *msg, + size_t count) +{ + u8 *rx = msg->rx_buf; + unsigned int i, j, k; + size_t size = 0; + u16 errors; + u32 value; + + /* read and parse packet header */ + value = tegra_dsi_readl(dsi, DSI_RD_DATA); + + switch (value & 0x3f) { + case MIPI_DSI_RX_ACKNOWLEDGE_AND_ERROR_REPORT: + errors = (value >> 8) & 0xffff; + dev_dbg(dsi->dev, "Acknowledge and error report: %04x\n", + errors); + for (i = 0; i < ARRAY_SIZE(error_report); i++) + if (errors & BIT(i)) + dev_dbg(dsi->dev, " %2u: %s\n", i, + error_report[i]); + break; + + case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_1BYTE: + rx[0] = (value >> 8) & 0xff; + size = 1; + break; + + case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_2BYTE: + rx[0] = (value >> 8) & 0xff; + rx[1] = (value >> 16) & 0xff; + size = 2; + break; + + case MIPI_DSI_RX_DCS_LONG_READ_RESPONSE: + size = ((value >> 8) & 0xff00) | ((value >> 8) & 0xff); + break; + + case MIPI_DSI_RX_GENERIC_LONG_READ_RESPONSE: + size = ((value >> 8) & 0xff00) | ((value >> 8) & 0xff); + break; + + default: + dev_err(dsi->dev, "unhandled response type: %02x\n", + value & 0x3f); + return -EPROTO; + } + + size = min(size, msg->rx_len); + + if (msg->rx_buf && size > 0) { + for (i = 0, j = 0; i < count - 1; i++, j += 4) { + u8 *rx = msg->rx_buf + j; + + value = tegra_dsi_readl(dsi, DSI_RD_DATA); + + for (k = 0; k < 4 && (j + k) < msg->rx_len; k++) + rx[j + k] = (value >> (k << 3)) & 0xff; + } + } + + return size; +} + +static int tegra_dsi_transmit(struct tegra_dsi *dsi, unsigned long timeout) +{ + tegra_dsi_writel(dsi, DSI_TRIGGER_HOST, DSI_TRIGGER); + + timeout = jiffies + msecs_to_jiffies(timeout); + + while (time_before(jiffies, timeout)) { + u32 value = tegra_dsi_readl(dsi, DSI_TRIGGER); + if ((value & DSI_TRIGGER_HOST) == 0) + return 0; + + usleep_range(1000, 2000); + } + + DRM_DEBUG_KMS("timeout waiting for transmission to complete\n"); + return -ETIMEDOUT; +} + +static int tegra_dsi_wait_for_response(struct tegra_dsi *dsi, + unsigned long timeout) +{ + timeout = jiffies + msecs_to_jiffies(250); + + while (time_before(jiffies, timeout)) { + u32 value = tegra_dsi_readl(dsi, DSI_STATUS); + u8 count = value & 0x1f; + + if (count > 0) + return count; + + usleep_range(1000, 2000); + } + + DRM_DEBUG_KMS("peripheral returned no data\n"); + return -ETIMEDOUT; +} + +static void tegra_dsi_writesl(struct tegra_dsi *dsi, unsigned long offset, + const void *buffer, size_t size) +{ + const u8 *buf = buffer; + size_t i, j; + u32 value; + + for (j = 0; j < size; j += 4) { + value = 0; + + for (i = 0; i < 4 && j + i < size; i++) + value |= buf[j + i] << (i << 3); + + tegra_dsi_writel(dsi, value, DSI_WR_DATA); + } +} + +static ssize_t tegra_dsi_host_transfer(struct mipi_dsi_host *host, + const struct mipi_dsi_msg *msg) +{ + struct tegra_dsi *dsi = host_to_tegra(host); + struct mipi_dsi_packet packet; + const u8 *header; + size_t count; + ssize_t err; + u32 value; + + err = mipi_dsi_create_packet(&packet, msg); + if (err < 0) + return err; + + header = packet.header; + + /* maximum FIFO depth is 1920 words */ + if (packet.size > dsi->video_fifo_depth * 4) + return -ENOSPC; + + /* reset underflow/overflow flags */ + value = tegra_dsi_readl(dsi, DSI_STATUS); + if (value & (DSI_STATUS_UNDERFLOW | DSI_STATUS_OVERFLOW)) { + value = DSI_HOST_CONTROL_FIFO_RESET; + tegra_dsi_writel(dsi, value, DSI_HOST_CONTROL); + usleep_range(10, 20); + } + + value = tegra_dsi_readl(dsi, DSI_POWER_CONTROL); + value |= DSI_POWER_CONTROL_ENABLE; + tegra_dsi_writel(dsi, value, DSI_POWER_CONTROL); + + usleep_range(5000, 10000); + + value = DSI_HOST_CONTROL_CRC_RESET | DSI_HOST_CONTROL_TX_TRIG_HOST | + DSI_HOST_CONTROL_CS | DSI_HOST_CONTROL_ECC; + + if ((msg->flags & MIPI_DSI_MSG_USE_LPM) == 0) + value |= DSI_HOST_CONTROL_HS; + + /* + * The host FIFO has a maximum of 64 words, so larger transmissions + * need to use the video FIFO. + */ + if (packet.size > dsi->host_fifo_depth * 4) + value |= DSI_HOST_CONTROL_FIFO_SEL; + + tegra_dsi_writel(dsi, value, DSI_HOST_CONTROL); + + /* + * For reads and messages with explicitly requested ACK, generate a + * BTA sequence after the transmission of the packet. + */ + if ((msg->flags & MIPI_DSI_MSG_REQ_ACK) || + (msg->rx_buf && msg->rx_len > 0)) { + value = tegra_dsi_readl(dsi, DSI_HOST_CONTROL); + value |= DSI_HOST_CONTROL_PKT_BTA; + tegra_dsi_writel(dsi, value, DSI_HOST_CONTROL); + } + + value = DSI_CONTROL_LANES(0) | DSI_CONTROL_HOST_ENABLE; + tegra_dsi_writel(dsi, value, DSI_CONTROL); + + /* write packet header, ECC is generated by hardware */ + value = header[2] << 16 | header[1] << 8 | header[0]; + tegra_dsi_writel(dsi, value, DSI_WR_DATA); + + /* write payload (if any) */ + if (packet.payload_length > 0) + tegra_dsi_writesl(dsi, DSI_WR_DATA, packet.payload, + packet.payload_length); + + err = tegra_dsi_transmit(dsi, 250); + if (err < 0) + return err; + + if ((msg->flags & MIPI_DSI_MSG_REQ_ACK) || + (msg->rx_buf && msg->rx_len > 0)) { + err = tegra_dsi_wait_for_response(dsi, 250); + if (err < 0) + return err; + + count = err; + + value = tegra_dsi_readl(dsi, DSI_RD_DATA); + switch (value) { + case 0x84: + /* + dev_dbg(dsi->dev, "ACK\n"); + */ + break; + + case 0x87: + /* + dev_dbg(dsi->dev, "ESCAPE\n"); + */ + break; + + default: + dev_err(dsi->dev, "unknown status: %08x\n", value); + break; + } + + if (count > 1) { + err = tegra_dsi_read_response(dsi, msg, count); + if (err < 0) + dev_err(dsi->dev, + "failed to parse response: %zd\n", + err); + else { + /* + * For read commands, return the number of + * bytes returned by the peripheral. + */ + count = err; + } + } + } else { + /* + * For write commands, we have transmitted the 4-byte header + * plus the variable-length payload. + */ + count = 4 + packet.payload_length; + } + + return count; +} + static int tegra_dsi_ganged_setup(struct tegra_dsi *dsi) { struct clk *parent; @@ -1069,6 +1335,7 @@ static int tegra_dsi_host_detach(struct mipi_dsi_host *host, static const struct mipi_dsi_host_ops tegra_dsi_host_ops = { .attach = tegra_dsi_host_attach, .detach = tegra_dsi_host_detach, + .transfer = tegra_dsi_host_transfer, }; static int tegra_dsi_ganged_probe(struct tegra_dsi *dsi) -- cgit v1.2.3 From 369bc65b6b996595310a4c6a73367332fc32b4df Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 7 Nov 2014 17:17:41 +0100 Subject: drm/tegra: dsi: Replace 1000000000UL by NSEC_PER_SEC Using the symbolic constant instantly provides a lot more context. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dsi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/gpu/drm/tegra/dsi.c') diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index 6646fa2adc38..2f12258ecd74 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -349,7 +349,7 @@ static int tegra_dsi_set_phy_timing(struct tegra_dsi *dsi) if (rate < 0) return rate; - period = DIV_ROUND_CLOSEST(1000000000UL, rate * 2); + period = DIV_ROUND_CLOSEST(NSEC_PER_SEC, rate * 2); err = mipi_dphy_timing_get_default(&timing, period); if (err < 0) -- cgit v1.2.3 From 030611ecc5d8b1daf8de110600c8771de45d398d Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 13 Nov 2014 14:32:06 +0100 Subject: drm/tegra: dsi: Replace 1000000 by USEC_PER_SEC Using the symbolic constant instantly provides a lot more context. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dsi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/gpu/drm/tegra/dsi.c') diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index 2f12258ecd74..8940360ccc9c 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -820,7 +820,7 @@ static int tegra_output_dsi_setup_clock(struct tegra_output *output, /* * Compute bit clock and round up to the next MHz. */ - plld = DIV_ROUND_UP(bclk * 8, 1000000) * 1000000; + plld = DIV_ROUND_UP(bclk * 8, USEC_PER_SEC) * USEC_PER_SEC; /* * We divide the frequency by two here, but we make up for that by -- cgit v1.2.3 From 7e3bc3a98fd1df5839cdc5cbce4dfdb9e4c03655 Mon Sep 17 00:00:00 2001 From: Sean Paul Date: Tue, 7 Oct 2014 16:04:42 +0200 Subject: drm/tegra: dsi: Set up PHY_TIMING & BTA_TIMING registers earlier Make sure the DSI PHY_TIMING and BTA_TIMING registers are initialized when the clocks are set up as opposed to when the output is enabled. This makes sure that the PHY timings are properly set up when the panel is prepared and that DCS commands sent at that time use the appropriate timings. Signed-off-by: Sean Paul Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dsi.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'drivers/gpu/drm/tegra/dsi.c') diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index 8940360ccc9c..33f67fd601c6 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -389,6 +389,9 @@ static int tegra_dsi_set_phy_timing(struct tegra_dsi *dsi) DSI_TIMING_FIELD(timing.tago, period, 1); tegra_dsi_writel(dsi, value, DSI_BTA_TIMING); + if (dsi->slave) + return tegra_dsi_set_phy_timing(dsi->slave); + return 0; } @@ -536,10 +539,6 @@ static int tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe, value &= ~DSI_CONTROL_HOST_ENABLE; tegra_dsi_writel(dsi, value, DSI_CONTROL); - err = tegra_dsi_set_phy_timing(dsi); - if (err < 0) - return err; - for (i = 0; i < NUM_PKT_SEQ; i++) tegra_dsi_writel(dsi, pkt_seq[i], DSI_PKT_SEQ_0_LO + i); @@ -860,6 +859,10 @@ static int tegra_output_dsi_setup_clock(struct tegra_output *output, */ tegra_dsi_set_timeout(dsi, bclk, vrefresh); + err = tegra_dsi_set_phy_timing(dsi); + if (err < 0) + return err; + return 0; } -- cgit v1.2.3