drm/tegra: dsi: Implement ->atomic_check()
The implementation of the ->atomic_check() callback precomputes all
parameters to check if the given configuration can be applied. If so the
precomputed values are stored in the atomic state object for the encoder
and applied during modeset. In that way the modeset no longer needs to
perform any checking but simply program values into registers.
Signed-off-by: Thierry Reding <treding@nvidia.com>
diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c
index d98058d..00b3071 100644
--- a/drivers/gpu/drm/tegra/dsi.c
+++ b/drivers/gpu/drm/tegra/dsi.c
@@ -28,6 +28,28 @@
#include "dsi.h"
#include "mipi-phy.h"
+struct tegra_dsi_state {
+ struct drm_connector_state base;
+
+ struct mipi_dphy_timing timing;
+ unsigned long period;
+
+ unsigned int vrefresh;
+ unsigned int lanes;
+ unsigned long pclk;
+ unsigned long bclk;
+
+ enum tegra_dsi_format format;
+ unsigned int mul;
+ unsigned int div;
+};
+
+static inline struct tegra_dsi_state *
+to_dsi_state(struct drm_connector_state *state)
+{
+ return container_of(state, struct tegra_dsi_state, base);
+}
+
struct tegra_dsi {
struct host1x_client client;
struct tegra_output output;
@@ -77,6 +99,11 @@
return container_of(output, struct tegra_dsi, output);
}
+static struct tegra_dsi_state *tegra_dsi_get_state(struct tegra_dsi *dsi)
+{
+ return to_dsi_state(dsi->output.connector.state);
+}
+
static inline u32 tegra_dsi_readl(struct tegra_dsi *dsi, unsigned long reg)
{
return readl(dsi->regs + (reg << 2));
@@ -335,62 +362,36 @@
[11] = 0,
};
-static int tegra_dsi_set_phy_timing(struct tegra_dsi *dsi)
+static void tegra_dsi_set_phy_timing(struct tegra_dsi *dsi,
+ unsigned long period,
+ const struct mipi_dphy_timing *timing)
{
- struct mipi_dphy_timing timing;
- unsigned long period;
u32 value;
- long rate;
- int err;
- rate = clk_get_rate(dsi->clk);
- if (rate < 0)
- return rate;
-
- period = DIV_ROUND_CLOSEST(NSEC_PER_SEC, rate * 2);
-
- err = mipi_dphy_timing_get_default(&timing, period);
- if (err < 0)
- return err;
-
- err = mipi_dphy_timing_validate(&timing, period);
- if (err < 0) {
- dev_err(dsi->dev, "failed to validate D-PHY timing: %d\n", err);
- return err;
- }
-
- /*
- * The D-PHY timing fields below are expressed in byte-clock cycles,
- * so multiply the period by 8.
- */
- period *= 8;
-
- value = DSI_TIMING_FIELD(timing.hsexit, period, 1) << 24 |
- DSI_TIMING_FIELD(timing.hstrail, period, 0) << 16 |
- DSI_TIMING_FIELD(timing.hszero, period, 3) << 8 |
- DSI_TIMING_FIELD(timing.hsprepare, period, 1);
+ value = DSI_TIMING_FIELD(timing->hsexit, period, 1) << 24 |
+ DSI_TIMING_FIELD(timing->hstrail, period, 0) << 16 |
+ DSI_TIMING_FIELD(timing->hszero, period, 3) << 8 |
+ DSI_TIMING_FIELD(timing->hsprepare, period, 1);
tegra_dsi_writel(dsi, value, DSI_PHY_TIMING_0);
- value = DSI_TIMING_FIELD(timing.clktrail, period, 1) << 24 |
- DSI_TIMING_FIELD(timing.clkpost, period, 1) << 16 |
- DSI_TIMING_FIELD(timing.clkzero, period, 1) << 8 |
- DSI_TIMING_FIELD(timing.lpx, period, 1);
+ value = DSI_TIMING_FIELD(timing->clktrail, period, 1) << 24 |
+ DSI_TIMING_FIELD(timing->clkpost, period, 1) << 16 |
+ DSI_TIMING_FIELD(timing->clkzero, period, 1) << 8 |
+ DSI_TIMING_FIELD(timing->lpx, period, 1);
tegra_dsi_writel(dsi, value, DSI_PHY_TIMING_1);
- value = DSI_TIMING_FIELD(timing.clkprepare, period, 1) << 16 |
- DSI_TIMING_FIELD(timing.clkpre, period, 1) << 8 |
+ value = DSI_TIMING_FIELD(timing->clkprepare, period, 1) << 16 |
+ DSI_TIMING_FIELD(timing->clkpre, period, 1) << 8 |
DSI_TIMING_FIELD(0xff * period, period, 0) << 0;
tegra_dsi_writel(dsi, value, DSI_PHY_TIMING_2);
- value = DSI_TIMING_FIELD(timing.taget, period, 1) << 16 |
- DSI_TIMING_FIELD(timing.tasure, period, 1) << 8 |
- DSI_TIMING_FIELD(timing.tago, period, 1);
+ value = DSI_TIMING_FIELD(timing->taget, period, 1) << 16 |
+ DSI_TIMING_FIELD(timing->tasure, period, 1) << 8 |
+ 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;
+ tegra_dsi_set_phy_timing(dsi->slave, period, timing);
}
static int tegra_dsi_get_muldiv(enum mipi_dsi_pixel_format format,
@@ -482,14 +483,22 @@
return dsi->lanes;
}
-static int tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe,
- const struct drm_display_mode *mode)
+static void tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe,
+ const struct drm_display_mode *mode)
{
unsigned int hact, hsw, hbp, hfp, i, mul, div;
- enum tegra_dsi_format format;
+ struct tegra_dsi_state *state;
const u32 *pkt_seq;
u32 value;
- int err;
+
+ /* XXX: pass in state into this function? */
+ if (dsi->master)
+ state = tegra_dsi_get_state(dsi->master);
+ else
+ state = tegra_dsi_get_state(dsi);
+
+ mul = state->mul;
+ div = state->div;
if (dsi->flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) {
DRM_DEBUG_KMS("Non-burst video mode with sync pulses\n");
@@ -502,15 +511,8 @@
pkt_seq = pkt_seq_command_mode;
}
- err = tegra_dsi_get_muldiv(dsi->format, &mul, &div);
- if (err < 0)
- return err;
-
- err = tegra_dsi_get_format(dsi->format, &format);
- if (err < 0)
- return err;
-
- value = DSI_CONTROL_CHANNEL(0) | DSI_CONTROL_FORMAT(format) |
+ value = DSI_CONTROL_CHANNEL(0) |
+ DSI_CONTROL_FORMAT(state->format) |
DSI_CONTROL_LANES(dsi->lanes - 1) |
DSI_CONTROL_SOURCE(pipe);
tegra_dsi_writel(dsi, value, DSI_CONTROL);
@@ -589,8 +591,8 @@
/* set SOL delay */
if (dsi->master || dsi->slave) {
- unsigned int lanes = tegra_dsi_get_lanes(dsi);
unsigned long delay, bclk, bclk_ganged;
+ unsigned int lanes = state->lanes;
/* SOL to valid, valid to FIFO and FIFO write delay */
delay = 4 + 4 + 2;
@@ -610,9 +612,7 @@
}
if (dsi->slave) {
- err = tegra_dsi_configure(dsi->slave, pipe, mode);
- if (err < 0)
- return err;
+ tegra_dsi_configure(dsi->slave, pipe, mode);
/*
* TODO: Support modes other than symmetrical left-right
@@ -622,8 +622,6 @@
tegra_dsi_ganged_enable(dsi->slave, mode->hdisplay / 2,
mode->hdisplay / 2);
}
-
- return 0;
}
static int tegra_dsi_wait_idle(struct tegra_dsi *dsi, unsigned long timeout)
@@ -732,13 +730,38 @@
{
}
+static void tegra_dsi_connector_reset(struct drm_connector *connector)
+{
+ struct tegra_dsi_state *state;
+
+ kfree(connector->state);
+ connector->state = NULL;
+
+ state = kzalloc(sizeof(*state), GFP_KERNEL);
+ if (state)
+ connector->state = &state->base;
+}
+
+static struct drm_connector_state *
+tegra_dsi_connector_duplicate_state(struct drm_connector *connector)
+{
+ struct tegra_dsi_state *state = to_dsi_state(connector->state);
+ struct tegra_dsi_state *copy;
+
+ copy = kmemdup(state, sizeof(*state), GFP_KERNEL);
+ if (!copy)
+ return NULL;
+
+ return ©->base;
+}
+
static const struct drm_connector_funcs tegra_dsi_connector_funcs = {
.dpms = tegra_dsi_connector_dpms,
- .reset = drm_atomic_helper_connector_reset,
+ .reset = tegra_dsi_connector_reset,
.detect = tegra_output_connector_detect,
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = tegra_output_connector_destroy,
- .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_duplicate_state = tegra_dsi_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
@@ -771,7 +794,9 @@
struct tegra_dc *dc = to_tegra_dc(encoder->crtc);
unsigned int mul, div, scdiv, vrefresh, lanes;
struct tegra_dsi *dsi = to_dsi(output);
+ struct mipi_dphy_timing timing;
unsigned long pclk, bclk, plld;
+ unsigned long period;
int err;
lanes = tegra_dsi_get_lanes(dsi);
@@ -792,6 +817,7 @@
* Compute bit clock and round up to the next MHz.
*/
plld = DIV_ROUND_UP(bclk * 8, USEC_PER_SEC) * USEC_PER_SEC;
+ period = DIV_ROUND_CLOSEST(NSEC_PER_SEC, plld);
/*
* We divide the frequency by two here, but we make up for that by
@@ -827,12 +853,22 @@
tegra_dsi_set_timeout(dsi, bclk, vrefresh);
- err = tegra_dsi_set_phy_timing(dsi);
+ err = mipi_dphy_timing_get_default(&timing, period);
+ if (err < 0)
+ return err;
+
+ err = mipi_dphy_timing_validate(&timing, period);
if (err < 0) {
- dev_err(dsi->dev, "failed to setup D-PHY timing: %d\n", err);
- return false;
+ dev_err(dsi->dev, "failed to validate D-PHY timing: %d\n", err);
+ return err;
}
+ /*
+ * The D-PHY timing fields are expressed in byte-clock cycles, so
+ * multiply the period by 8.
+ */
+ tegra_dsi_set_phy_timing(dsi, period * 8, &timing);
+
return true;
}
@@ -851,19 +887,24 @@
struct tegra_output *output = encoder_to_output(encoder);
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;
+ state = tegra_dsi_get_state(dsi);
- err = tegra_dsi_configure(dsi, dc->pipe, mode);
- if (err < 0) {
- dev_err(dsi->dev, "failed to configure DSI: %d\n", err);
- return;
- }
+ 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_prepare(output->panel);
+ tegra_dsi_configure(dsi, dc->pipe, mode);
+
/* enable display controller */
value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS);
value |= DSI_ENABLE;
@@ -929,6 +970,87 @@
return;
}
+static int
+tegra_dsi_encoder_atomic_check(struct drm_encoder *encoder,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ struct tegra_output *output = encoder_to_output(encoder);
+ struct tegra_dsi_state *state = to_dsi_state(conn_state);
+ struct tegra_dc *dc = to_tegra_dc(conn_state->crtc);
+ struct tegra_dsi *dsi = to_dsi(output);
+ unsigned int scdiv;
+ unsigned long plld;
+ int err;
+
+ state->pclk = crtc_state->mode.clock * 1000;
+
+ err = tegra_dsi_get_muldiv(dsi->format, &state->mul, &state->div);
+ if (err < 0)
+ return err;
+
+ state->lanes = tegra_dsi_get_lanes(dsi);
+
+ err = tegra_dsi_get_format(dsi->format, &state->format);
+ if (err < 0)
+ return err;
+
+ state->vrefresh = drm_mode_vrefresh(&crtc_state->mode);
+
+ /* compute byte clock */
+ state->bclk = (state->pclk * state->mul) / (state->div * state->lanes);
+
+ DRM_DEBUG_KMS("mul: %u, div: %u, lanes: %u\n", state->mul, state->div,
+ state->lanes);
+ DRM_DEBUG_KMS("format: %u, vrefresh: %u\n", state->format,
+ state->vrefresh);
+ DRM_DEBUG_KMS("bclk: %lu\n", state->bclk);
+
+ /*
+ * Compute bit clock and round up to the next MHz.
+ */
+ plld = DIV_ROUND_UP(state->bclk * 8, USEC_PER_SEC) * USEC_PER_SEC;
+ state->period = DIV_ROUND_CLOSEST(NSEC_PER_SEC, plld);
+
+ err = mipi_dphy_timing_get_default(&state->timing, state->period);
+ if (err < 0)
+ return err;
+
+ err = mipi_dphy_timing_validate(&state->timing, state->period);
+ if (err < 0) {
+ dev_err(dsi->dev, "failed to validate D-PHY timing: %d\n", err);
+ return err;
+ }
+
+ /*
+ * We divide the frequency by two here, but we make up for that by
+ * setting the shift clock divider (further below) to half of the
+ * correct value.
+ */
+ plld /= 2;
+
+ /*
+ * Derive pixel clock from bit clock using the shift clock divider.
+ * Note that this is only half of what we would expect, but we need
+ * that to make up for the fact that we divided the bit clock by a
+ * factor of two above.
+ *
+ * It's not clear exactly why this is necessary, but the display is
+ * not working properly otherwise. Perhaps the PLLs cannot generate
+ * frequencies sufficiently high.
+ */
+ scdiv = ((8 * state->mul) / (state->div * state->lanes)) - 2;
+
+ err = tegra_dc_state_setup_clock(dc, crtc_state, dsi->clk_parent,
+ plld, scdiv);
+ if (err < 0) {
+ dev_err(output->dev, "failed to setup CRTC state: %d\n", err);
+ return err;
+ }
+
+ return err;
+}
+
static const struct drm_encoder_helper_funcs tegra_dsi_encoder_helper_funcs = {
.dpms = tegra_dsi_encoder_dpms,
.mode_fixup = tegra_dsi_encoder_mode_fixup,
@@ -936,6 +1058,7 @@
.commit = tegra_dsi_encoder_commit,
.mode_set = tegra_dsi_encoder_mode_set,
.disable = tegra_dsi_encoder_disable,
+ .atomic_check = tegra_dsi_encoder_atomic_check,
};
static int tegra_dsi_pad_enable(struct tegra_dsi *dsi)