|
@@ -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 @@ static inline struct tegra_dsi *to_dsi(struct tegra_output *output)
|
|
|
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 @@ static const u32 pkt_seq_command_mode[NUM_PKT_SEQ] = {
|
|
|
[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 @@ static unsigned int tegra_dsi_get_lanes(struct tegra_dsi *dsi)
|
|
|
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 @@ static int tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe,
|
|
|
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 @@ static int tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe,
|
|
|
|
|
|
/* 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 @@ static int tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe,
|
|
|
}
|
|
|
|
|
|
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 @@ static int tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe,
|
|
|
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_dpms(struct drm_connector *connector, int mode)
|
|
|
{
|
|
|
}
|
|
|
|
|
|
+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 @@ static bool tegra_dsi_encoder_mode_fixup(struct drm_encoder *encoder,
|
|
|
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 @@ static bool tegra_dsi_encoder_mode_fixup(struct drm_encoder *encoder,
|
|
|
* 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 @@ static bool tegra_dsi_encoder_mode_fixup(struct drm_encoder *encoder,
|
|
|
|
|
|
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 @@ static void tegra_dsi_encoder_mode_set(struct drm_encoder *encoder,
|
|
|
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 @@ static void tegra_dsi_encoder_disable(struct drm_encoder *encoder)
|
|
|
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 @@ static const struct drm_encoder_helper_funcs tegra_dsi_encoder_helper_funcs = {
|
|
|
.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)
|