Commit 59682719 authored by Thierry Reding's avatar Thierry Reding

drm/tegra: hdmi: Demidlayer

Implement encoder and connector within the HDMI 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 3b0e5855
...@@ -193,7 +193,6 @@ struct tegra_output_ops { ...@@ -193,7 +193,6 @@ struct tegra_output_ops {
}; };
enum tegra_output_type { enum tegra_output_type {
TEGRA_OUTPUT_HDMI,
TEGRA_OUTPUT_DSI, TEGRA_OUTPUT_DSI,
TEGRA_OUTPUT_EDP, TEGRA_OUTPUT_EDP,
}; };
......
...@@ -9,10 +9,14 @@ ...@@ -9,10 +9,14 @@
#include <linux/clk.h> #include <linux/clk.h>
#include <linux/debugfs.h> #include <linux/debugfs.h>
#include <linux/gpio.h>
#include <linux/hdmi.h> #include <linux/hdmi.h>
#include <linux/regulator/consumer.h> #include <linux/regulator/consumer.h>
#include <linux/reset.h> #include <linux/reset.h>
#include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h>
#include "hdmi.h" #include "hdmi.h"
#include "drm.h" #include "drm.h"
#include "dc.h" #include "dc.h"
...@@ -40,7 +44,6 @@ struct tegra_hdmi { ...@@ -40,7 +44,6 @@ struct tegra_hdmi {
struct host1x_client client; struct host1x_client client;
struct tegra_output output; struct tegra_output output;
struct device *dev; struct device *dev;
bool enabled;
struct regulator *hdmi; struct regulator *hdmi;
struct regulator *pll; struct regulator *pll;
...@@ -768,53 +771,107 @@ static bool tegra_output_is_hdmi(struct tegra_output *output) ...@@ -768,53 +771,107 @@ static bool tegra_output_is_hdmi(struct tegra_output *output)
return drm_detect_hdmi_monitor(edid); return drm_detect_hdmi_monitor(edid);
} }
static int tegra_output_hdmi_enable(struct tegra_output *output) static void tegra_hdmi_connector_dpms(struct drm_connector *connector,
int mode)
{ {
unsigned int h_sync_width, h_front_porch, h_back_porch, i, rekey; }
struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc);
struct drm_display_mode *mode = &dc->base.mode; static const struct drm_connector_funcs tegra_hdmi_connector_funcs = {
.dpms = tegra_hdmi_connector_dpms,
.detect = tegra_output_connector_detect,
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = tegra_output_connector_destroy,
};
static enum drm_mode_status
tegra_hdmi_connector_mode_valid(struct drm_connector *connector,
struct drm_display_mode *mode)
{
struct tegra_output *output = connector_to_output(connector);
struct tegra_hdmi *hdmi = to_hdmi(output); struct tegra_hdmi *hdmi = to_hdmi(output);
struct device_node *node = hdmi->dev->of_node; unsigned long pclk = mode->clock * 1000;
unsigned int pulse_start, div82, pclk; enum drm_mode_status status = MODE_OK;
int retries = 1000; struct clk *parent;
u32 value; long err;
int err;
if (hdmi->enabled) parent = clk_get_parent(hdmi->clk_parent);
return 0;
hdmi->dvi = !tegra_output_is_hdmi(output); err = clk_round_rate(parent, pclk * 4);
if (err <= 0)
status = MODE_NOCLOCK;
pclk = mode->clock * 1000; return status;
h_sync_width = mode->hsync_end - mode->hsync_start; }
h_back_porch = mode->htotal - mode->hsync_end;
h_front_porch = mode->hsync_start - mode->hdisplay;
err = regulator_enable(hdmi->pll); static const struct drm_connector_helper_funcs
tegra_hdmi_connector_helper_funcs = {
.get_modes = tegra_output_connector_get_modes,
.mode_valid = tegra_hdmi_connector_mode_valid,
.best_encoder = tegra_output_connector_best_encoder,
};
static const struct drm_encoder_funcs tegra_hdmi_encoder_funcs = {
.destroy = tegra_output_encoder_destroy,
};
static void tegra_hdmi_encoder_dpms(struct drm_encoder *encoder, int mode)
{
}
static bool tegra_hdmi_encoder_mode_fixup(struct drm_encoder *encoder,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted)
{
struct tegra_output *output = encoder_to_output(encoder);
struct tegra_dc *dc = to_tegra_dc(encoder->crtc);
struct tegra_hdmi *hdmi = to_hdmi(output);
unsigned long pclk = mode->clock * 1000;
int err;
err = tegra_dc_setup_clock(dc, hdmi->clk_parent, pclk, 0);
if (err < 0) { if (err < 0) {
dev_err(hdmi->dev, "failed to enable PLL regulator: %d\n", err); dev_err(output->dev, "failed to setup DC clock: %d\n", err);
return err; return false;
} }
err = regulator_enable(hdmi->vdd); err = clk_set_rate(hdmi->clk_parent, pclk);
if (err < 0) { if (err < 0) {
dev_err(hdmi->dev, "failed to enable VDD regulator: %d\n", err); dev_err(output->dev, "failed to set clock rate to %lu Hz\n",
return err; pclk);
return false;
} }
err = clk_set_rate(hdmi->clk, pclk); return true;
if (err < 0) }
return err;
err = clk_prepare_enable(hdmi->clk); static void tegra_hdmi_encoder_prepare(struct drm_encoder *encoder)
if (err < 0) { {
dev_err(hdmi->dev, "failed to enable clock: %d\n", err); }
return err;
}
reset_control_assert(hdmi->rst); static void tegra_hdmi_encoder_commit(struct drm_encoder *encoder)
usleep_range(1000, 2000); {
reset_control_deassert(hdmi->rst); }
static void tegra_hdmi_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted)
{
unsigned int h_sync_width, h_front_porch, h_back_porch, i, rekey;
struct tegra_output *output = encoder_to_output(encoder);
struct tegra_dc *dc = to_tegra_dc(encoder->crtc);
struct device_node *node = output->dev->of_node;
struct tegra_hdmi *hdmi = to_hdmi(output);
unsigned int pulse_start, div82, pclk;
int retries = 1000;
u32 value;
int err;
hdmi->dvi = !tegra_output_is_hdmi(output);
pclk = mode->clock * 1000;
h_sync_width = mode->hsync_end - mode->hsync_start;
h_back_porch = mode->htotal - mode->hsync_end;
h_front_porch = mode->hsync_start - mode->hdisplay;
/* power up sequence */ /* power up sequence */
value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_SOR_PLL0); value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_SOR_PLL0);
...@@ -1000,104 +1057,33 @@ static int tegra_output_hdmi_enable(struct tegra_output *output) ...@@ -1000,104 +1057,33 @@ static int tegra_output_hdmi_enable(struct tegra_output *output)
tegra_dc_commit(dc); tegra_dc_commit(dc);
/* TODO: add HDCP support */ /* TODO: add HDCP support */
hdmi->enabled = true;
return 0;
} }
static int tegra_output_hdmi_disable(struct tegra_output *output) static void tegra_hdmi_encoder_disable(struct drm_encoder *encoder)
{ {
struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc); struct tegra_dc *dc = to_tegra_dc(encoder->crtc);
struct tegra_hdmi *hdmi = to_hdmi(output);
u32 value; u32 value;
if (!hdmi->enabled)
return 0;
/* /*
* The following accesses registers of the display controller, so make * The following accesses registers of the display controller, so make
* sure it's only executed when the output is attached to one. * sure it's only executed when the output is attached to one.
*/ */
if (dc) { if (dc) {
/*
* XXX: We can't do this here because it causes HDMI to go
* into an erroneous state with the result that HDMI won't
* properly work once disabled. See also a similar symptom
* for the SOR output.
*/
/*
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 = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS);
value &= ~HDMI_ENABLE; value &= ~HDMI_ENABLE;
tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS); tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS);
tegra_dc_commit(dc); tegra_dc_commit(dc);
} }
clk_disable_unprepare(hdmi->clk);
reset_control_assert(hdmi->rst);
regulator_disable(hdmi->vdd);
regulator_disable(hdmi->pll);
hdmi->enabled = false;
return 0;
}
static int tegra_output_hdmi_setup_clock(struct tegra_output *output,
struct clk *clk, unsigned long pclk,
unsigned int *div)
{
struct tegra_hdmi *hdmi = to_hdmi(output);
int err;
err = clk_set_parent(clk, hdmi->clk_parent);
if (err < 0) {
dev_err(output->dev, "failed to set parent: %d\n", err);
return err;
}
err = clk_set_rate(hdmi->clk_parent, pclk);
if (err < 0)
dev_err(output->dev, "failed to set clock rate to %lu Hz\n",
pclk);
*div = 0;
return 0;
}
static int tegra_output_hdmi_check_mode(struct tegra_output *output,
struct drm_display_mode *mode,
enum drm_mode_status *status)
{
struct tegra_hdmi *hdmi = to_hdmi(output);
unsigned long pclk = mode->clock * 1000;
struct clk *parent;
long err;
parent = clk_get_parent(hdmi->clk_parent);
err = clk_round_rate(parent, pclk * 4);
if (err <= 0)
*status = MODE_NOCLOCK;
else
*status = MODE_OK;
return 0;
} }
static const struct tegra_output_ops hdmi_ops = { static const struct drm_encoder_helper_funcs tegra_hdmi_encoder_helper_funcs = {
.enable = tegra_output_hdmi_enable, .dpms = tegra_hdmi_encoder_dpms,
.disable = tegra_output_hdmi_disable, .mode_fixup = tegra_hdmi_encoder_mode_fixup,
.setup_clock = tegra_output_hdmi_setup_clock, .prepare = tegra_hdmi_encoder_prepare,
.check_mode = tegra_output_hdmi_check_mode, .commit = tegra_hdmi_encoder_commit,
.mode_set = tegra_hdmi_encoder_mode_set,
.disable = tegra_hdmi_encoder_disable,
}; };
static int tegra_hdmi_show_regs(struct seq_file *s, void *data) static int tegra_hdmi_show_regs(struct seq_file *s, void *data)
...@@ -1345,15 +1331,28 @@ static int tegra_hdmi_init(struct host1x_client *client) ...@@ -1345,15 +1331,28 @@ static int tegra_hdmi_init(struct host1x_client *client)
struct tegra_hdmi *hdmi = host1x_client_to_hdmi(client); struct tegra_hdmi *hdmi = host1x_client_to_hdmi(client);
int err; int err;
hdmi->output.type = TEGRA_OUTPUT_HDMI;
hdmi->output.dev = client->dev; hdmi->output.dev = client->dev;
hdmi->output.ops = &hdmi_ops;
err = tegra_output_init(drm, &hdmi->output); drm_connector_init(drm, &hdmi->output.connector,
if (err < 0) { &tegra_hdmi_connector_funcs,
dev_err(client->dev, "output setup failed: %d\n", err); DRM_MODE_CONNECTOR_HDMIA);
return err; drm_connector_helper_add(&hdmi->output.connector,
} &tegra_hdmi_connector_helper_funcs);
hdmi->output.connector.dpms = DRM_MODE_DPMS_OFF;
drm_encoder_init(drm, &hdmi->output.encoder, &tegra_hdmi_encoder_funcs,
DRM_MODE_ENCODER_TMDS);
drm_encoder_helper_add(&hdmi->output.encoder,
&tegra_hdmi_encoder_helper_funcs);
drm_mode_connector_attach_encoder(&hdmi->output.connector,
&hdmi->output.encoder);
drm_connector_register(&hdmi->output.connector);
hdmi->output.encoder.possible_crtcs = 0x3;
if (gpio_is_valid(hdmi->output.hpd_gpio))
enable_irq(hdmi->output.hpd_irq);
if (IS_ENABLED(CONFIG_DEBUG_FS)) { if (IS_ENABLED(CONFIG_DEBUG_FS)) {
err = tegra_hdmi_debugfs_init(hdmi, drm->primary); err = tegra_hdmi_debugfs_init(hdmi, drm->primary);
...@@ -1368,6 +1367,26 @@ static int tegra_hdmi_init(struct host1x_client *client) ...@@ -1368,6 +1367,26 @@ static int tegra_hdmi_init(struct host1x_client *client)
return err; return err;
} }
err = regulator_enable(hdmi->pll);
if (err < 0) {
dev_err(hdmi->dev, "failed to enable PLL regulator: %d\n", err);
return err;
}
err = regulator_enable(hdmi->vdd);
if (err < 0) {
dev_err(hdmi->dev, "failed to enable VDD regulator: %d\n", err);
return err;
}
err = clk_prepare_enable(hdmi->clk);
if (err < 0) {
dev_err(hdmi->dev, "failed to enable clock: %d\n", err);
return err;
}
reset_control_deassert(hdmi->rst);
return 0; return 0;
} }
...@@ -1376,6 +1395,13 @@ static int tegra_hdmi_exit(struct host1x_client *client) ...@@ -1376,6 +1395,13 @@ static int tegra_hdmi_exit(struct host1x_client *client)
struct tegra_hdmi *hdmi = host1x_client_to_hdmi(client); struct tegra_hdmi *hdmi = host1x_client_to_hdmi(client);
int err; int err;
tegra_output_exit(&hdmi->output);
clk_disable_unprepare(hdmi->clk);
reset_control_assert(hdmi->rst);
regulator_disable(hdmi->vdd);
regulator_disable(hdmi->pll);
regulator_disable(hdmi->hdmi); regulator_disable(hdmi->hdmi);
if (IS_ENABLED(CONFIG_DEBUG_FS)) { if (IS_ENABLED(CONFIG_DEBUG_FS)) {
...@@ -1385,18 +1411,6 @@ static int tegra_hdmi_exit(struct host1x_client *client) ...@@ -1385,18 +1411,6 @@ static int tegra_hdmi_exit(struct host1x_client *client)
err); err);
} }
err = tegra_output_disable(&hdmi->output);
if (err < 0) {
dev_err(client->dev, "output failed to disable: %d\n", err);
return err;
}
err = tegra_output_exit(&hdmi->output);
if (err < 0) {
dev_err(client->dev, "output cleanup failed: %d\n", err);
return err;
}
return 0; return 0;
} }
......
...@@ -274,11 +274,6 @@ int tegra_output_init(struct drm_device *drm, struct tegra_output *output) ...@@ -274,11 +274,6 @@ int tegra_output_init(struct drm_device *drm, struct tegra_output *output)
int connector, encoder; int connector, encoder;
switch (output->type) { switch (output->type) {
case TEGRA_OUTPUT_HDMI:
connector = DRM_MODE_CONNECTOR_HDMIA;
encoder = DRM_MODE_ENCODER_TMDS;
break;
case TEGRA_OUTPUT_DSI: case TEGRA_OUTPUT_DSI:
connector = DRM_MODE_CONNECTOR_DSI; connector = DRM_MODE_CONNECTOR_DSI;
encoder = DRM_MODE_ENCODER_DSI; encoder = DRM_MODE_ENCODER_DSI;
......
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