|
@@ -10,7 +10,9 @@
|
|
#include <linux/debugfs.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/io.h>
|
|
#include <linux/io.h>
|
|
|
|
+#include <linux/of_device.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/platform_device.h>
|
|
|
|
+#include <linux/regulator/consumer.h>
|
|
#include <linux/reset.h>
|
|
#include <linux/reset.h>
|
|
|
|
|
|
#include <soc/tegra/pmc.h>
|
|
#include <soc/tegra/pmc.h>
|
|
@@ -23,11 +25,146 @@
|
|
#include "drm.h"
|
|
#include "drm.h"
|
|
#include "sor.h"
|
|
#include "sor.h"
|
|
|
|
|
|
|
|
+#define SOR_REKEY 0x38
|
|
|
|
+
|
|
|
|
+struct tegra_sor_hdmi_settings {
|
|
|
|
+ unsigned long frequency;
|
|
|
|
+
|
|
|
|
+ u8 vcocap;
|
|
|
|
+ u8 ichpmp;
|
|
|
|
+ u8 loadadj;
|
|
|
|
+ u8 termadj;
|
|
|
|
+ u8 tx_pu;
|
|
|
|
+ u8 bg_vref;
|
|
|
|
+
|
|
|
|
+ u8 drive_current[4];
|
|
|
|
+ u8 preemphasis[4];
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+#if 1
|
|
|
|
+static const struct tegra_sor_hdmi_settings tegra210_sor_hdmi_defaults[] = {
|
|
|
|
+ {
|
|
|
|
+ .frequency = 54000000,
|
|
|
|
+ .vcocap = 0x0,
|
|
|
|
+ .ichpmp = 0x1,
|
|
|
|
+ .loadadj = 0x3,
|
|
|
|
+ .termadj = 0x9,
|
|
|
|
+ .tx_pu = 0x10,
|
|
|
|
+ .bg_vref = 0x8,
|
|
|
|
+ .drive_current = { 0x33, 0x3a, 0x3a, 0x3a },
|
|
|
|
+ .preemphasis = { 0x00, 0x00, 0x00, 0x00 },
|
|
|
|
+ }, {
|
|
|
|
+ .frequency = 75000000,
|
|
|
|
+ .vcocap = 0x3,
|
|
|
|
+ .ichpmp = 0x1,
|
|
|
|
+ .loadadj = 0x3,
|
|
|
|
+ .termadj = 0x9,
|
|
|
|
+ .tx_pu = 0x40,
|
|
|
|
+ .bg_vref = 0x8,
|
|
|
|
+ .drive_current = { 0x33, 0x3a, 0x3a, 0x3a },
|
|
|
|
+ .preemphasis = { 0x00, 0x00, 0x00, 0x00 },
|
|
|
|
+ }, {
|
|
|
|
+ .frequency = 150000000,
|
|
|
|
+ .vcocap = 0x3,
|
|
|
|
+ .ichpmp = 0x1,
|
|
|
|
+ .loadadj = 0x3,
|
|
|
|
+ .termadj = 0x9,
|
|
|
|
+ .tx_pu = 0x66,
|
|
|
|
+ .bg_vref = 0x8,
|
|
|
|
+ .drive_current = { 0x33, 0x3a, 0x3a, 0x3a },
|
|
|
|
+ .preemphasis = { 0x00, 0x00, 0x00, 0x00 },
|
|
|
|
+ }, {
|
|
|
|
+ .frequency = 300000000,
|
|
|
|
+ .vcocap = 0x3,
|
|
|
|
+ .ichpmp = 0x1,
|
|
|
|
+ .loadadj = 0x3,
|
|
|
|
+ .termadj = 0x9,
|
|
|
|
+ .tx_pu = 0x66,
|
|
|
|
+ .bg_vref = 0xa,
|
|
|
|
+ .drive_current = { 0x33, 0x3f, 0x3f, 0x3f },
|
|
|
|
+ .preemphasis = { 0x00, 0x17, 0x17, 0x17 },
|
|
|
|
+ }, {
|
|
|
|
+ .frequency = 600000000,
|
|
|
|
+ .vcocap = 0x3,
|
|
|
|
+ .ichpmp = 0x1,
|
|
|
|
+ .loadadj = 0x3,
|
|
|
|
+ .termadj = 0x9,
|
|
|
|
+ .tx_pu = 0x66,
|
|
|
|
+ .bg_vref = 0x8,
|
|
|
|
+ .drive_current = { 0x33, 0x3f, 0x3f, 0x3f },
|
|
|
|
+ .preemphasis = { 0x00, 0x00, 0x00, 0x00 },
|
|
|
|
+ },
|
|
|
|
+};
|
|
|
|
+#else
|
|
|
|
+static const struct tegra_sor_hdmi_settings tegra210_sor_hdmi_defaults[] = {
|
|
|
|
+ {
|
|
|
|
+ .frequency = 75000000,
|
|
|
|
+ .vcocap = 0x3,
|
|
|
|
+ .ichpmp = 0x1,
|
|
|
|
+ .loadadj = 0x3,
|
|
|
|
+ .termadj = 0x9,
|
|
|
|
+ .tx_pu = 0x40,
|
|
|
|
+ .bg_vref = 0x8,
|
|
|
|
+ .drive_current = { 0x29, 0x29, 0x29, 0x29 },
|
|
|
|
+ .preemphasis = { 0x00, 0x00, 0x00, 0x00 },
|
|
|
|
+ }, {
|
|
|
|
+ .frequency = 150000000,
|
|
|
|
+ .vcocap = 0x3,
|
|
|
|
+ .ichpmp = 0x1,
|
|
|
|
+ .loadadj = 0x3,
|
|
|
|
+ .termadj = 0x9,
|
|
|
|
+ .tx_pu = 0x66,
|
|
|
|
+ .bg_vref = 0x8,
|
|
|
|
+ .drive_current = { 0x30, 0x37, 0x37, 0x37 },
|
|
|
|
+ .preemphasis = { 0x01, 0x02, 0x02, 0x02 },
|
|
|
|
+ }, {
|
|
|
|
+ .frequency = 300000000,
|
|
|
|
+ .vcocap = 0x3,
|
|
|
|
+ .ichpmp = 0x6,
|
|
|
|
+ .loadadj = 0x3,
|
|
|
|
+ .termadj = 0x9,
|
|
|
|
+ .tx_pu = 0x66,
|
|
|
|
+ .bg_vref = 0xf,
|
|
|
|
+ .drive_current = { 0x30, 0x37, 0x37, 0x37 },
|
|
|
|
+ .preemphasis = { 0x10, 0x3e, 0x3e, 0x3e },
|
|
|
|
+ }, {
|
|
|
|
+ .frequency = 600000000,
|
|
|
|
+ .vcocap = 0x3,
|
|
|
|
+ .ichpmp = 0xa,
|
|
|
|
+ .loadadj = 0x3,
|
|
|
|
+ .termadj = 0xb,
|
|
|
|
+ .tx_pu = 0x66,
|
|
|
|
+ .bg_vref = 0xe,
|
|
|
|
+ .drive_current = { 0x35, 0x3e, 0x3e, 0x3e },
|
|
|
|
+ .preemphasis = { 0x02, 0x3f, 0x3f, 0x3f },
|
|
|
|
+ },
|
|
|
|
+};
|
|
|
|
+#endif
|
|
|
|
+
|
|
|
|
+struct tegra_sor_soc {
|
|
|
|
+ bool supports_edp;
|
|
|
|
+ bool supports_lvds;
|
|
|
|
+ bool supports_hdmi;
|
|
|
|
+ bool supports_dp;
|
|
|
|
+
|
|
|
|
+ const struct tegra_sor_hdmi_settings *settings;
|
|
|
|
+ unsigned int num_settings;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+struct tegra_sor;
|
|
|
|
+
|
|
|
|
+struct tegra_sor_ops {
|
|
|
|
+ const char *name;
|
|
|
|
+ int (*probe)(struct tegra_sor *sor);
|
|
|
|
+ int (*remove)(struct tegra_sor *sor);
|
|
|
|
+};
|
|
|
|
+
|
|
struct tegra_sor {
|
|
struct tegra_sor {
|
|
struct host1x_client client;
|
|
struct host1x_client client;
|
|
struct tegra_output output;
|
|
struct tegra_output output;
|
|
struct device *dev;
|
|
struct device *dev;
|
|
|
|
|
|
|
|
+ const struct tegra_sor_soc *soc;
|
|
void __iomem *regs;
|
|
void __iomem *regs;
|
|
|
|
|
|
struct reset_control *rst;
|
|
struct reset_control *rst;
|
|
@@ -41,6 +178,16 @@ struct tegra_sor {
|
|
struct drm_info_list *debugfs_files;
|
|
struct drm_info_list *debugfs_files;
|
|
struct drm_minor *minor;
|
|
struct drm_minor *minor;
|
|
struct dentry *debugfs;
|
|
struct dentry *debugfs;
|
|
|
|
+
|
|
|
|
+ const struct tegra_sor_ops *ops;
|
|
|
|
+
|
|
|
|
+ /* for HDMI 2.0 */
|
|
|
|
+ struct tegra_sor_hdmi_settings *settings;
|
|
|
|
+ unsigned int num_settings;
|
|
|
|
+
|
|
|
|
+ struct regulator *avdd_io_supply;
|
|
|
|
+ struct regulator *vdd_pll_supply;
|
|
|
|
+ struct regulator *hdmi_supply;
|
|
};
|
|
};
|
|
|
|
|
|
struct tegra_sor_config {
|
|
struct tegra_sor_config {
|
|
@@ -184,6 +331,47 @@ static int tegra_sor_dp_train_fast(struct tegra_sor *sor,
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+static void tegra_sor_dp_term_calibrate(struct tegra_sor *sor)
|
|
|
|
+{
|
|
|
|
+ u32 mask = 0x08, adj = 0, value;
|
|
|
|
+
|
|
|
|
+ /* enable pad calibration logic */
|
|
|
|
+ value = tegra_sor_readl(sor, SOR_DP_PADCTL0);
|
|
|
|
+ value &= ~SOR_DP_PADCTL_PAD_CAL_PD;
|
|
|
|
+ tegra_sor_writel(sor, value, SOR_DP_PADCTL0);
|
|
|
|
+
|
|
|
|
+ value = tegra_sor_readl(sor, SOR_PLL1);
|
|
|
|
+ value |= SOR_PLL1_TMDS_TERM;
|
|
|
|
+ tegra_sor_writel(sor, value, SOR_PLL1);
|
|
|
|
+
|
|
|
|
+ while (mask) {
|
|
|
|
+ adj |= mask;
|
|
|
|
+
|
|
|
|
+ value = tegra_sor_readl(sor, SOR_PLL1);
|
|
|
|
+ value &= ~SOR_PLL1_TMDS_TERMADJ_MASK;
|
|
|
|
+ value |= SOR_PLL1_TMDS_TERMADJ(adj);
|
|
|
|
+ tegra_sor_writel(sor, value, SOR_PLL1);
|
|
|
|
+
|
|
|
|
+ usleep_range(100, 200);
|
|
|
|
+
|
|
|
|
+ value = tegra_sor_readl(sor, SOR_PLL1);
|
|
|
|
+ if (value & SOR_PLL1_TERM_COMPOUT)
|
|
|
|
+ adj &= ~mask;
|
|
|
|
+
|
|
|
|
+ mask >>= 1;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ value = tegra_sor_readl(sor, SOR_PLL1);
|
|
|
|
+ value &= ~SOR_PLL1_TMDS_TERMADJ_MASK;
|
|
|
|
+ value |= SOR_PLL1_TMDS_TERMADJ(adj);
|
|
|
|
+ tegra_sor_writel(sor, value, SOR_PLL1);
|
|
|
|
+
|
|
|
|
+ /* disable pad calibration logic */
|
|
|
|
+ value = tegra_sor_readl(sor, SOR_DP_PADCTL0);
|
|
|
|
+ value |= SOR_DP_PADCTL_PAD_CAL_PD;
|
|
|
|
+ tegra_sor_writel(sor, value, SOR_DP_PADCTL0);
|
|
|
|
+}
|
|
|
|
+
|
|
static void tegra_sor_super_update(struct tegra_sor *sor)
|
|
static void tegra_sor_super_update(struct tegra_sor *sor)
|
|
{
|
|
{
|
|
tegra_sor_writel(sor, 0, SOR_SUPER_STATE0);
|
|
tegra_sor_writel(sor, 0, SOR_SUPER_STATE0);
|
|
@@ -800,10 +988,11 @@ static const struct drm_info_list debugfs_files[] = {
|
|
static int tegra_sor_debugfs_init(struct tegra_sor *sor,
|
|
static int tegra_sor_debugfs_init(struct tegra_sor *sor,
|
|
struct drm_minor *minor)
|
|
struct drm_minor *minor)
|
|
{
|
|
{
|
|
|
|
+ const char *name = sor->soc->supports_dp ? "sor1" : "sor";
|
|
unsigned int i;
|
|
unsigned int i;
|
|
int err;
|
|
int err;
|
|
|
|
|
|
- sor->debugfs = debugfs_create_dir("sor", minor->debugfs_root);
|
|
|
|
|
|
+ sor->debugfs = debugfs_create_dir(name, minor->debugfs_root);
|
|
if (!sor->debugfs)
|
|
if (!sor->debugfs)
|
|
return -ENOMEM;
|
|
return -ENOMEM;
|
|
|
|
|
|
@@ -858,7 +1047,7 @@ tegra_sor_connector_detect(struct drm_connector *connector, bool force)
|
|
if (sor->dpaux)
|
|
if (sor->dpaux)
|
|
return tegra_dpaux_detect(sor->dpaux);
|
|
return tegra_dpaux_detect(sor->dpaux);
|
|
|
|
|
|
- return connector_status_unknown;
|
|
|
|
|
|
+ return tegra_output_connector_detect(connector, force);
|
|
}
|
|
}
|
|
|
|
|
|
static const struct drm_connector_funcs tegra_sor_connector_funcs = {
|
|
static const struct drm_connector_funcs tegra_sor_connector_funcs = {
|
|
@@ -956,6 +1145,48 @@ static void tegra_sor_edp_disable(struct drm_encoder *encoder)
|
|
clk_disable_unprepare(sor->clk);
|
|
clk_disable_unprepare(sor->clk);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+#if 0
|
|
|
|
+static int calc_h_ref_to_sync(const struct drm_display_mode *mode,
|
|
|
|
+ unsigned int *value)
|
|
|
|
+{
|
|
|
|
+ unsigned int hfp, hsw, hbp, a = 0, b;
|
|
|
|
+
|
|
|
|
+ hfp = mode->hsync_start - mode->hdisplay;
|
|
|
|
+ hsw = mode->hsync_end - mode->hsync_start;
|
|
|
|
+ hbp = mode->htotal - mode->hsync_end;
|
|
|
|
+
|
|
|
|
+ pr_info("hfp: %u, hsw: %u, hbp: %u\n", hfp, hsw, hbp);
|
|
|
|
+
|
|
|
|
+ b = hfp - 1;
|
|
|
|
+
|
|
|
|
+ pr_info("a: %u, b: %u\n", a, b);
|
|
|
|
+ pr_info("a + hsw + hbp = %u\n", a + hsw + hbp);
|
|
|
|
+
|
|
|
|
+ if (a + hsw + hbp <= 11) {
|
|
|
|
+ a = 1 + 11 - hsw - hbp;
|
|
|
|
+ pr_info("a: %u\n", a);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (a > b)
|
|
|
|
+ return -EINVAL;
|
|
|
|
+
|
|
|
|
+ if (hsw < 1)
|
|
|
|
+ return -EINVAL;
|
|
|
|
+
|
|
|
|
+ if (mode->hdisplay < 16)
|
|
|
|
+ return -EINVAL;
|
|
|
|
+
|
|
|
|
+ if (value) {
|
|
|
|
+ if (b > a && a % 2)
|
|
|
|
+ *value = a + 1;
|
|
|
|
+ else
|
|
|
|
+ *value = a;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+#endif
|
|
|
|
+
|
|
static void tegra_sor_edp_enable(struct drm_encoder *encoder)
|
|
static void tegra_sor_edp_enable(struct drm_encoder *encoder)
|
|
{
|
|
{
|
|
struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode;
|
|
struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode;
|
|
@@ -1377,34 +1608,578 @@ tegra_sor_encoder_atomic_check(struct drm_encoder *encoder,
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
-static const struct drm_encoder_helper_funcs tegra_sor_edp_helper_funcs = {
|
|
|
|
|
|
+static const struct drm_encoder_helper_funcs tegra_sor_edp_helpers = {
|
|
.disable = tegra_sor_edp_disable,
|
|
.disable = tegra_sor_edp_disable,
|
|
.enable = tegra_sor_edp_enable,
|
|
.enable = tegra_sor_edp_enable,
|
|
.atomic_check = tegra_sor_encoder_atomic_check,
|
|
.atomic_check = tegra_sor_encoder_atomic_check,
|
|
};
|
|
};
|
|
|
|
|
|
|
|
+static inline u32 tegra_sor_hdmi_subpack(const u8 *ptr, size_t size)
|
|
|
|
+{
|
|
|
|
+ u32 value = 0;
|
|
|
|
+ size_t i;
|
|
|
|
+
|
|
|
|
+ for (i = size; i > 0; i--)
|
|
|
|
+ value = (value << 8) | ptr[i - 1];
|
|
|
|
+
|
|
|
|
+ return value;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void tegra_sor_hdmi_write_infopack(struct tegra_sor *sor,
|
|
|
|
+ const void *data, size_t size)
|
|
|
|
+{
|
|
|
|
+ const u8 *ptr = data;
|
|
|
|
+ unsigned long offset;
|
|
|
|
+ size_t i, j;
|
|
|
|
+ u32 value;
|
|
|
|
+
|
|
|
|
+ switch (ptr[0]) {
|
|
|
|
+ case HDMI_INFOFRAME_TYPE_AVI:
|
|
|
|
+ offset = SOR_HDMI_AVI_INFOFRAME_HEADER;
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case HDMI_INFOFRAME_TYPE_AUDIO:
|
|
|
|
+ offset = SOR_HDMI_AUDIO_INFOFRAME_HEADER;
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case HDMI_INFOFRAME_TYPE_VENDOR:
|
|
|
|
+ offset = SOR_HDMI_VSI_INFOFRAME_HEADER;
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ default:
|
|
|
|
+ dev_err(sor->dev, "unsupported infoframe type: %02x\n",
|
|
|
|
+ ptr[0]);
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ value = INFOFRAME_HEADER_TYPE(ptr[0]) |
|
|
|
|
+ INFOFRAME_HEADER_VERSION(ptr[1]) |
|
|
|
|
+ INFOFRAME_HEADER_LEN(ptr[2]);
|
|
|
|
+ tegra_sor_writel(sor, value, offset);
|
|
|
|
+ offset++;
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * Each subpack contains 7 bytes, divided into:
|
|
|
|
+ * - subpack_low: bytes 0 - 3
|
|
|
|
+ * - subpack_high: bytes 4 - 6 (with byte 7 padded to 0x00)
|
|
|
|
+ */
|
|
|
|
+ for (i = 3, j = 0; i < size; i += 7, j += 8) {
|
|
|
|
+ size_t rem = size - i, num = min_t(size_t, rem, 4);
|
|
|
|
+
|
|
|
|
+ value = tegra_sor_hdmi_subpack(&ptr[i], num);
|
|
|
|
+ tegra_sor_writel(sor, value, offset++);
|
|
|
|
+
|
|
|
|
+ num = min_t(size_t, rem - num, 3);
|
|
|
|
+
|
|
|
|
+ value = tegra_sor_hdmi_subpack(&ptr[i + 4], num);
|
|
|
|
+ tegra_sor_writel(sor, value, offset++);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int
|
|
|
|
+tegra_sor_hdmi_setup_avi_infoframe(struct tegra_sor *sor,
|
|
|
|
+ const struct drm_display_mode *mode)
|
|
|
|
+{
|
|
|
|
+ u8 buffer[HDMI_INFOFRAME_SIZE(AVI)];
|
|
|
|
+ struct hdmi_avi_infoframe frame;
|
|
|
|
+ u32 value;
|
|
|
|
+ int err;
|
|
|
|
+
|
|
|
|
+ /* disable AVI infoframe */
|
|
|
|
+ value = tegra_sor_readl(sor, SOR_HDMI_AVI_INFOFRAME_CTRL);
|
|
|
|
+ value &= ~INFOFRAME_CTRL_SINGLE;
|
|
|
|
+ value &= ~INFOFRAME_CTRL_OTHER;
|
|
|
|
+ value &= ~INFOFRAME_CTRL_ENABLE;
|
|
|
|
+ tegra_sor_writel(sor, value, SOR_HDMI_AVI_INFOFRAME_CTRL);
|
|
|
|
+
|
|
|
|
+ err = drm_hdmi_avi_infoframe_from_display_mode(&frame, mode);
|
|
|
|
+ if (err < 0) {
|
|
|
|
+ dev_err(sor->dev, "failed to setup AVI infoframe: %d\n", err);
|
|
|
|
+ return err;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ err = hdmi_avi_infoframe_pack(&frame, buffer, sizeof(buffer));
|
|
|
|
+ if (err < 0) {
|
|
|
|
+ dev_err(sor->dev, "failed to pack AVI infoframe: %d\n", err);
|
|
|
|
+ return err;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ tegra_sor_hdmi_write_infopack(sor, buffer, err);
|
|
|
|
+
|
|
|
|
+ /* enable AVI infoframe */
|
|
|
|
+ value = tegra_sor_readl(sor, SOR_HDMI_AVI_INFOFRAME_CTRL);
|
|
|
|
+ value |= INFOFRAME_CTRL_CHECKSUM_ENABLE;
|
|
|
|
+ value |= INFOFRAME_CTRL_ENABLE;
|
|
|
|
+ tegra_sor_writel(sor, value, SOR_HDMI_AVI_INFOFRAME_CTRL);
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void tegra_sor_hdmi_disable_audio_infoframe(struct tegra_sor *sor)
|
|
|
|
+{
|
|
|
|
+ u32 value;
|
|
|
|
+
|
|
|
|
+ value = tegra_sor_readl(sor, SOR_HDMI_AUDIO_INFOFRAME_CTRL);
|
|
|
|
+ value &= ~INFOFRAME_CTRL_ENABLE;
|
|
|
|
+ tegra_sor_writel(sor, value, SOR_HDMI_AUDIO_INFOFRAME_CTRL);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static struct tegra_sor_hdmi_settings *
|
|
|
|
+tegra_sor_hdmi_find_settings(struct tegra_sor *sor, unsigned long frequency)
|
|
|
|
+{
|
|
|
|
+ unsigned int i;
|
|
|
|
+
|
|
|
|
+ for (i = 0; i < sor->num_settings; i++)
|
|
|
|
+ if (frequency <= sor->settings[i].frequency)
|
|
|
|
+ return &sor->settings[i];
|
|
|
|
+
|
|
|
|
+ return NULL;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void tegra_sor_hdmi_disable(struct drm_encoder *encoder)
|
|
|
|
+{
|
|
|
|
+ struct tegra_output *output = encoder_to_output(encoder);
|
|
|
|
+ struct tegra_dc *dc = to_tegra_dc(encoder->crtc);
|
|
|
|
+ struct tegra_sor *sor = to_sor(output);
|
|
|
|
+ u32 value;
|
|
|
|
+ int err;
|
|
|
|
+
|
|
|
|
+ err = tegra_sor_detach(sor);
|
|
|
|
+ if (err < 0)
|
|
|
|
+ dev_err(sor->dev, "failed to detach SOR: %d\n", err);
|
|
|
|
+
|
|
|
|
+ tegra_sor_writel(sor, 0, SOR_STATE1);
|
|
|
|
+ tegra_sor_update(sor);
|
|
|
|
+
|
|
|
|
+ /* disable display to SOR clock */
|
|
|
|
+ value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS);
|
|
|
|
+ value &= ~SOR1_TIMING_CYA;
|
|
|
|
+ value &= ~SOR1_ENABLE;
|
|
|
|
+ tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS);
|
|
|
|
+
|
|
|
|
+ tegra_dc_commit(dc);
|
|
|
|
+
|
|
|
|
+ err = tegra_sor_power_down(sor);
|
|
|
|
+ if (err < 0)
|
|
|
|
+ dev_err(sor->dev, "failed to power down SOR: %d\n", err);
|
|
|
|
+
|
|
|
|
+ err = tegra_io_rail_power_off(TEGRA_IO_RAIL_HDMI);
|
|
|
|
+ if (err < 0)
|
|
|
|
+ dev_err(sor->dev, "failed to power off HDMI rail: %d\n", err);
|
|
|
|
+
|
|
|
|
+ reset_control_assert(sor->rst);
|
|
|
|
+ usleep_range(1000, 2000);
|
|
|
|
+ clk_disable_unprepare(sor->clk);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void tegra_sor_hdmi_enable(struct drm_encoder *encoder)
|
|
|
|
+{
|
|
|
|
+ struct tegra_output *output = encoder_to_output(encoder);
|
|
|
|
+ unsigned int h_ref_to_sync = 1, pulse_start, max_ac;
|
|
|
|
+ struct tegra_dc *dc = to_tegra_dc(encoder->crtc);
|
|
|
|
+ unsigned int vbe, vse, hbe, hse, vbs, hbs, div;
|
|
|
|
+ struct tegra_sor_hdmi_settings *settings;
|
|
|
|
+ struct tegra_sor *sor = to_sor(output);
|
|
|
|
+ struct drm_display_mode *mode;
|
|
|
|
+ struct drm_display_info *info;
|
|
|
|
+ u32 value;
|
|
|
|
+ int err;
|
|
|
|
+
|
|
|
|
+ mode = &encoder->crtc->state->adjusted_mode;
|
|
|
|
+ info = &output->connector.display_info;
|
|
|
|
+
|
|
|
|
+ err = clk_prepare_enable(sor->clk);
|
|
|
|
+ if (err < 0)
|
|
|
|
+ dev_err(sor->dev, "failed to enable clock: %d\n", err);
|
|
|
|
+
|
|
|
|
+ usleep_range(1000, 2000);
|
|
|
|
+
|
|
|
|
+ reset_control_deassert(sor->rst);
|
|
|
|
+
|
|
|
|
+ err = clk_set_parent(sor->clk, sor->clk_safe);
|
|
|
|
+ if (err < 0)
|
|
|
|
+ dev_err(sor->dev, "failed to set safe parent clock: %d\n", err);
|
|
|
|
+
|
|
|
|
+ div = clk_get_rate(sor->clk) / 1000000 * 4;
|
|
|
|
+
|
|
|
|
+ err = tegra_io_rail_power_on(TEGRA_IO_RAIL_HDMI);
|
|
|
|
+ if (err < 0)
|
|
|
|
+ dev_err(sor->dev, "failed to power on HDMI rail: %d\n", err);
|
|
|
|
+
|
|
|
|
+ usleep_range(20, 100);
|
|
|
|
+
|
|
|
|
+ value = tegra_sor_readl(sor, SOR_PLL2);
|
|
|
|
+ value &= ~SOR_PLL2_BANDGAP_POWERDOWN;
|
|
|
|
+ tegra_sor_writel(sor, value, SOR_PLL2);
|
|
|
|
+
|
|
|
|
+ usleep_range(20, 100);
|
|
|
|
+
|
|
|
|
+ value = tegra_sor_readl(sor, SOR_PLL3);
|
|
|
|
+ value &= ~SOR_PLL3_PLL_VDD_MODE_3V3;
|
|
|
|
+ tegra_sor_writel(sor, value, SOR_PLL3);
|
|
|
|
+
|
|
|
|
+ value = tegra_sor_readl(sor, SOR_PLL0);
|
|
|
|
+ value &= ~SOR_PLL0_VCOPD;
|
|
|
|
+ value &= ~SOR_PLL0_PWR;
|
|
|
|
+ tegra_sor_writel(sor, value, SOR_PLL0);
|
|
|
|
+
|
|
|
|
+ value = tegra_sor_readl(sor, SOR_PLL2);
|
|
|
|
+ value &= ~SOR_PLL2_SEQ_PLLCAPPD_ENFORCE;
|
|
|
|
+ tegra_sor_writel(sor, value, SOR_PLL2);
|
|
|
|
+
|
|
|
|
+ usleep_range(200, 400);
|
|
|
|
+
|
|
|
|
+ value = tegra_sor_readl(sor, SOR_PLL2);
|
|
|
|
+ value &= ~SOR_PLL2_POWERDOWN_OVERRIDE;
|
|
|
|
+ value &= ~SOR_PLL2_PORT_POWERDOWN;
|
|
|
|
+ tegra_sor_writel(sor, value, SOR_PLL2);
|
|
|
|
+
|
|
|
|
+ usleep_range(20, 100);
|
|
|
|
+
|
|
|
|
+ value = tegra_sor_readl(sor, SOR_DP_PADCTL0);
|
|
|
|
+ value |= SOR_DP_PADCTL_PD_TXD_3 | SOR_DP_PADCTL_PD_TXD_0 |
|
|
|
|
+ SOR_DP_PADCTL_PD_TXD_1 | SOR_DP_PADCTL_PD_TXD_2;
|
|
|
|
+ tegra_sor_writel(sor, value, SOR_DP_PADCTL0);
|
|
|
|
+
|
|
|
|
+ while (true) {
|
|
|
|
+ value = tegra_sor_readl(sor, SOR_LANE_SEQ_CTL);
|
|
|
|
+ if ((value & SOR_LANE_SEQ_CTL_STATE_BUSY) == 0)
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ usleep_range(250, 1000);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ value = SOR_LANE_SEQ_CTL_TRIGGER | SOR_LANE_SEQ_CTL_SEQUENCE_DOWN |
|
|
|
|
+ SOR_LANE_SEQ_CTL_POWER_STATE_UP | SOR_LANE_SEQ_CTL_DELAY(5);
|
|
|
|
+ tegra_sor_writel(sor, value, SOR_LANE_SEQ_CTL);
|
|
|
|
+
|
|
|
|
+ while (true) {
|
|
|
|
+ value = tegra_sor_readl(sor, SOR_LANE_SEQ_CTL);
|
|
|
|
+ if ((value & SOR_LANE_SEQ_CTL_TRIGGER) == 0)
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ usleep_range(250, 1000);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ value = tegra_sor_readl(sor, SOR_CLK_CNTRL);
|
|
|
|
+ value &= ~SOR_CLK_CNTRL_DP_LINK_SPEED_MASK;
|
|
|
|
+ value &= ~SOR_CLK_CNTRL_DP_CLK_SEL_MASK;
|
|
|
|
+
|
|
|
|
+ if (mode->clock < 340000)
|
|
|
|
+ value |= SOR_CLK_CNTRL_DP_LINK_SPEED_G2_70;
|
|
|
|
+ else
|
|
|
|
+ value |= SOR_CLK_CNTRL_DP_LINK_SPEED_G5_40;
|
|
|
|
+
|
|
|
|
+ value |= SOR_CLK_CNTRL_DP_CLK_SEL_SINGLE_PCLK;
|
|
|
|
+ tegra_sor_writel(sor, value, SOR_CLK_CNTRL);
|
|
|
|
+
|
|
|
|
+ value = tegra_sor_readl(sor, SOR_DP_SPARE0);
|
|
|
|
+ value |= SOR_DP_SPARE_DISP_VIDEO_PREAMBLE;
|
|
|
|
+ value &= ~SOR_DP_SPARE_PANEL_INTERNAL;
|
|
|
|
+ value |= SOR_DP_SPARE_SEQ_ENABLE;
|
|
|
|
+ tegra_sor_writel(sor, value, SOR_DP_SPARE0);
|
|
|
|
+
|
|
|
|
+ value = SOR_SEQ_CTL_PU_PC(0) | SOR_SEQ_CTL_PU_PC_ALT(0) |
|
|
|
|
+ SOR_SEQ_CTL_PD_PC(8) | SOR_SEQ_CTL_PD_PC_ALT(8);
|
|
|
|
+ tegra_sor_writel(sor, value, SOR_SEQ_CTL);
|
|
|
|
+
|
|
|
|
+ value = SOR_SEQ_INST_DRIVE_PWM_OUT_LO | SOR_SEQ_INST_HALT |
|
|
|
|
+ SOR_SEQ_INST_WAIT_VSYNC | SOR_SEQ_INST_WAIT(1);
|
|
|
|
+ tegra_sor_writel(sor, value, SOR_SEQ_INST(0));
|
|
|
|
+ tegra_sor_writel(sor, value, SOR_SEQ_INST(8));
|
|
|
|
+
|
|
|
|
+ /* program the reference clock */
|
|
|
|
+ value = SOR_REFCLK_DIV_INT(div) | SOR_REFCLK_DIV_FRAC(div);
|
|
|
|
+ tegra_sor_writel(sor, value, SOR_REFCLK);
|
|
|
|
+
|
|
|
|
+ /* XXX don't hardcode */
|
|
|
|
+ value = SOR_XBAR_CTRL_LINK1_XSEL(4, 4) |
|
|
|
|
+ SOR_XBAR_CTRL_LINK1_XSEL(3, 3) |
|
|
|
|
+ SOR_XBAR_CTRL_LINK1_XSEL(2, 2) |
|
|
|
|
+ SOR_XBAR_CTRL_LINK1_XSEL(1, 1) |
|
|
|
|
+ SOR_XBAR_CTRL_LINK1_XSEL(0, 0) |
|
|
|
|
+ SOR_XBAR_CTRL_LINK0_XSEL(4, 4) |
|
|
|
|
+ SOR_XBAR_CTRL_LINK0_XSEL(3, 3) |
|
|
|
|
+ SOR_XBAR_CTRL_LINK0_XSEL(2, 0) |
|
|
|
|
+ SOR_XBAR_CTRL_LINK0_XSEL(1, 1) |
|
|
|
|
+ SOR_XBAR_CTRL_LINK0_XSEL(0, 2);
|
|
|
|
+ tegra_sor_writel(sor, value, SOR_XBAR_CTRL);
|
|
|
|
+
|
|
|
|
+ tegra_sor_writel(sor, 0x00000000, SOR_XBAR_POL);
|
|
|
|
+
|
|
|
|
+ err = clk_set_parent(sor->clk, sor->clk_parent);
|
|
|
|
+ if (err < 0)
|
|
|
|
+ dev_err(sor->dev, "failed to set parent clock: %d\n", err);
|
|
|
|
+
|
|
|
|
+ value = SOR_INPUT_CONTROL_HDMI_SRC_SELECT(dc->pipe);
|
|
|
|
+
|
|
|
|
+ /* XXX is this the proper check? */
|
|
|
|
+ if (mode->clock < 75000)
|
|
|
|
+ value |= SOR_INPUT_CONTROL_ARM_VIDEO_RANGE_LIMITED;
|
|
|
|
+
|
|
|
|
+ tegra_sor_writel(sor, value, SOR_INPUT_CONTROL);
|
|
|
|
+
|
|
|
|
+ max_ac = ((mode->htotal - mode->hdisplay) - SOR_REKEY - 18) / 32;
|
|
|
|
+
|
|
|
|
+ value = SOR_HDMI_CTRL_ENABLE | SOR_HDMI_CTRL_MAX_AC_PACKET(max_ac) |
|
|
|
|
+ SOR_HDMI_CTRL_AUDIO_LAYOUT | SOR_HDMI_CTRL_REKEY(SOR_REKEY);
|
|
|
|
+ tegra_sor_writel(sor, value, SOR_HDMI_CTRL);
|
|
|
|
+
|
|
|
|
+ /* H_PULSE2 setup */
|
|
|
|
+ pulse_start = h_ref_to_sync + (mode->hsync_end - mode->hsync_start) +
|
|
|
|
+ (mode->htotal - mode->hsync_end) - 10;
|
|
|
|
+
|
|
|
|
+ value = PULSE_LAST_END_A | PULSE_QUAL_VACTIVE |
|
|
|
|
+ PULSE_POLARITY_HIGH | PULSE_MODE_NORMAL;
|
|
|
|
+ tegra_dc_writel(dc, value, DC_DISP_H_PULSE2_CONTROL);
|
|
|
|
+
|
|
|
|
+ value = PULSE_END(pulse_start + 8) | PULSE_START(pulse_start);
|
|
|
|
+ tegra_dc_writel(dc, value, DC_DISP_H_PULSE2_POSITION_A);
|
|
|
|
+
|
|
|
|
+ value = tegra_dc_readl(dc, DC_DISP_DISP_SIGNAL_OPTIONS0);
|
|
|
|
+ value |= H_PULSE2_ENABLE;
|
|
|
|
+ tegra_dc_writel(dc, value, DC_DISP_DISP_SIGNAL_OPTIONS0);
|
|
|
|
+
|
|
|
|
+ /* infoframe setup */
|
|
|
|
+ err = tegra_sor_hdmi_setup_avi_infoframe(sor, mode);
|
|
|
|
+ if (err < 0)
|
|
|
|
+ dev_err(sor->dev, "failed to setup AVI infoframe: %d\n", err);
|
|
|
|
+
|
|
|
|
+ /* XXX HDMI audio support not implemented yet */
|
|
|
|
+ tegra_sor_hdmi_disable_audio_infoframe(sor);
|
|
|
|
+
|
|
|
|
+ /* use single TMDS protocol */
|
|
|
|
+ value = tegra_sor_readl(sor, SOR_STATE1);
|
|
|
|
+ value &= ~SOR_STATE_ASY_PROTOCOL_MASK;
|
|
|
|
+ value |= SOR_STATE_ASY_PROTOCOL_SINGLE_TMDS_A;
|
|
|
|
+ tegra_sor_writel(sor, value, SOR_STATE1);
|
|
|
|
+
|
|
|
|
+ /* power up pad calibration */
|
|
|
|
+ value = tegra_sor_readl(sor, SOR_DP_PADCTL0);
|
|
|
|
+ value &= ~SOR_DP_PADCTL_PAD_CAL_PD;
|
|
|
|
+ tegra_sor_writel(sor, value, SOR_DP_PADCTL0);
|
|
|
|
+
|
|
|
|
+ /* production settings */
|
|
|
|
+ settings = tegra_sor_hdmi_find_settings(sor, mode->clock * 1000);
|
|
|
|
+ if (IS_ERR(settings)) {
|
|
|
|
+ dev_err(sor->dev, "no settings for pixel clock %d Hz: %ld\n",
|
|
|
|
+ mode->clock * 1000, PTR_ERR(settings));
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ value = tegra_sor_readl(sor, SOR_PLL0);
|
|
|
|
+ value &= ~SOR_PLL0_ICHPMP_MASK;
|
|
|
|
+ value &= ~SOR_PLL0_VCOCAP_MASK;
|
|
|
|
+ value |= SOR_PLL0_ICHPMP(settings->ichpmp);
|
|
|
|
+ value |= SOR_PLL0_VCOCAP(settings->vcocap);
|
|
|
|
+ tegra_sor_writel(sor, value, SOR_PLL0);
|
|
|
|
+
|
|
|
|
+ tegra_sor_dp_term_calibrate(sor);
|
|
|
|
+
|
|
|
|
+ value = tegra_sor_readl(sor, SOR_PLL1);
|
|
|
|
+ value &= ~SOR_PLL1_LOADADJ_MASK;
|
|
|
|
+ value |= SOR_PLL1_LOADADJ(settings->loadadj);
|
|
|
|
+ tegra_sor_writel(sor, value, SOR_PLL1);
|
|
|
|
+
|
|
|
|
+ value = tegra_sor_readl(sor, SOR_PLL3);
|
|
|
|
+ value &= ~SOR_PLL3_BG_VREF_LEVEL_MASK;
|
|
|
|
+ value |= SOR_PLL3_BG_VREF_LEVEL(settings->bg_vref);
|
|
|
|
+ tegra_sor_writel(sor, value, SOR_PLL3);
|
|
|
|
+
|
|
|
|
+ value = settings->drive_current[0] << 24 |
|
|
|
|
+ settings->drive_current[1] << 16 |
|
|
|
|
+ settings->drive_current[2] << 8 |
|
|
|
|
+ settings->drive_current[3] << 0;
|
|
|
|
+ tegra_sor_writel(sor, value, SOR_LANE_DRIVE_CURRENT0);
|
|
|
|
+
|
|
|
|
+ value = settings->preemphasis[0] << 24 |
|
|
|
|
+ settings->preemphasis[1] << 16 |
|
|
|
|
+ settings->preemphasis[2] << 8 |
|
|
|
|
+ settings->preemphasis[3] << 0;
|
|
|
|
+ tegra_sor_writel(sor, value, SOR_LANE_PREEMPHASIS0);
|
|
|
|
+
|
|
|
|
+ value = tegra_sor_readl(sor, SOR_DP_PADCTL0);
|
|
|
|
+ value &= ~SOR_DP_PADCTL_TX_PU_MASK;
|
|
|
|
+ value |= SOR_DP_PADCTL_TX_PU_ENABLE;
|
|
|
|
+ value |= SOR_DP_PADCTL_TX_PU(settings->tx_pu);
|
|
|
|
+ tegra_sor_writel(sor, value, SOR_DP_PADCTL0);
|
|
|
|
+
|
|
|
|
+ /* power down pad calibration */
|
|
|
|
+ value = tegra_sor_readl(sor, SOR_DP_PADCTL0);
|
|
|
|
+ value |= SOR_DP_PADCTL_PAD_CAL_PD;
|
|
|
|
+ tegra_sor_writel(sor, value, SOR_DP_PADCTL0);
|
|
|
|
+
|
|
|
|
+ /* miscellaneous display controller settings */
|
|
|
|
+ value = VSYNC_H_POSITION(1);
|
|
|
|
+ tegra_dc_writel(dc, value, DC_DISP_DISP_TIMING_OPTIONS);
|
|
|
|
+
|
|
|
|
+ value = tegra_dc_readl(dc, DC_DISP_DISP_COLOR_CONTROL);
|
|
|
|
+ value &= ~DITHER_CONTROL_MASK;
|
|
|
|
+ value &= ~BASE_COLOR_SIZE_MASK;
|
|
|
|
+
|
|
|
|
+ switch (info->bpc) {
|
|
|
|
+ case 6:
|
|
|
|
+ value |= BASE_COLOR_SIZE_666;
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case 8:
|
|
|
|
+ value |= BASE_COLOR_SIZE_888;
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ default:
|
|
|
|
+ WARN(1, "%u bits-per-color not supported\n", info->bpc);
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ tegra_dc_writel(dc, value, DC_DISP_DISP_COLOR_CONTROL);
|
|
|
|
+
|
|
|
|
+ err = tegra_sor_power_up(sor, 250);
|
|
|
|
+ if (err < 0)
|
|
|
|
+ dev_err(sor->dev, "failed to power up SOR: %d\n", err);
|
|
|
|
+
|
|
|
|
+ /* configure mode */
|
|
|
|
+ value = tegra_sor_readl(sor, SOR_STATE1);
|
|
|
|
+ value &= ~SOR_STATE_ASY_PIXELDEPTH_MASK;
|
|
|
|
+ value &= ~SOR_STATE_ASY_CRC_MODE_MASK;
|
|
|
|
+ value &= ~SOR_STATE_ASY_OWNER_MASK;
|
|
|
|
+
|
|
|
|
+ value |= SOR_STATE_ASY_CRC_MODE_COMPLETE |
|
|
|
|
+ SOR_STATE_ASY_OWNER(dc->pipe + 1);
|
|
|
|
+
|
|
|
|
+ if (mode->flags & DRM_MODE_FLAG_PHSYNC)
|
|
|
|
+ value &= ~SOR_STATE_ASY_HSYNCPOL;
|
|
|
|
+
|
|
|
|
+ if (mode->flags & DRM_MODE_FLAG_NHSYNC)
|
|
|
|
+ value |= SOR_STATE_ASY_HSYNCPOL;
|
|
|
|
+
|
|
|
|
+ if (mode->flags & DRM_MODE_FLAG_PVSYNC)
|
|
|
|
+ value &= ~SOR_STATE_ASY_VSYNCPOL;
|
|
|
|
+
|
|
|
|
+ if (mode->flags & DRM_MODE_FLAG_NVSYNC)
|
|
|
|
+ value |= SOR_STATE_ASY_VSYNCPOL;
|
|
|
|
+
|
|
|
|
+ switch (info->bpc) {
|
|
|
|
+ case 8:
|
|
|
|
+ value |= SOR_STATE_ASY_PIXELDEPTH_BPP_24_444;
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case 6:
|
|
|
|
+ value |= SOR_STATE_ASY_PIXELDEPTH_BPP_18_444;
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ default:
|
|
|
|
+ BUG();
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ tegra_sor_writel(sor, value, SOR_STATE1);
|
|
|
|
+
|
|
|
|
+ value = tegra_sor_readl(sor, SOR_HEAD_STATE0(dc->pipe));
|
|
|
|
+ value &= ~SOR_HEAD_STATE_RANGECOMPRESS_MASK;
|
|
|
|
+ value &= ~SOR_HEAD_STATE_DYNRANGE_MASK;
|
|
|
|
+ tegra_sor_writel(sor, value, SOR_HEAD_STATE0(dc->pipe));
|
|
|
|
+
|
|
|
|
+ value = tegra_sor_readl(sor, SOR_HEAD_STATE0(dc->pipe));
|
|
|
|
+ value &= ~SOR_HEAD_STATE_COLORSPACE_MASK;
|
|
|
|
+ value |= SOR_HEAD_STATE_COLORSPACE_RGB;
|
|
|
|
+ tegra_sor_writel(sor, value, SOR_HEAD_STATE0(dc->pipe));
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * TODO: The video timing programming below doesn't seem to match the
|
|
|
|
+ * register definitions.
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+ value = ((mode->vtotal & 0x7fff) << 16) | (mode->htotal & 0x7fff);
|
|
|
|
+ tegra_sor_writel(sor, value, SOR_HEAD_STATE1(dc->pipe));
|
|
|
|
+
|
|
|
|
+ /* sync end = sync width - 1 */
|
|
|
|
+ vse = mode->vsync_end - mode->vsync_start - 1;
|
|
|
|
+ hse = mode->hsync_end - mode->hsync_start - 1;
|
|
|
|
+
|
|
|
|
+ value = ((vse & 0x7fff) << 16) | (hse & 0x7fff);
|
|
|
|
+ tegra_sor_writel(sor, value, SOR_HEAD_STATE2(dc->pipe));
|
|
|
|
+
|
|
|
|
+ /* blank end = sync end + back porch */
|
|
|
|
+ vbe = vse + (mode->vtotal - mode->vsync_end);
|
|
|
|
+ hbe = hse + (mode->htotal - mode->hsync_end);
|
|
|
|
+
|
|
|
|
+ value = ((vbe & 0x7fff) << 16) | (hbe & 0x7fff);
|
|
|
|
+ tegra_sor_writel(sor, value, SOR_HEAD_STATE3(dc->pipe));
|
|
|
|
+
|
|
|
|
+ /* blank start = blank end + active */
|
|
|
|
+ vbs = vbe + mode->vdisplay;
|
|
|
|
+ hbs = hbe + mode->hdisplay;
|
|
|
|
+
|
|
|
|
+ value = ((vbs & 0x7fff) << 16) | (hbs & 0x7fff);
|
|
|
|
+ tegra_sor_writel(sor, value, SOR_HEAD_STATE4(dc->pipe));
|
|
|
|
+
|
|
|
|
+ tegra_sor_writel(sor, 0x1, SOR_HEAD_STATE5(dc->pipe));
|
|
|
|
+
|
|
|
|
+ tegra_sor_update(sor);
|
|
|
|
+
|
|
|
|
+ err = tegra_sor_attach(sor);
|
|
|
|
+ if (err < 0)
|
|
|
|
+ dev_err(sor->dev, "failed to attach SOR: %d\n", err);
|
|
|
|
+
|
|
|
|
+ /* enable display to SOR clock and generate HDMI preamble */
|
|
|
|
+ value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS);
|
|
|
|
+ value |= SOR1_ENABLE | SOR1_TIMING_CYA;
|
|
|
|
+ tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS);
|
|
|
|
+
|
|
|
|
+ tegra_dc_commit(dc);
|
|
|
|
+
|
|
|
|
+ err = tegra_sor_wakeup(sor);
|
|
|
|
+ if (err < 0)
|
|
|
|
+ dev_err(sor->dev, "failed to wakeup SOR: %d\n", err);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static const struct drm_encoder_helper_funcs tegra_sor_hdmi_helpers = {
|
|
|
|
+ .disable = tegra_sor_hdmi_disable,
|
|
|
|
+ .enable = tegra_sor_hdmi_enable,
|
|
|
|
+ .atomic_check = tegra_sor_encoder_atomic_check,
|
|
|
|
+};
|
|
|
|
+
|
|
static int tegra_sor_init(struct host1x_client *client)
|
|
static int tegra_sor_init(struct host1x_client *client)
|
|
{
|
|
{
|
|
struct drm_device *drm = dev_get_drvdata(client->parent);
|
|
struct drm_device *drm = dev_get_drvdata(client->parent);
|
|
|
|
+ const struct drm_encoder_helper_funcs *helpers = NULL;
|
|
struct tegra_sor *sor = host1x_client_to_sor(client);
|
|
struct tegra_sor *sor = host1x_client_to_sor(client);
|
|
|
|
+ int connector = DRM_MODE_CONNECTOR_Unknown;
|
|
|
|
+ int encoder = DRM_MODE_ENCODER_NONE;
|
|
int err;
|
|
int err;
|
|
|
|
|
|
- if (!sor->dpaux)
|
|
|
|
- return -ENODEV;
|
|
|
|
|
|
+ if (!sor->dpaux) {
|
|
|
|
+ if (sor->soc->supports_hdmi) {
|
|
|
|
+ connector = DRM_MODE_CONNECTOR_HDMIA;
|
|
|
|
+ encoder = DRM_MODE_ENCODER_TMDS;
|
|
|
|
+ helpers = &tegra_sor_hdmi_helpers;
|
|
|
|
+ } else if (sor->soc->supports_lvds) {
|
|
|
|
+ connector = DRM_MODE_CONNECTOR_LVDS;
|
|
|
|
+ encoder = DRM_MODE_ENCODER_LVDS;
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ if (sor->soc->supports_edp) {
|
|
|
|
+ connector = DRM_MODE_CONNECTOR_eDP;
|
|
|
|
+ encoder = DRM_MODE_ENCODER_TMDS;
|
|
|
|
+ helpers = &tegra_sor_edp_helpers;
|
|
|
|
+ } else if (sor->soc->supports_dp) {
|
|
|
|
+ connector = DRM_MODE_CONNECTOR_DisplayPort;
|
|
|
|
+ encoder = DRM_MODE_ENCODER_TMDS;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
sor->output.dev = sor->dev;
|
|
sor->output.dev = sor->dev;
|
|
|
|
|
|
drm_connector_init(drm, &sor->output.connector,
|
|
drm_connector_init(drm, &sor->output.connector,
|
|
&tegra_sor_connector_funcs,
|
|
&tegra_sor_connector_funcs,
|
|
- DRM_MODE_CONNECTOR_eDP);
|
|
|
|
|
|
+ connector);
|
|
drm_connector_helper_add(&sor->output.connector,
|
|
drm_connector_helper_add(&sor->output.connector,
|
|
&tegra_sor_connector_helper_funcs);
|
|
&tegra_sor_connector_helper_funcs);
|
|
sor->output.connector.dpms = DRM_MODE_DPMS_OFF;
|
|
sor->output.connector.dpms = DRM_MODE_DPMS_OFF;
|
|
|
|
|
|
drm_encoder_init(drm, &sor->output.encoder, &tegra_sor_encoder_funcs,
|
|
drm_encoder_init(drm, &sor->output.encoder, &tegra_sor_encoder_funcs,
|
|
- DRM_MODE_ENCODER_TMDS);
|
|
|
|
- drm_encoder_helper_add(&sor->output.encoder,
|
|
|
|
- &tegra_sor_edp_helper_funcs);
|
|
|
|
|
|
+ encoder);
|
|
|
|
+ drm_encoder_helper_add(&sor->output.encoder, helpers);
|
|
|
|
|
|
drm_mode_connector_attach_encoder(&sor->output.connector,
|
|
drm_mode_connector_attach_encoder(&sor->output.connector,
|
|
&sor->output.encoder);
|
|
&sor->output.encoder);
|
|
@@ -1497,18 +2272,130 @@ static const struct host1x_client_ops sor_client_ops = {
|
|
.exit = tegra_sor_exit,
|
|
.exit = tegra_sor_exit,
|
|
};
|
|
};
|
|
|
|
|
|
|
|
+static const struct tegra_sor_ops tegra_sor_edp_ops = {
|
|
|
|
+ .name = "eDP",
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static int tegra_sor_hdmi_probe(struct tegra_sor *sor)
|
|
|
|
+{
|
|
|
|
+ int err;
|
|
|
|
+
|
|
|
|
+ sor->avdd_io_supply = devm_regulator_get(sor->dev, "avdd-io");
|
|
|
|
+ if (IS_ERR(sor->avdd_io_supply)) {
|
|
|
|
+ dev_err(sor->dev, "cannot get AVDD I/O supply: %ld\n",
|
|
|
|
+ PTR_ERR(sor->avdd_io_supply));
|
|
|
|
+ return PTR_ERR(sor->avdd_io_supply);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ err = regulator_enable(sor->avdd_io_supply);
|
|
|
|
+ if (err < 0) {
|
|
|
|
+ dev_err(sor->dev, "failed to enable AVDD I/O supply: %d\n",
|
|
|
|
+ err);
|
|
|
|
+ return err;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ sor->vdd_pll_supply = devm_regulator_get(sor->dev, "vdd-pll");
|
|
|
|
+ if (IS_ERR(sor->vdd_pll_supply)) {
|
|
|
|
+ dev_err(sor->dev, "cannot get VDD PLL supply: %ld\n",
|
|
|
|
+ PTR_ERR(sor->vdd_pll_supply));
|
|
|
|
+ return PTR_ERR(sor->vdd_pll_supply);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ err = regulator_enable(sor->vdd_pll_supply);
|
|
|
|
+ if (err < 0) {
|
|
|
|
+ dev_err(sor->dev, "failed to enable VDD PLL supply: %d\n",
|
|
|
|
+ err);
|
|
|
|
+ return err;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ sor->hdmi_supply = devm_regulator_get(sor->dev, "hdmi");
|
|
|
|
+ if (IS_ERR(sor->hdmi_supply)) {
|
|
|
|
+ dev_err(sor->dev, "cannot get HDMI supply: %ld\n",
|
|
|
|
+ PTR_ERR(sor->hdmi_supply));
|
|
|
|
+ return PTR_ERR(sor->hdmi_supply);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ err = regulator_enable(sor->hdmi_supply);
|
|
|
|
+ if (err < 0) {
|
|
|
|
+ dev_err(sor->dev, "failed to enable HDMI supply: %d\n", err);
|
|
|
|
+ return err;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int tegra_sor_hdmi_remove(struct tegra_sor *sor)
|
|
|
|
+{
|
|
|
|
+ regulator_disable(sor->hdmi_supply);
|
|
|
|
+ regulator_disable(sor->vdd_pll_supply);
|
|
|
|
+ regulator_disable(sor->avdd_io_supply);
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static const struct tegra_sor_ops tegra_sor_hdmi_ops = {
|
|
|
|
+ .name = "HDMI",
|
|
|
|
+ .probe = tegra_sor_hdmi_probe,
|
|
|
|
+ .remove = tegra_sor_hdmi_remove,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static const struct tegra_sor_soc tegra124_sor = {
|
|
|
|
+ .supports_edp = true,
|
|
|
|
+ .supports_lvds = true,
|
|
|
|
+ .supports_hdmi = false,
|
|
|
|
+ .supports_dp = false,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static const struct tegra_sor_soc tegra210_sor = {
|
|
|
|
+ .supports_edp = true,
|
|
|
|
+ .supports_lvds = false,
|
|
|
|
+ .supports_hdmi = false,
|
|
|
|
+ .supports_dp = false,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static const struct tegra_sor_soc tegra210_sor1 = {
|
|
|
|
+ .supports_edp = false,
|
|
|
|
+ .supports_lvds = false,
|
|
|
|
+ .supports_hdmi = true,
|
|
|
|
+ .supports_dp = true,
|
|
|
|
+
|
|
|
|
+ .num_settings = ARRAY_SIZE(tegra210_sor_hdmi_defaults),
|
|
|
|
+ .settings = tegra210_sor_hdmi_defaults,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static const struct of_device_id tegra_sor_of_match[] = {
|
|
|
|
+ { .compatible = "nvidia,tegra210-sor1", .data = &tegra210_sor1 },
|
|
|
|
+ { .compatible = "nvidia,tegra210-sor", .data = &tegra210_sor },
|
|
|
|
+ { .compatible = "nvidia,tegra124-sor", .data = &tegra124_sor },
|
|
|
|
+ { },
|
|
|
|
+};
|
|
|
|
+MODULE_DEVICE_TABLE(of, tegra_sor_of_match);
|
|
|
|
+
|
|
static int tegra_sor_probe(struct platform_device *pdev)
|
|
static int tegra_sor_probe(struct platform_device *pdev)
|
|
{
|
|
{
|
|
|
|
+ const struct of_device_id *match;
|
|
struct device_node *np;
|
|
struct device_node *np;
|
|
struct tegra_sor *sor;
|
|
struct tegra_sor *sor;
|
|
struct resource *regs;
|
|
struct resource *regs;
|
|
int err;
|
|
int err;
|
|
|
|
|
|
|
|
+ match = of_match_device(tegra_sor_of_match, &pdev->dev);
|
|
|
|
+
|
|
sor = devm_kzalloc(&pdev->dev, sizeof(*sor), GFP_KERNEL);
|
|
sor = devm_kzalloc(&pdev->dev, sizeof(*sor), GFP_KERNEL);
|
|
if (!sor)
|
|
if (!sor)
|
|
return -ENOMEM;
|
|
return -ENOMEM;
|
|
|
|
|
|
sor->output.dev = sor->dev = &pdev->dev;
|
|
sor->output.dev = sor->dev = &pdev->dev;
|
|
|
|
+ sor->soc = match->data;
|
|
|
|
+
|
|
|
|
+ sor->settings = devm_kmemdup(&pdev->dev, sor->soc->settings,
|
|
|
|
+ sor->soc->num_settings *
|
|
|
|
+ sizeof(*sor->settings),
|
|
|
|
+ GFP_KERNEL);
|
|
|
|
+ if (!sor->settings)
|
|
|
|
+ return -ENOMEM;
|
|
|
|
+
|
|
|
|
+ sor->num_settings = sor->soc->num_settings;
|
|
|
|
|
|
np = of_parse_phandle(pdev->dev.of_node, "nvidia,dpaux", 0);
|
|
np = of_parse_phandle(pdev->dev.of_node, "nvidia,dpaux", 0);
|
|
if (np) {
|
|
if (np) {
|
|
@@ -1519,50 +2406,83 @@ static int tegra_sor_probe(struct platform_device *pdev)
|
|
return -EPROBE_DEFER;
|
|
return -EPROBE_DEFER;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ if (!sor->dpaux) {
|
|
|
|
+ if (sor->soc->supports_hdmi) {
|
|
|
|
+ sor->ops = &tegra_sor_hdmi_ops;
|
|
|
|
+ } else if (sor->soc->supports_lvds) {
|
|
|
|
+ dev_err(&pdev->dev, "LVDS not supported yet\n");
|
|
|
|
+ return -ENODEV;
|
|
|
|
+ } else {
|
|
|
|
+ dev_err(&pdev->dev, "unknown (non-DP) support\n");
|
|
|
|
+ return -ENODEV;
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ if (sor->soc->supports_edp) {
|
|
|
|
+ sor->ops = &tegra_sor_edp_ops;
|
|
|
|
+ } else if (sor->soc->supports_dp) {
|
|
|
|
+ dev_err(&pdev->dev, "DisplayPort not supported yet\n");
|
|
|
|
+ return -ENODEV;
|
|
|
|
+ } else {
|
|
|
|
+ dev_err(&pdev->dev, "unknown (DP) support\n");
|
|
|
|
+ return -ENODEV;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
err = tegra_output_probe(&sor->output);
|
|
err = tegra_output_probe(&sor->output);
|
|
if (err < 0) {
|
|
if (err < 0) {
|
|
dev_err(&pdev->dev, "failed to probe output: %d\n", err);
|
|
dev_err(&pdev->dev, "failed to probe output: %d\n", err);
|
|
return err;
|
|
return err;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ if (sor->ops && sor->ops->probe) {
|
|
|
|
+ err = sor->ops->probe(sor);
|
|
|
|
+ if (err < 0) {
|
|
|
|
+ dev_err(&pdev->dev, "failed to probe %s: %d\n",
|
|
|
|
+ sor->ops->name, err);
|
|
|
|
+ goto output;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
sor->regs = devm_ioremap_resource(&pdev->dev, regs);
|
|
sor->regs = devm_ioremap_resource(&pdev->dev, regs);
|
|
- if (IS_ERR(sor->regs))
|
|
|
|
- return PTR_ERR(sor->regs);
|
|
|
|
|
|
+ if (IS_ERR(sor->regs)) {
|
|
|
|
+ err = PTR_ERR(sor->regs);
|
|
|
|
+ goto remove;
|
|
|
|
+ }
|
|
|
|
|
|
sor->rst = devm_reset_control_get(&pdev->dev, "sor");
|
|
sor->rst = devm_reset_control_get(&pdev->dev, "sor");
|
|
if (IS_ERR(sor->rst)) {
|
|
if (IS_ERR(sor->rst)) {
|
|
- dev_err(&pdev->dev, "failed to get reset control: %ld\n",
|
|
|
|
- PTR_ERR(sor->rst));
|
|
|
|
- return PTR_ERR(sor->rst);
|
|
|
|
|
|
+ err = PTR_ERR(sor->rst);
|
|
|
|
+ dev_err(&pdev->dev, "failed to get reset control: %d\n", err);
|
|
|
|
+ goto remove;
|
|
}
|
|
}
|
|
|
|
|
|
sor->clk = devm_clk_get(&pdev->dev, NULL);
|
|
sor->clk = devm_clk_get(&pdev->dev, NULL);
|
|
if (IS_ERR(sor->clk)) {
|
|
if (IS_ERR(sor->clk)) {
|
|
- dev_err(&pdev->dev, "failed to get module clock: %ld\n",
|
|
|
|
- PTR_ERR(sor->clk));
|
|
|
|
- return PTR_ERR(sor->clk);
|
|
|
|
|
|
+ err = PTR_ERR(sor->clk);
|
|
|
|
+ dev_err(&pdev->dev, "failed to get module clock: %d\n", err);
|
|
|
|
+ goto remove;
|
|
}
|
|
}
|
|
|
|
|
|
sor->clk_parent = devm_clk_get(&pdev->dev, "parent");
|
|
sor->clk_parent = devm_clk_get(&pdev->dev, "parent");
|
|
if (IS_ERR(sor->clk_parent)) {
|
|
if (IS_ERR(sor->clk_parent)) {
|
|
- dev_err(&pdev->dev, "failed to get parent clock: %ld\n",
|
|
|
|
- PTR_ERR(sor->clk_parent));
|
|
|
|
- return PTR_ERR(sor->clk_parent);
|
|
|
|
|
|
+ err = PTR_ERR(sor->clk_parent);
|
|
|
|
+ dev_err(&pdev->dev, "failed to get parent clock: %d\n", err);
|
|
|
|
+ goto remove;
|
|
}
|
|
}
|
|
|
|
|
|
sor->clk_safe = devm_clk_get(&pdev->dev, "safe");
|
|
sor->clk_safe = devm_clk_get(&pdev->dev, "safe");
|
|
if (IS_ERR(sor->clk_safe)) {
|
|
if (IS_ERR(sor->clk_safe)) {
|
|
- dev_err(&pdev->dev, "failed to get safe clock: %ld\n",
|
|
|
|
- PTR_ERR(sor->clk_safe));
|
|
|
|
- return PTR_ERR(sor->clk_safe);
|
|
|
|
|
|
+ err = PTR_ERR(sor->clk_safe);
|
|
|
|
+ dev_err(&pdev->dev, "failed to get safe clock: %d\n", err);
|
|
|
|
+ goto remove;
|
|
}
|
|
}
|
|
|
|
|
|
sor->clk_dp = devm_clk_get(&pdev->dev, "dp");
|
|
sor->clk_dp = devm_clk_get(&pdev->dev, "dp");
|
|
if (IS_ERR(sor->clk_dp)) {
|
|
if (IS_ERR(sor->clk_dp)) {
|
|
- dev_err(&pdev->dev, "failed to get DP clock: %ld\n",
|
|
|
|
- PTR_ERR(sor->clk_dp));
|
|
|
|
- return PTR_ERR(sor->clk_dp);
|
|
|
|
|
|
+ err = PTR_ERR(sor->clk_dp);
|
|
|
|
+ dev_err(&pdev->dev, "failed to get DP clock: %d\n", err);
|
|
|
|
+ goto remove;
|
|
}
|
|
}
|
|
|
|
|
|
INIT_LIST_HEAD(&sor->client.list);
|
|
INIT_LIST_HEAD(&sor->client.list);
|
|
@@ -1573,12 +2493,19 @@ static int tegra_sor_probe(struct platform_device *pdev)
|
|
if (err < 0) {
|
|
if (err < 0) {
|
|
dev_err(&pdev->dev, "failed to register host1x client: %d\n",
|
|
dev_err(&pdev->dev, "failed to register host1x client: %d\n",
|
|
err);
|
|
err);
|
|
- return err;
|
|
|
|
|
|
+ goto remove;
|
|
}
|
|
}
|
|
|
|
|
|
platform_set_drvdata(pdev, sor);
|
|
platform_set_drvdata(pdev, sor);
|
|
|
|
|
|
return 0;
|
|
return 0;
|
|
|
|
+
|
|
|
|
+remove:
|
|
|
|
+ if (sor->ops && sor->ops->remove)
|
|
|
|
+ sor->ops->remove(sor);
|
|
|
|
+output:
|
|
|
|
+ tegra_output_remove(&sor->output);
|
|
|
|
+ return err;
|
|
}
|
|
}
|
|
|
|
|
|
static int tegra_sor_remove(struct platform_device *pdev)
|
|
static int tegra_sor_remove(struct platform_device *pdev)
|
|
@@ -1593,18 +2520,17 @@ static int tegra_sor_remove(struct platform_device *pdev)
|
|
return err;
|
|
return err;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ if (sor->ops && sor->ops->remove) {
|
|
|
|
+ err = sor->ops->remove(sor);
|
|
|
|
+ if (err < 0)
|
|
|
|
+ dev_err(&pdev->dev, "failed to remove SOR: %d\n", err);
|
|
|
|
+ }
|
|
|
|
+
|
|
tegra_output_remove(&sor->output);
|
|
tegra_output_remove(&sor->output);
|
|
|
|
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
-static const struct of_device_id tegra_sor_of_match[] = {
|
|
|
|
- { .compatible = "nvidia,tegra124-sor", },
|
|
|
|
- { .compatible = "nvidia,tegra210-sor", },
|
|
|
|
- { },
|
|
|
|
-};
|
|
|
|
-MODULE_DEVICE_TABLE(of, tegra_sor_of_match);
|
|
|
|
-
|
|
|
|
struct platform_driver tegra_sor_driver = {
|
|
struct platform_driver tegra_sor_driver = {
|
|
.driver = {
|
|
.driver = {
|
|
.name = "tegra-sor",
|
|
.name = "tegra-sor",
|