drm/tegra: sor: Reimplement pad clock
authorThierry Reding <treding@nvidia.com>
Thu, 12 Oct 2017 15:53:11 +0000 (17:53 +0200)
committerThierry Reding <treding@nvidia.com>
Mon, 20 Nov 2017 12:23:54 +0000 (13:23 +0100)
The current implementation of the pad clock isn't quite correct. This
has the side-effect of being incompatible with the implementation for
Tegra186 (provided by the BPMP) and therefore would require a massive
change to the driver to cope with the differences. Instead, simply do
what Tegra186 does and add some code to fallback to the old behaviour
for existing device trees.

Signed-off-by: Thierry Reding <treding@nvidia.com>
drivers/gpu/drm/tegra/sor.c

index 4bcacd3f48613d416deea7a32cff7f58c97709b3..b0a1dedac8026e0ad89ee1227cb7b7881d3fee7d 100644 (file)
@@ -174,9 +174,9 @@ struct tegra_sor {
 
        struct reset_control *rst;
        struct clk *clk_parent;
-       struct clk *clk_brick;
        struct clk *clk_safe;
-       struct clk *clk_src;
+       struct clk *clk_out;
+       struct clk *clk_pad;
        struct clk *clk_dp;
        struct clk *clk;
 
@@ -255,7 +255,7 @@ static int tegra_sor_set_parent_clock(struct tegra_sor *sor, struct clk *parent)
 
        clk_disable_unprepare(sor->clk);
 
-       err = clk_set_parent(sor->clk, parent);
+       err = clk_set_parent(sor->clk_out, parent);
        if (err < 0)
                return err;
 
@@ -266,24 +266,24 @@ static int tegra_sor_set_parent_clock(struct tegra_sor *sor, struct clk *parent)
        return 0;
 }
 
-struct tegra_clk_sor_brick {
+struct tegra_clk_sor_pad {
        struct clk_hw hw;
        struct tegra_sor *sor;
 };
 
-static inline struct tegra_clk_sor_brick *to_brick(struct clk_hw *hw)
+static inline struct tegra_clk_sor_pad *to_pad(struct clk_hw *hw)
 {
-       return container_of(hw, struct tegra_clk_sor_brick, hw);
+       return container_of(hw, struct tegra_clk_sor_pad, hw);
 }
 
-static const char * const tegra_clk_sor_brick_parents[] = {
+static const char * const tegra_clk_sor_pad_parents[] = {
        "pll_d2_out0", "pll_dp"
 };
 
-static int tegra_clk_sor_brick_set_parent(struct clk_hw *hw, u8 index)
+static int tegra_clk_sor_pad_set_parent(struct clk_hw *hw, u8 index)
 {
-       struct tegra_clk_sor_brick *brick = to_brick(hw);
-       struct tegra_sor *sor = brick->sor;
+       struct tegra_clk_sor_pad *pad = to_pad(hw);
+       struct tegra_sor *sor = pad->sor;
        u32 value;
 
        value = tegra_sor_readl(sor, SOR_CLK_CNTRL);
@@ -304,10 +304,10 @@ static int tegra_clk_sor_brick_set_parent(struct clk_hw *hw, u8 index)
        return 0;
 }
 
-static u8 tegra_clk_sor_brick_get_parent(struct clk_hw *hw)
+static u8 tegra_clk_sor_pad_get_parent(struct clk_hw *hw)
 {
-       struct tegra_clk_sor_brick *brick = to_brick(hw);
-       struct tegra_sor *sor = brick->sor;
+       struct tegra_clk_sor_pad *pad = to_pad(hw);
+       struct tegra_sor *sor = pad->sor;
        u8 parent = U8_MAX;
        u32 value;
 
@@ -328,33 +328,33 @@ static u8 tegra_clk_sor_brick_get_parent(struct clk_hw *hw)
        return parent;
 }
 
-static const struct clk_ops tegra_clk_sor_brick_ops = {
-       .set_parent = tegra_clk_sor_brick_set_parent,
-       .get_parent = tegra_clk_sor_brick_get_parent,
+static const struct clk_ops tegra_clk_sor_pad_ops = {
+       .set_parent = tegra_clk_sor_pad_set_parent,
+       .get_parent = tegra_clk_sor_pad_get_parent,
 };
 
-static struct clk *tegra_clk_sor_brick_register(struct tegra_sor *sor,
-                                               const char *name)
+static struct clk *tegra_clk_sor_pad_register(struct tegra_sor *sor,
+                                             const char *name)
 {
-       struct tegra_clk_sor_brick *brick;
+       struct tegra_clk_sor_pad *pad;
        struct clk_init_data init;
        struct clk *clk;
 
-       brick = devm_kzalloc(sor->dev, sizeof(*brick), GFP_KERNEL);
-       if (!brick)
+       pad = devm_kzalloc(sor->dev, sizeof(*pad), GFP_KERNEL);
+       if (!pad)
                return ERR_PTR(-ENOMEM);
 
-       brick->sor = sor;
+       pad->sor = sor;
 
        init.name = name;
        init.flags = 0;
-       init.parent_names = tegra_clk_sor_brick_parents;
-       init.num_parents = ARRAY_SIZE(tegra_clk_sor_brick_parents);
-       init.ops = &tegra_clk_sor_brick_ops;
+       init.parent_names = tegra_clk_sor_pad_parents;
+       init.num_parents = ARRAY_SIZE(tegra_clk_sor_pad_parents);
+       init.ops = &tegra_clk_sor_pad_ops;
 
-       brick->hw.init = &init;
+       pad->hw.init = &init;
 
-       clk = devm_clk_register(sor->dev, &brick->hw);
+       clk = devm_clk_register(sor->dev, &pad->hw);
 
        return clk;
 }
@@ -998,8 +998,10 @@ static int tegra_sor_power_down(struct tegra_sor *sor)
 
        /* switch to safe parent clock */
        err = tegra_sor_set_parent_clock(sor, sor->clk_safe);
-       if (err < 0)
+       if (err < 0) {
                dev_err(sor->dev, "failed to set safe parent clock: %d\n", err);
+               return err;
+       }
 
        value = tegra_sor_readl(sor, SOR_DP_PADCTL0);
        value &= ~(SOR_DP_PADCTL_PD_TXD_3 | SOR_DP_PADCTL_PD_TXD_0 |
@@ -2007,8 +2009,10 @@ static void tegra_sor_hdmi_enable(struct drm_encoder *encoder)
 
        /* switch to safe parent clock */
        err = tegra_sor_set_parent_clock(sor, sor->clk_safe);
-       if (err < 0)
+       if (err < 0) {
                dev_err(sor->dev, "failed to set safe parent clock: %d\n", err);
+               return;
+       }
 
        div = clk_get_rate(sor->clk) / 1000000 * 4;
 
@@ -2111,13 +2115,17 @@ static void tegra_sor_hdmi_enable(struct drm_encoder *encoder)
        tegra_sor_writel(sor, value, SOR_XBAR_CTRL);
 
        /* switch to parent clock */
-       err = clk_set_parent(sor->clk_src, sor->clk_parent);
-       if (err < 0)
-               dev_err(sor->dev, "failed to set source clock: %d\n", err);
-
-       err = tegra_sor_set_parent_clock(sor, sor->clk_src);
-       if (err < 0)
+       err = clk_set_parent(sor->clk, sor->clk_parent);
+       if (err < 0) {
                dev_err(sor->dev, "failed to set parent clock: %d\n", err);
+               return;
+       }
+
+       err = tegra_sor_set_parent_clock(sor, sor->clk_pad);
+       if (err < 0) {
+               dev_err(sor->dev, "failed to set pad clock: %d\n", err);
+               return;
+       }
 
        value = SOR_INPUT_CONTROL_HDMI_SRC_SELECT(dc->pipe);
 
@@ -2628,11 +2636,24 @@ static int tegra_sor_probe(struct platform_device *pdev)
        }
 
        if (sor->soc->supports_hdmi || sor->soc->supports_dp) {
-               sor->clk_src = devm_clk_get(&pdev->dev, "source");
-               if (IS_ERR(sor->clk_src)) {
-                       err = PTR_ERR(sor->clk_src);
-                       dev_err(sor->dev, "failed to get source clock: %d\n",
-                               err);
+               struct device_node *np = pdev->dev.of_node;
+               const char *name;
+
+               /*
+                * For backwards compatibility with Tegra210 device trees,
+                * fall back to the old clock name "source" if the new "out"
+                * clock is not available.
+                */
+               if (of_property_match_string(np, "clock-names", "out") < 0)
+                       name = "source";
+               else
+                       name = "out";
+
+               sor->clk_out = devm_clk_get(&pdev->dev, name);
+               if (IS_ERR(sor->clk_out)) {
+                       err = PTR_ERR(sor->clk_out);
+                       dev_err(sor->dev, "failed to get %s clock: %d\n",
+                               name, err);
                        goto remove;
                }
        }
@@ -2658,16 +2679,60 @@ static int tegra_sor_probe(struct platform_device *pdev)
                goto remove;
        }
 
+       /*
+        * Starting with Tegra186, the BPMP provides an implementation for
+        * the pad output clock, so we have to look it up from device tree.
+        */
+       sor->clk_pad = devm_clk_get(&pdev->dev, "pad");
+       if (IS_ERR(sor->clk_pad)) {
+               if (sor->clk_pad != ERR_PTR(-ENOENT)) {
+                       err = PTR_ERR(sor->clk_pad);
+                       goto remove;
+               }
+
+               /*
+                * If the pad output clock is not available, then we assume
+                * we're on Tegra210 or earlier and have to provide our own
+                * implementation.
+                */
+               sor->clk_pad = NULL;
+       }
+
+       /*
+        * The bootloader may have set up the SOR such that it's module clock
+        * is sourced by one of the display PLLs. However, that doesn't work
+        * without properly having set up other bits of the SOR.
+        */
+       err = clk_set_parent(sor->clk_out, sor->clk_safe);
+       if (err < 0) {
+               dev_err(&pdev->dev, "failed to use safe clock: %d\n", err);
+               goto remove;
+       }
+
        platform_set_drvdata(pdev, sor);
        pm_runtime_enable(&pdev->dev);
 
-       pm_runtime_get_sync(&pdev->dev);
-       sor->clk_brick = tegra_clk_sor_brick_register(sor, "sor1_brick");
-       pm_runtime_put(&pdev->dev);
+       /*
+        * On Tegra210 and earlier, provide our own implementation for the
+        * pad output clock.
+        */
+       if (!sor->clk_pad) {
+               err = pm_runtime_get_sync(&pdev->dev);
+               if (err < 0) {
+                       dev_err(&pdev->dev, "failed to get runtime PM: %d\n",
+                               err);
+                       goto remove;
+               }
+
+               sor->clk_pad = tegra_clk_sor_pad_register(sor,
+                                                         "sor1_pad_clkout");
+               pm_runtime_put(&pdev->dev);
+       }
 
-       if (IS_ERR(sor->clk_brick)) {
-               err = PTR_ERR(sor->clk_brick);
-               dev_err(&pdev->dev, "failed to register SOR clock: %d\n", err);
+       if (IS_ERR(sor->clk_pad)) {
+               err = PTR_ERR(sor->clk_pad);
+               dev_err(&pdev->dev, "failed to register SOR pad clock: %d\n",
+                       err);
                goto remove;
        }