Merge tag 'ti-v2020.07-rc3' of https://gitlab.denx.de/u-boot/custodians/u-boot-ti
[oweals/u-boot.git] / drivers / video / sunxi / sunxi_dw_hdmi.c
index 33920a2b676a9126902aa0fe8598e5de5e8ba5c1..01d4b7a11c96026a87b3b271cc8ea4cd431945b1 100644 (file)
@@ -1,9 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0+
 /*
  * Allwinner DW HDMI bridge
  *
  * (C) Copyright 2017 Jernej Skrabec <jernej.skrabec@siol.net>
- *
- * SPDX-License-Identifier:    GPL-2.0+
  */
 
 #include <common.h>
 #include <dm.h>
 #include <dw_hdmi.h>
 #include <edid.h>
+#include <log.h>
+#include <time.h>
 #include <asm/io.h>
 #include <asm/arch/clock.h>
 #include <asm/arch/lcdc.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
 
 struct sunxi_dw_hdmi_priv {
        struct dw_hdmi hdmi;
@@ -133,7 +136,7 @@ static int sunxi_dw_hdmi_wait_for_hpd(void)
        return -1;
 }
 
-static void sunxi_dw_hdmi_phy_set(uint clock)
+static void sunxi_dw_hdmi_phy_set(uint clock, int phy_div)
 {
        struct sunxi_hdmi_phy * const phy =
                (struct sunxi_hdmi_phy *)(SUNXI_HDMI_BASE + HDMI_PHY_OFFS);
@@ -147,7 +150,7 @@ static void sunxi_dw_hdmi_phy_set(uint clock)
        switch (div) {
        case 1:
                writel(0x30dc5fc0, &phy->pll);
-               writel(0x800863C0, &phy->clk);
+               writel(0x800863C0 | (phy_div - 1), &phy->clk);
                mdelay(10);
                writel(0x00000001, &phy->unk3);
                setbits_le32(&phy->pll, BIT(25));
@@ -165,7 +168,7 @@ static void sunxi_dw_hdmi_phy_set(uint clock)
                break;
        case 2:
                writel(0x39dc5040, &phy->pll);
-               writel(0x80084381, &phy->clk);
+               writel(0x80084380 | (phy_div - 1), &phy->clk);
                mdelay(10);
                writel(0x00000001, &phy->unk3);
                setbits_le32(&phy->pll, BIT(25));
@@ -179,7 +182,7 @@ static void sunxi_dw_hdmi_phy_set(uint clock)
                break;
        case 4:
                writel(0x39dc5040, &phy->pll);
-               writel(0x80084343, &phy->clk);
+               writel(0x80084340 | (phy_div - 1), &phy->clk);
                mdelay(10);
                writel(0x00000001, &phy->unk3);
                setbits_le32(&phy->pll, BIT(25));
@@ -193,7 +196,7 @@ static void sunxi_dw_hdmi_phy_set(uint clock)
                break;
        case 11:
                writel(0x39dc5040, &phy->pll);
-               writel(0x8008430a, &phy->clk);
+               writel(0x80084300 | (phy_div - 1), &phy->clk);
                mdelay(10);
                writel(0x00000001, &phy->unk3);
                setbits_le32(&phy->pll, BIT(25));
@@ -208,36 +211,46 @@ static void sunxi_dw_hdmi_phy_set(uint clock)
        }
 }
 
-static void sunxi_dw_hdmi_pll_set(uint clk_khz)
+static void sunxi_dw_hdmi_pll_set(uint clk_khz, int *phy_div)
 {
-       int value, n, m, div = 0, diff;
-       int best_n = 0, best_m = 0, best_diff = 0x0FFFFFFF;
-
-       div = sunxi_dw_hdmi_get_divider(clk_khz * 1000);
+       int value, n, m, div, diff;
+       int best_n = 0, best_m = 0, best_div = 0, best_diff = 0x0FFFFFFF;
 
        /*
         * Find the lowest divider resulting in a matching clock. If there
         * is no match, pick the closest lower clock, as monitors tend to
         * not sync to higher frequencies.
         */
-       for (m = 1; m <= 16; m++) {
-               n = (m * div * clk_khz) / 24000;
-
-               if ((n >= 1) && (n <= 128)) {
-                       value = (24000 * n) / m / div;
-                       diff = clk_khz - value;
-                       if (diff < best_diff) {
-                               best_diff = diff;
-                               best_m = m;
-                               best_n = n;
+       for (div = 1; div <= 16; div++) {
+               int target = clk_khz * div;
+
+               if (target < 192000)
+                       continue;
+               if (target > 912000)
+                       continue;
+
+               for (m = 1; m <= 16; m++) {
+                       n = (m * target) / 24000;
+
+                       if (n >= 1 && n <= 128) {
+                               value = (24000 * n) / m / div;
+                               diff = clk_khz - value;
+                               if (diff < best_diff) {
+                                       best_diff = diff;
+                                       best_m = m;
+                                       best_n = n;
+                                       best_div = div;
+                               }
                        }
                }
        }
 
+       *phy_div = best_div;
+
        clock_set_pll3_factors(best_m, best_n);
        debug("dotclock: %dkHz = %dkHz: (24MHz * %d) / %d / %d\n",
-             clk_khz, (clock_get_pll3() / 1000) / div,
-             best_n, best_m, div);
+             clk_khz, (clock_get_pll3() / 1000) / best_div,
+             best_n, best_m, best_div);
 }
 
 static void sunxi_dw_hdmi_lcdc_init(int mux, const struct display_timing *edid,
@@ -245,7 +258,7 @@ static void sunxi_dw_hdmi_lcdc_init(int mux, const struct display_timing *edid,
 {
        struct sunxi_ccm_reg * const ccm =
                (struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
-       int div = sunxi_dw_hdmi_get_divider(edid->pixelclock.typ);
+       int div = DIV_ROUND_UP(clock_get_pll3(), edid->pixelclock.typ);
        struct sunxi_lcdc_reg *lcdc;
 
        if (mux == 0) {
@@ -277,8 +290,10 @@ static void sunxi_dw_hdmi_lcdc_init(int mux, const struct display_timing *edid,
 
 static int sunxi_dw_hdmi_phy_cfg(struct dw_hdmi *hdmi, uint mpixelclock)
 {
-       sunxi_dw_hdmi_pll_set(mpixelclock/1000);
-       sunxi_dw_hdmi_phy_set(mpixelclock);
+       int phy_div;
+
+       sunxi_dw_hdmi_pll_set(mpixelclock / 1000, &phy_div);
+       sunxi_dw_hdmi_phy_set(mpixelclock, phy_div);
 
        return 0;
 }
@@ -304,15 +319,11 @@ static int sunxi_dw_hdmi_enable(struct udevice *dev, int panel_bpp,
 
        sunxi_dw_hdmi_lcdc_init(priv->mux, edid, panel_bpp);
 
-       /*
-        * Condition in original code is a bit weird. This is attempt
-        * to make it more reasonable and it works. It could be that
-        * bits and conditions are related and should be separated.
-        */
-       if (!((edid->flags & DISPLAY_FLAGS_HSYNC_HIGH) &&
-             (edid->flags & DISPLAY_FLAGS_VSYNC_HIGH))) {
-               setbits_le32(&phy->pol, 0x300);
-       }
+       if (edid->flags & DISPLAY_FLAGS_VSYNC_LOW)
+               setbits_le32(&phy->pol, 0x200);
+
+       if (edid->flags & DISPLAY_FLAGS_HSYNC_LOW)
+               setbits_le32(&phy->pol, 0x100);
 
        setbits_le32(&phy->ctrl, 0xf << 12);
 
@@ -366,6 +377,9 @@ static int sunxi_dw_hdmi_probe(struct udevice *dev)
        priv->hdmi.phy_set = sunxi_dw_hdmi_phy_cfg;
        priv->mux = uc_plat->source_id;
 
+       uclass_get_device_by_phandle(UCLASS_I2C, dev, "ddc-i2c-bus",
+                                    &priv->hdmi.ddc_bus);
+
        dw_hdmi_init(&priv->hdmi);
 
        return 0;