Commit 5b901e78 authored by Thierry Reding's avatar Thierry Reding

drm/tegra: dsi: Demidlayer

Implement encoder and connector within the DSI driver itself using the
Tegra output helpers rather than using the Tegra output as midlayer. By
doing so one level of indirection is removed and output drivers become
more flexible while keeping the majority of the advantages provided by
the common output helpers.
Signed-off-by: default avatarThierry Reding <treding@nvidia.com>
parent 59682719
......@@ -193,7 +193,6 @@ struct tegra_output_ops {
};
enum tegra_output_type {
TEGRA_OUTPUT_DSI,
TEGRA_OUTPUT_EDP,
};
......
......@@ -51,7 +51,6 @@ struct tegra_dsi {
struct mipi_dsi_host host;
struct regulator *vdd;
bool enabled;
unsigned int video_fifo_depth;
unsigned int host_fifo_depth;
......@@ -628,46 +627,6 @@ static int tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe,
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;
tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS);
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 |
PW4_ENABLE | PM0_ENABLE | PM1_ENABLE;
tegra_dc_writel(dc, value, DC_CMD_DISPLAY_POWER_CONTROL);
tegra_dc_commit(dc);
/* enable DSI controller */
tegra_dsi_enable(dsi);
dsi->enabled = true;
return 0;
}
static int tegra_dsi_wait_idle(struct tegra_dsi *dsi, unsigned long timeout)
{
u32 value;
......@@ -704,6 +663,29 @@ static void tegra_dsi_ganged_disable(struct tegra_dsi *dsi)
tegra_dsi_writel(dsi, 0, DSI_GANGED_MODE_CONTROL);
}
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);
if (dsi->slave)
tegra_dsi_set_timeout(dsi->slave, bclk, vrefresh);
}
static void tegra_dsi_disable(struct tegra_dsi *dsi)
{
u32 value;
......@@ -747,82 +729,51 @@ static void tegra_dsi_soft_reset(struct tegra_dsi *dsi)
tegra_dsi_soft_reset(dsi->slave);
}
static int tegra_output_dsi_disable(struct tegra_output *output)
static void tegra_dsi_connector_dpms(struct drm_connector *connector, int mode)
{
struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc);
struct tegra_dsi *dsi = to_dsi(output);
u32 value;
int err;
if (!dsi->enabled)
return 0;
tegra_dsi_video_disable(dsi);
/*
* The following accesses registers of the display controller, so make
* sure it's only executed when the output is attached to one.
*/
if (dc) {
value = tegra_dc_readl(dc, DC_CMD_DISPLAY_POWER_CONTROL);
value &= ~(PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE |
PW4_ENABLE | PM0_ENABLE | PM1_ENABLE);
tegra_dc_writel(dc, value, DC_CMD_DISPLAY_POWER_CONTROL);
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);
tegra_dsi_disable(dsi);
dsi->enabled = false;
return 0;
}
static void tegra_dsi_set_timeout(struct tegra_dsi *dsi, unsigned long bclk,
unsigned int vrefresh)
{
unsigned int timeout;
u32 value;
static const struct drm_connector_funcs tegra_dsi_connector_funcs = {
.dpms = tegra_dsi_connector_dpms,
.detect = tegra_output_connector_detect,
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = tegra_output_connector_destroy,
};
/* 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);
static enum drm_mode_status
tegra_dsi_connector_mode_valid(struct drm_connector *connector,
struct drm_display_mode *mode)
{
return MODE_OK;
}
/* 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);
static const struct drm_connector_helper_funcs tegra_dsi_connector_helper_funcs = {
.get_modes = tegra_output_connector_get_modes,
.mode_valid = tegra_dsi_connector_mode_valid,
.best_encoder = tegra_output_connector_best_encoder,
};
value = DSI_TALLY_TA(0) | DSI_TALLY_LRX(0) | DSI_TALLY_HTX(0);
tegra_dsi_writel(dsi, value, DSI_TO_TALLY);
static const struct drm_encoder_funcs tegra_dsi_encoder_funcs = {
.destroy = tegra_output_encoder_destroy,
};
if (dsi->slave)
tegra_dsi_set_timeout(dsi->slave, bclk, vrefresh);
static void tegra_dsi_encoder_dpms(struct drm_encoder *encoder, int mode)
{
}
static int tegra_output_dsi_setup_clock(struct tegra_output *output,
struct clk *clk, unsigned long pclk,
unsigned int *divp)
static bool tegra_dsi_encoder_mode_fixup(struct drm_encoder *encoder,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted)
{
struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc);
struct drm_display_mode *mode = &dc->base.mode;
struct tegra_output *output = encoder_to_output(encoder);
struct tegra_dc *dc = to_tegra_dc(encoder->crtc);
unsigned int mul, div, scdiv, vrefresh, lanes;
struct tegra_dsi *dsi = to_dsi(output);
unsigned int mul, div, vrefresh, lanes;
unsigned long bclk, plld;
unsigned long pclk, bclk, plld;
int err;
lanes = tegra_dsi_get_lanes(dsi);
pclk = mode->clock * 1000;
err = tegra_dsi_get_muldiv(dsi->format, &mul, &div);
if (err < 0)
......@@ -847,19 +798,6 @@ static int tegra_output_dsi_setup_clock(struct tegra_output *output,
*/
plld /= 2;
err = clk_set_parent(clk, dsi->clk_parent);
if (err < 0) {
dev_err(dsi->dev, "failed to set parent clock: %d\n", err);
return err;
}
err = clk_set_rate(dsi->clk_parent, plld);
if (err < 0) {
dev_err(dsi->dev, "failed to set base clock rate to %lu Hz\n",
plld);
return err;
}
/*
* 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
......@@ -870,39 +808,132 @@ 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 * lanes)) - 2;
scdiv = ((8 * mul) / (div * lanes)) - 2;
err = tegra_dc_setup_clock(dc, dsi->clk_parent, plld, scdiv);
if (err < 0) {
dev_err(output->dev, "failed to setup DC clock: %d\n", err);
return false;
}
err = clk_set_rate(dsi->clk_parent, plld);
if (err < 0) {
dev_err(dsi->dev, "failed to set clock rate to %lu Hz\n",
plld);
return false;
}
/*
* XXX: Move the below somewhere else so that we don't need to have
* access to the vrefresh in this function?
*/
tegra_dsi_set_timeout(dsi, bclk, vrefresh);
err = tegra_dsi_set_phy_timing(dsi);
if (err < 0)
return err;
if (err < 0) {
dev_err(dsi->dev, "failed to setup D-PHY timing: %d\n", err);
return false;
}
return 0;
return true;
}
static void tegra_dsi_encoder_prepare(struct drm_encoder *encoder)
{
}
static int tegra_output_dsi_check_mode(struct tegra_output *output,
static void tegra_dsi_encoder_commit(struct drm_encoder *encoder)
{
}
static void tegra_dsi_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
enum drm_mode_status *status)
struct drm_display_mode *adjusted)
{
struct tegra_output *output = encoder_to_output(encoder);
struct tegra_dc *dc = to_tegra_dc(encoder->crtc);
struct tegra_dsi *dsi = to_dsi(output);
u32 value;
int err;
err = tegra_dsi_configure(dsi, dc->pipe, mode);
if (err < 0) {
dev_err(dsi->dev, "failed to configure DSI: %d\n", err);
return;
}
if (output->panel)
drm_panel_prepare(output->panel);
/* 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);
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 |
PW4_ENABLE | PM0_ENABLE | PM1_ENABLE;
tegra_dc_writel(dc, value, DC_CMD_DISPLAY_POWER_CONTROL);
tegra_dc_commit(dc);
/* enable DSI controller */
tegra_dsi_enable(dsi);
if (output->panel)
drm_panel_enable(output->panel);
return;
}
static void tegra_dsi_encoder_disable(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);
u32 value;
int err;
if (output->panel)
drm_panel_disable(output->panel);
tegra_dsi_video_disable(dsi);
/*
* FIXME: For now, always assume that the mode is okay.
* The following accesses registers of the display controller, so make
* sure it's only executed when the output is attached to one.
*/
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);
*status = MODE_OK;
tegra_dc_commit(dc);
}
return 0;
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_unprepare(output->panel);
tegra_dsi_disable(dsi);
return;
}
static const struct tegra_output_ops dsi_ops = {
.enable = tegra_output_dsi_enable,
.disable = tegra_output_dsi_disable,
.setup_clock = tegra_output_dsi_setup_clock,
.check_mode = tegra_output_dsi_check_mode,
static const struct drm_encoder_helper_funcs tegra_dsi_encoder_helper_funcs = {
.dpms = tegra_dsi_encoder_dpms,
.mode_fixup = tegra_dsi_encoder_mode_fixup,
.prepare = tegra_dsi_encoder_prepare,
.commit = tegra_dsi_encoder_commit,
.mode_set = tegra_dsi_encoder_mode_set,
.disable = tegra_dsi_encoder_disable,
};
static int tegra_dsi_pad_enable(struct tegra_dsi *dsi)
......@@ -952,15 +983,30 @@ static int tegra_dsi_init(struct host1x_client *client)
/* 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;
}
drm_connector_init(drm, &dsi->output.connector,
&tegra_dsi_connector_funcs,
DRM_MODE_CONNECTOR_DSI);
drm_connector_helper_add(&dsi->output.connector,
&tegra_dsi_connector_helper_funcs);
dsi->output.connector.dpms = DRM_MODE_DPMS_OFF;
if (dsi->output.panel)
drm_panel_attach(dsi->output.panel,
&dsi->output.connector);
drm_encoder_init(drm, &dsi->output.encoder,
&tegra_dsi_encoder_funcs,
DRM_MODE_ENCODER_DSI);
drm_encoder_helper_add(&dsi->output.encoder,
&tegra_dsi_encoder_helper_funcs);
drm_mode_connector_attach_encoder(&dsi->output.connector,
&dsi->output.encoder);
drm_connector_register(&dsi->output.connector);
dsi->output.encoder.possible_crtcs = 0x3;
}
if (IS_ENABLED(CONFIG_DEBUG_FS)) {
......@@ -981,28 +1027,14 @@ static int tegra_dsi_exit(struct host1x_client *client)
struct tegra_dsi *dsi = host1x_client_to_dsi(client);
int err;
tegra_output_exit(&dsi->output);
if (IS_ENABLED(CONFIG_DEBUG_FS)) {
err = tegra_dsi_debugfs_exit(dsi);
if (err < 0)
dev_err(dsi->dev, "debugfs cleanup failed: %d\n", 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;
}
}
reset_control_assert(dsi->rst);
return 0;
......@@ -1547,6 +1579,12 @@ static int tegra_dsi_remove(struct platform_device *pdev)
return err;
}
err = tegra_output_remove(&dsi->output);
if (err < 0) {
dev_err(&pdev->dev, "failed to remove output: %d\n", err);
return err;
}
mipi_dsi_host_unregister(&dsi->host);
tegra_mipi_free(dsi->mipi);
......@@ -1555,12 +1593,6 @@ static int tegra_dsi_remove(struct platform_device *pdev)
clk_disable_unprepare(dsi->clk);
reset_control_assert(dsi->rst);
err = tegra_output_remove(&dsi->output);
if (err < 0) {
dev_err(&pdev->dev, "failed to remove output: %d\n", err);
return err;
}
return 0;
}
......
......@@ -274,11 +274,6 @@ int tegra_output_init(struct drm_device *drm, struct tegra_output *output)
int connector, encoder;
switch (output->type) {
case TEGRA_OUTPUT_DSI:
connector = DRM_MODE_CONNECTOR_DSI;
encoder = DRM_MODE_ENCODER_DSI;
break;
case TEGRA_OUTPUT_EDP:
connector = DRM_MODE_CONNECTOR_eDP;
encoder = DRM_MODE_ENCODER_TMDS;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment