drm/tegra: sor: Add HDMI support
authorThierry Reding <treding@nvidia.com>
Thu, 30 Jul 2015 08:34:24 +0000 (10:34 +0200)
committerThierry Reding <treding@nvidia.com>
Thu, 13 Aug 2015 11:49:37 +0000 (13:49 +0200)
The SOR1 introduced on Tegra210 supports HDMI 2.0 and DisplayPort. Add
HDMI support and name the debugfs node after the type of SOR. The SOR
introduced with Tegra124 is known simply as "sor", whereas the
additional SOR found on Tegra210 is known as "sor1".

Signed-off-by: Thierry Reding <treding@nvidia.com>
Documentation/devicetree/bindings/gpu/nvidia,tegra20-host1x.txt
drivers/gpu/drm/tegra/dc.h
drivers/gpu/drm/tegra/drm.c
drivers/gpu/drm/tegra/sor.c
drivers/gpu/drm/tegra/sor.h

index 6261159112822309036d371f5c8154e44b059136..e685610d38e26cfa439d986d5f12995593ba1daf 100644 (file)
@@ -201,6 +201,7 @@ of the following host1x client modules:
     - "nvidia,tegra124-sor": for Tegra124 and Tegra132
     - "nvidia,tegra132-sor": for Tegra132
     - "nvidia,tegra210-sor": for Tegra210
+    - "nvidia,tegra210-sor1": for Tegra210
   - reg: Physical base address and length of the controller's registers.
   - interrupts: The interrupt outputs from the controller.
   - clocks: Must contain an entry for each entry in clock-names.
index 203056a378f077e4b598ce27ac35ada360d0402e..4a268635749bdc667ef3ec86b3c7e3774f1faf09 100644 (file)
 #define DC_DISP_DISP_WIN_OPTIONS               0x402
 #define HDMI_ENABLE    (1 << 30)
 #define DSI_ENABLE     (1 << 29)
+#define SOR1_TIMING_CYA        (1 << 27)
+#define SOR1_ENABLE    (1 << 26)
 #define SOR_ENABLE     (1 << 25)
 #define CURSOR_ENABLE  (1 << 16)
 
 #define BASE_COLOR_SIZE565     (6 << 0)
 #define BASE_COLOR_SIZE332     (7 << 0)
 #define BASE_COLOR_SIZE888     (8 << 0)
+#define DITHER_CONTROL_MASK    (3 << 8)
 #define DITHER_CONTROL_DISABLE (0 << 8)
 #define DITHER_CONTROL_ORDERED (2 << 8)
 #define DITHER_CONTROL_ERRDIFF (3 << 8)
+#define BASE_COLOR_SIZE_MASK   (0xf << 0)
 #define BASE_COLOR_SIZE_666    (0 << 0)
 #define BASE_COLOR_SIZE_111    (1 << 0)
 #define BASE_COLOR_SIZE_222    (2 << 0)
index dfbbd88b040f557ff1e67f965bb1ea9b6182b81c..6d88cf1fcd1cd3e0a6335fa766d8ca578c0815e3 100644 (file)
@@ -1061,6 +1061,7 @@ static const struct of_device_id host1x_drm_subdevs[] = {
        { .compatible = "nvidia,tegra210-dc", },
        { .compatible = "nvidia,tegra210-dsi", },
        { .compatible = "nvidia,tegra210-sor", },
+       { .compatible = "nvidia,tegra210-sor1", },
        { /* sentinel */ }
 };
 
index 8495478d1e15aa9664f8bf611f0aaeb1cdbdee1d..da1715ebdd7118c698621e574764b0054aeb125b 100644 (file)
@@ -10,7 +10,9 @@
 #include <linux/debugfs.h>
 #include <linux/gpio.h>
 #include <linux/io.h>
+#include <linux/of_device.h>
 #include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
 #include <linux/reset.h>
 
 #include <soc/tegra/pmc.h>
 #include "drm.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 host1x_client client;
        struct tegra_output output;
        struct device *dev;
 
+       const struct tegra_sor_soc *soc;
        void __iomem *regs;
 
        struct reset_control *rst;
@@ -41,6 +178,16 @@ struct tegra_sor {
        struct drm_info_list *debugfs_files;
        struct drm_minor *minor;
        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 {
@@ -184,6 +331,47 @@ static int tegra_sor_dp_train_fast(struct tegra_sor *sor,
        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)
 {
        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,
                                  struct drm_minor *minor)
 {
+       const char *name = sor->soc->supports_dp ? "sor1" : "sor";
        unsigned int i;
        int err;
 
-       sor->debugfs = debugfs_create_dir("sor", minor->debugfs_root);
+       sor->debugfs = debugfs_create_dir(name, minor->debugfs_root);
        if (!sor->debugfs)
                return -ENOMEM;
 
@@ -858,7 +1047,7 @@ tegra_sor_connector_detect(struct drm_connector *connector, bool force)
        if (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 = {
@@ -956,6 +1145,48 @@ static void tegra_sor_edp_disable(struct drm_encoder *encoder)
        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)
 {
        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;
 }
 
-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,
        .enable = tegra_sor_edp_enable,
        .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)
 {
        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);
+       int connector = DRM_MODE_CONNECTOR_Unknown;
+       int encoder = DRM_MODE_ENCODER_NONE;
        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;
 
        drm_connector_init(drm, &sor->output.connector,
                           &tegra_sor_connector_funcs,
-                          DRM_MODE_CONNECTOR_eDP);
+                          connector);
        drm_connector_helper_add(&sor->output.connector,
                                 &tegra_sor_connector_helper_funcs);
        sor->output.connector.dpms = DRM_MODE_DPMS_OFF;
 
        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,
                                          &sor->output.encoder);
@@ -1497,18 +2272,130 @@ static const struct host1x_client_ops sor_client_ops = {
        .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)
 {
+       const struct of_device_id *match;
        struct device_node *np;
        struct tegra_sor *sor;
        struct resource *regs;
        int err;
 
+       match = of_match_device(tegra_sor_of_match, &pdev->dev);
+
        sor = devm_kzalloc(&pdev->dev, sizeof(*sor), GFP_KERNEL);
        if (!sor)
                return -ENOMEM;
 
        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);
        if (np) {
@@ -1519,50 +2406,83 @@ static int tegra_sor_probe(struct platform_device *pdev)
                        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);
        if (err < 0) {
                dev_err(&pdev->dev, "failed to probe output: %d\n", 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);
        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");
        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);
        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");
        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");
        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");
        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);
@@ -1573,12 +2493,19 @@ static int tegra_sor_probe(struct platform_device *pdev)
        if (err < 0) {
                dev_err(&pdev->dev, "failed to register host1x client: %d\n",
                        err);
-               return err;
+               goto remove;
        }
 
        platform_set_drvdata(pdev, sor);
 
        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)
@@ -1593,18 +2520,17 @@ static int tegra_sor_remove(struct platform_device *pdev)
                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);
 
        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 = {
        .driver = {
                .name = "tegra-sor",
index 561b03ba969da5e9de4825d9acfcb716520b4f84..2d31d027e3f68cfe22d36d03cd4f75f6a575333b 100644 (file)
 #define  SOR_STATE_ASY_OWNER(x)                        (((x) & 0xf) << 0)
 
 #define SOR_HEAD_STATE0(x) (0x05 + (x))
+#define  SOR_HEAD_STATE_RANGECOMPRESS_MASK (0x1 << 3)
+#define  SOR_HEAD_STATE_DYNRANGE_MASK (0x1 << 2)
+#define  SOR_HEAD_STATE_DYNRANGE_VESA (0 << 2)
+#define  SOR_HEAD_STATE_DYNRANGE_CEA (1 << 2)
+#define  SOR_HEAD_STATE_COLORSPACE_MASK (0x3 << 0)
+#define  SOR_HEAD_STATE_COLORSPACE_RGB (0 << 0)
 #define SOR_HEAD_STATE1(x) (0x07 + (x))
 #define SOR_HEAD_STATE2(x) (0x09 + (x))
 #define SOR_HEAD_STATE3(x) (0x0b + (x))
 
 #define SOR_PLL1 0x18
 /* XXX: read-only bit? */
+#define  SOR_PLL1_LOADADJ_MASK                 (0xf << 20)
+#define  SOR_PLL1_LOADADJ(x)                   (((x) & 0xf) << 20)
 #define  SOR_PLL1_TERM_COMPOUT                 (1 << 15)
+#define  SOR_PLL1_TMDS_TERMADJ_MASK            (0xf << 9)
+#define  SOR_PLL1_TMDS_TERMADJ(x)              (((x) & 0xf) << 9)
 #define  SOR_PLL1_TMDS_TERM                    (1 << 8)
 
 #define SOR_PLL2 0x19
 #define  SOR_PLL2_BANDGAP_POWERDOWN            (1 << 22)
 #define  SOR_PLL2_POWERDOWN_OVERRIDE           (1 << 18)
 #define  SOR_PLL2_SEQ_PLLCAPPD                 (1 << 17)
+#define  SOR_PLL2_SEQ_PLL_PULLDOWN             (1 << 16)
 
 #define SOR_PLL3 0x1a
+#define  SOR_PLL3_BG_VREF_LEVEL_MASK           (0xf << 24)
+#define  SOR_PLL3_BG_VREF_LEVEL(x)             (((x) & 0xf) << 24)
 #define  SOR_PLL3_PLL_VDD_MODE_1V8             (0 << 13)
 #define  SOR_PLL3_PLL_VDD_MODE_3V3             (1 << 13)
 
 #define SOR_CSTM 0x1b
+#define  SOR_CSTM_ROTCLK_MASK                  (0xf << 24)
+#define  SOR_CSTM_ROTCLK(x)                    (((x) & 0xf) << 24)
 #define  SOR_CSTM_LVDS                         (1 << 16)
 #define  SOR_CSTM_LINK_ACT_B                   (1 << 15)
 #define  SOR_CSTM_LINK_ACT_A                   (1 << 14)
 #define SOR_CRCB 0x1e
 #define SOR_BLANK 0x1f
 #define SOR_SEQ_CTL 0x20
+#define  SOR_SEQ_CTL_PD_PC_ALT(x)      (((x) & 0xf) << 12)
+#define  SOR_SEQ_CTL_PD_PC(x)          (((x) & 0xf) <<  8)
+#define  SOR_SEQ_CTL_PU_PC_ALT(x)      (((x) & 0xf) <<  4)
+#define  SOR_SEQ_CTL_PU_PC(x)          (((x) & 0xf) <<  0)
 
 #define SOR_LANE_SEQ_CTL 0x21
 #define  SOR_LANE_SEQ_CTL_TRIGGER              (1 << 31)
+#define  SOR_LANE_SEQ_CTL_STATE_BUSY           (1 << 28)
 #define  SOR_LANE_SEQ_CTL_SEQUENCE_UP          (0 << 20)
 #define  SOR_LANE_SEQ_CTL_SEQUENCE_DOWN                (1 << 20)
 #define  SOR_LANE_SEQ_CTL_POWER_STATE_UP       (0 << 16)
 #define  SOR_LANE_SEQ_CTL_POWER_STATE_DOWN     (1 << 16)
+#define  SOR_LANE_SEQ_CTL_DELAY(x)             (((x) & 0xf) << 12)
 
 #define SOR_SEQ_INST(x) (0x22 + (x))
+#define  SOR_SEQ_INST_PLL_PULLDOWN (1 << 31)
+#define  SOR_SEQ_INST_POWERDOWN_MACRO (1 << 30)
+#define  SOR_SEQ_INST_ASSERT_PLL_RESET (1 << 29)
+#define  SOR_SEQ_INST_BLANK_V (1 << 28)
+#define  SOR_SEQ_INST_BLANK_H (1 << 27)
+#define  SOR_SEQ_INST_BLANK_DE (1 << 26)
+#define  SOR_SEQ_INST_BLACK_DATA (1 << 25)
+#define  SOR_SEQ_INST_TRISTATE_IOS (1 << 24)
+#define  SOR_SEQ_INST_DRIVE_PWM_OUT_LO (1 << 23)
+#define  SOR_SEQ_INST_PIN_B_LOW (0 << 22)
+#define  SOR_SEQ_INST_PIN_B_HIGH (1 << 22)
+#define  SOR_SEQ_INST_PIN_A_LOW (0 << 21)
+#define  SOR_SEQ_INST_PIN_A_HIGH (1 << 21)
+#define  SOR_SEQ_INST_SEQUENCE_UP (0 << 19)
+#define  SOR_SEQ_INST_SEQUENCE_DOWN (1 << 19)
+#define  SOR_SEQ_INST_LANE_SEQ_STOP (0 << 18)
+#define  SOR_SEQ_INST_LANE_SEQ_RUN (1 << 18)
+#define  SOR_SEQ_INST_PORT_POWERDOWN (1 << 17)
+#define  SOR_SEQ_INST_PLL_POWERDOWN (1 << 16)
+#define  SOR_SEQ_INST_HALT (1 << 15)
+#define  SOR_SEQ_INST_WAIT_US (0 << 12)
+#define  SOR_SEQ_INST_WAIT_MS (1 << 12)
+#define  SOR_SEQ_INST_WAIT_VSYNC (2 << 12)
+#define  SOR_SEQ_INST_WAIT(x) (((x) & 0x3ff) << 0)
 
 #define SOR_PWM_DIV 0x32
 #define  SOR_PWM_DIV_MASK                      0xffffff
 #define SOR_TRIG 0x48
 #define SOR_MSCHECK 0x49
 #define SOR_XBAR_CTRL 0x4a
+#define  SOR_XBAR_CTRL_LINK1_XSEL(channel, value) ((((value) & 0x7) << ((channel) * 3)) << 17)
+#define  SOR_XBAR_CTRL_LINK0_XSEL(channel, value) ((((value) & 0x7) << ((channel) * 3)) <<  2)
+#define  SOR_XBAR_CTRL_LINK_SWAP (1 << 1)
+#define  SOR_XBAR_CTRL_BYPASS (1 << 0)
 #define SOR_XBAR_POL 0x4b
 
 #define SOR_DP_LINKCTL0 0x4c
 #define SOR_DP_DEBUG1 0x5f
 
 #define SOR_DP_SPARE0 0x60
+#define  SOR_DP_SPARE_DISP_VIDEO_PREAMBLE      (1 << 3)
 #define  SOR_DP_SPARE_MACRO_SOR_CLK            (1 << 2)
 #define  SOR_DP_SPARE_PANEL_INTERNAL           (1 << 1)
 #define  SOR_DP_SPARE_SEQ_ENABLE               (1 << 0)
 #define SOR_DP_LQ_CSTM1 0x70
 #define SOR_DP_LQ_CSTM2 0x71
 
+#define SOR_HDMI_AUDIO_INFOFRAME_CTRL 0x9a
+#define SOR_HDMI_AUDIO_INFOFRAME_STATUS 0x9b
+#define SOR_HDMI_AUDIO_INFOFRAME_HEADER 0x9c
+
+#define SOR_HDMI_AVI_INFOFRAME_CTRL 0x9f
+#define  INFOFRAME_CTRL_CHECKSUM_ENABLE        (1 << 9)
+#define  INFOFRAME_CTRL_SINGLE         (1 << 8)
+#define  INFOFRAME_CTRL_OTHER          (1 << 4)
+#define  INFOFRAME_CTRL_ENABLE         (1 << 0)
+
+#define SOR_HDMI_AVI_INFOFRAME_STATUS 0xa0
+#define  INFOFRAME_STATUS_DONE         (1 << 0)
+
+#define SOR_HDMI_AVI_INFOFRAME_HEADER 0xa1
+#define  INFOFRAME_HEADER_LEN(x) (((x) & 0xff) << 16)
+#define  INFOFRAME_HEADER_VERSION(x) (((x) & 0xff) << 8)
+#define  INFOFRAME_HEADER_TYPE(x) (((x) & 0xff) << 0)
+
+#define SOR_HDMI_CTRL 0xc0
+#define  SOR_HDMI_CTRL_ENABLE (1 << 30)
+#define  SOR_HDMI_CTRL_MAX_AC_PACKET(x) (((x) & 0x1f) << 16)
+#define  SOR_HDMI_CTRL_AUDIO_LAYOUT (1 << 10)
+#define  SOR_HDMI_CTRL_REKEY(x) (((x) & 0x7f) << 0)
+
+#define SOR_REFCLK 0xe6
+#define  SOR_REFCLK_DIV_INT(x) ((((x) >> 2) & 0xff) << 8)
+#define  SOR_REFCLK_DIV_FRAC(x) (((x) & 0x3) << 6)
+
+#define SOR_INPUT_CONTROL 0xe8
+#define  SOR_INPUT_CONTROL_ARM_VIDEO_RANGE_LIMITED (1 << 1)
+#define  SOR_INPUT_CONTROL_HDMI_SRC_SELECT(x) (((x) & 0x1) << 0)
+
+#define SOR_HDMI_VSI_INFOFRAME_CTRL 0x123
+#define SOR_HDMI_VSI_INFOFRAME_STATUS 0x124
+#define SOR_HDMI_VSI_INFOFRAME_HEADER 0x125
+
 #endif