Re: [PATCH v6 4/5] drm/msm/dp: add support for DP PLL driver

Stephen Boyd
2020-06-10T02:07:06Z

Quoting Tanmay Shah (2020-06-08 20:46:23)
> diff --git a/drivers/gpu/drm/msm/dp/dp_catalog.c b/drivers/gpu/drm/msm/dp/dp_catalog.c
> index d02f4eb..2b982f0 100644
> --- a/drivers/gpu/drm/msm/dp/dp_catalog.c
> +++ b/drivers/gpu/drm/msm/dp/dp_catalog.c
> @@ -5,6 +5,7 @@
>
> #define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__
>
> +#include <linux/rational.h>
> #include <linux/delay.h>
> #include <linux/iopoll.h>
> #include <drm/drm_dp_helper.h>
> @@ -134,59 +135,61 @@ static inline void dp_write_ahb(struct dp_catalog_private *catalog,
> writel(data, catalog->io->dp_controller.base + offset);
> }
>
> -static inline u32 dp_read_cc(struct dp_catalog_private *catalog, u32 offset)
> -{
> - return readl_relaxed(catalog->io->dp_cc_io.base + offset);
> -}
> -

Why was this added in the first place? Remove it from the place it came
in please.

> static inline void dp_write_phy(struct dp_catalog_private *catalog,
> u32 offset, u32 data)
> {
> + offset += DP_PHY_REG_OFFSET;
> /*
> * To make sure phy reg writes happens before any other operation,
[...]
> @@ -568,17 +574,37 @@ void dp_catalog_ctrl_config_msa(struct dp_catalog *dp_catalog,
> bool fixed_nvid)
> {
> u32 pixel_m, pixel_n;
> - u32 mvid, nvid;
> + u32 mvid, nvid, div, pixel_div = 0, dispcc_input_rate;
> u32 const nvid_fixed = DP_LINK_CONSTANT_N_VALUE;
> u32 const link_rate_hbr2 = 540000;
> u32 const link_rate_hbr3 = 810000;
> + unsigned long den, num;
>
> struct dp_catalog_private *catalog = container_of(dp_catalog,
> struct dp_catalog_private, dp_catalog);
>
> - pixel_m = dp_read_cc(catalog, MMSS_DP_PIXEL_M);
> - pixel_n = dp_read_cc(catalog, MMSS_DP_PIXEL_N);
> - DRM_DEBUG_DP("pixel_m=0x%x, pixel_n=0x%x\n", pixel_m, pixel_n);
> + div = dp_read_phy(catalog, REG_DP_PHY_VCO_DIV);

Why do we need to read the phy? The pixel_div seems to match what the
clk driver is doing so presumably we can make this follow the link rate
being used vs. having to read the phy.

> + div &= 0x03;
> +
> + if (div == 0)
> + pixel_div = 6;
> + else if (div == 1)
> + pixel_div = 2;
> + else if (div == 2)
> + pixel_div = 4;
> + else
> + DRM_ERROR("Invalid pixel mux divider\n");
> +
> + dispcc_input_rate = (rate * 10) / pixel_div;
> +
> + rational_best_approximation(dispcc_input_rate, stream_rate_khz,
> + (unsigned long)(1 << 16) - 1,
> + (unsigned long)(1 << 16) - 1, &den, &num);
> +
> + den = ~(den - num);
> + den = den & 0xFFFF;
> + pixel_m = num;
> + pixel_n = den;
>
> mvid = (pixel_m & 0xFFFF) * 5;
> nvid = (0xFFFF & (~pixel_n)) + (pixel_m & 0xFFFF);
> diff --git a/drivers/gpu/drm/msm/dp/dp_pll_10nm.c b/drivers/gpu/drm/msm/dp/dp_pll_10nm.c
> new file mode 100644
> index 0000000..998d659
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/dp/dp_pll_10nm.c
> @@ -0,0 +1,903 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (c) 2016-2020, The Linux Foundation. All rights reserved.
> + */
> +
> +/*
> + * Display Port PLL driver block diagram for branch clocks
> + *
> + * +------------------------------+
> + * | DP_VCO_CLK |
> + * | |
> + * | +-------------------+ |
> + * | | (DP PLL/VCO) | |
> + * | +---------+---------+ |
> + * | v |
> + * | +----------+-----------+ |
> + * | | hsclk_divsel_clk_src | |
> + * | +----------+-----------+ |
> + * +------------------------------+
> + * |
> + * +---------<---------v------------>----------+
> + * | |
> + * +--------v---------+ |
> + * | dp_phy_pll | |
> + * | link_clk | |
> + * +--------+---------+ |
> + * | |
> + * | |
> + * v v
> + * Input to DISPCC block |
> + * for link clk, crypto clk |
> + * and interface clock |
> + * |
> + * |
> + * +--------<------------+-----------------+---<---+
> + * | | |
> + * +----v---------+ +--------v-----+ +--------v------+
> + * | vco_divided | | vco_divided | | vco_divided |
> + * | _clk_src | | _clk_src | | _clk_src |
> + * | | | | | |
> + * |divsel_six | | divsel_two | | divsel_four |
> + * +-------+------+ +-----+--------+ +--------+------+
> + * | | |
> + * v---->----------v-------------<------v
> + * |
> + * +----------+---------+
> + * | dp_phy_pll_vco |
> + * | div_clk |
> + * +---------+----------+
> + * |
> + * v
> + * Input to DISPCC block
> + * for DP pixel clock
> + *
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/kernel.h>
> +#include <linux/regmap.h>
> +#include <linux/iopoll.h>

Should be a clk-provider.h include here given that this is providing
clks.

> +
> +#include "dp_hpd.h"
> +#include "dp_pll.h"
> +#include "dp_pll_private.h"
> +
> +#define NUM_PROVIDED_CLKS 2
> +
> +#define DP_LINK_CLK_SRC 0
> +#define DP_PIXEL_CLK_SRC 1
> +
> +static struct dp_pll_db *dp_pdb;
> +
> +static const struct clk_ops dp_10nm_vco_clk_ops = {
> + .recalc_rate = dp_vco_recalc_rate_10nm,
> + .set_rate = dp_vco_set_rate_10nm,
> + .round_rate = dp_vco_round_rate_10nm,
> + .prepare = dp_vco_prepare_10nm,
> + .unprepare = dp_vco_unprepare_10nm,
> +};
> +
> +struct dp_pll_10nm_pclksel {
> + struct clk_hw hw;
> +
> + /* divider params */
> + u8 shift;
> + u8 width;
> + u8 flags; /* same flags as used by clk_divider struct */
> +
> + struct dp_pll_db *pll;
> +};
> +
> +#define to_pll_10nm_pclksel(_hw) \
> + container_of(_hw, struct dp_pll_10nm_pclksel, hw)
> +
> +static const struct clk_parent_data disp_cc_parent_data_0[] = {
> + { .fw_name = "bi_tcxo" },
> + { .fw_name = "dp_phy_pll_link_clk", .name = "dp_phy_pll_link_clk" },
> + { .fw_name = "dp_phy_pll_vco_div_clk",
> + .name = "dp_phy_pll_vco_div_clk"},
> + { .fw_name = "core_bi_pll_test_se", .name = "core_bi_pll_test_se" },
> +};
> +
> +static struct dp_pll_vco_clk dp_vco_clk = {
> + .min_rate = DP_VCO_HSCLK_RATE_1620MHZDIV1000,
> + .max_rate = DP_VCO_HSCLK_RATE_8100MHZDIV1000,
> +};
> +
> +static int dp_pll_mux_set_parent_10nm(struct clk_hw *hw, u8 val)
> +{
> + struct dp_pll_10nm_pclksel *pclksel = to_pll_10nm_pclksel(hw);
> + struct dp_pll_db *dp_res = pclksel->pll;
> + struct dp_io_pll *pll_io = &dp_res->base->pll_io;
> + u32 auxclk_div;
> +
> + auxclk_div = PLL_REG_R(pll_io->phy_base, REG_DP_PHY_VCO_DIV);
> + auxclk_div &= ~0x03;
> +
> + if (val == 0)
> + auxclk_div |= 1;
> + else if (val == 1)
> + auxclk_div |= 2;
> + else if (val == 2)
> + auxclk_div |= 0;
> +
> + PLL_REG_W(pll_io->phy_base,
> + REG_DP_PHY_VCO_DIV, auxclk_div);
> + DRM_DEBUG_DP("%s: mux=%d auxclk_div=%x\n", __func__, val, auxclk_div);
> +
> + return 0;
> +}
> +
> +static u8 dp_pll_mux_get_parent_10nm(struct clk_hw *hw)
> +{
> + u32 auxclk_div = 0;
> + struct dp_pll_10nm_pclksel *pclksel = to_pll_10nm_pclksel(hw);
> + struct dp_pll_db *dp_res = pclksel->pll;
> + struct dp_io_pll *pll_io = &dp_res->base->pll_io;
> + u8 val = 0;
> +
> + auxclk_div = PLL_REG_R(pll_io->phy_base, REG_DP_PHY_VCO_DIV);
> + auxclk_div &= 0x03;
> +
> + if (auxclk_div == 1) /* Default divider */
> + val = 0;
> + else if (auxclk_div == 2)
> + val = 1;
> + else if (auxclk_div == 0)
> + val = 2;
> +
> + DRM_DEBUG_DP("%s: auxclk_div=%d, val=%d\n", __func__, auxclk_div, val);
> +
> + return val;
> +}
> +
> +static int dp_pll_clk_mux_determine_rate(struct clk_hw *hw,
> + struct clk_rate_request *req)
> +{
> + unsigned long rate = 0;
> + int ret = 0;
> +
> + rate = clk_get_rate(hw->clk);
> +
> + if (rate <= 0) {
> + DRM_ERROR("Rate is not set properly\n");
> + return -EINVAL;
> + }
> +
> + req->rate = rate;
> +
> + DRM_DEBUG_DP("%s: rate=%ld\n", __func__, req->rate);
> + /* Set the new parent of mux if there is a new valid parent */
> + if (hw->clk && req->best_parent_hw->clk) {
> + ret = clk_set_parent(hw->clk, req->best_parent_hw->clk);

Why do we need to call clk consumer APIs from the clk provider ops? This
is pretty confusing what's going on here.

> + if (ret) {
> + DRM_ERROR("%s: clk_set_parent failed: ret=%d\n",
> + __func__, ret);
> + return ret;
> + }
> + }
> + return 0;
> +}
> +
> +static unsigned long dp_pll_mux_recalc_rate(struct clk_hw *hw,
> + unsigned long parent_rate)
> +{
> + struct clk_hw *div_clk_hw = NULL, *vco_clk_hw = NULL;
> + struct dp_pll_vco_clk *vco;
> +
> + div_clk_hw = clk_hw_get_parent(hw);
> + if (!div_clk_hw)
> + return 0;
> +
> + vco_clk_hw = clk_hw_get_parent(div_clk_hw);
> + if (!vco_clk_hw)
> + return 0;
> +
> + vco = to_dp_vco_hw(vco_clk_hw);
> + if (!vco)
> + return 0;
> +
> + if (vco->rate == DP_VCO_HSCLK_RATE_8100MHZDIV1000)
> + return (vco->rate / 6);
> + else if (vco->rate == DP_VCO_HSCLK_RATE_5400MHZDIV1000)
> + return (vco->rate / 4);
> + else
> + return (vco->rate / 2);
> +}
> +
> +static int dp_pll_10nm_get_provider(struct msm_dp_pll *pll,
> + struct clk **link_clk_provider,
> + struct clk **pixel_clk_provider)
> +{
> + struct clk_hw_onecell_data *hw_data = pll->hw_data;
> +
> + if (link_clk_provider)
> + *link_clk_provider = hw_data->hws[DP_LINK_CLK_SRC]->clk;
> + if (pixel_clk_provider)
> + *pixel_clk_provider = hw_data->hws[DP_PIXEL_CLK_SRC]->clk;
> +
> + return 0;
> +}
> +
> +static const struct clk_ops dp_10nm_pclksel_clk_ops = {
> + .get_parent = dp_pll_mux_get_parent_10nm,
> + .set_parent = dp_pll_mux_set_parent_10nm,
> + .recalc_rate = dp_pll_mux_recalc_rate,
> + .determine_rate = dp_pll_clk_mux_determine_rate,
> +};
> +
> +static struct clk_hw *dp_pll_10nm_pixel_clk_sel(struct dp_pll_db *pll_10nm)
> +{
> + struct device *dev = &pll_10nm->pdev->dev;
> + struct dp_pll_10nm_pclksel *pll_pclksel;
> + struct clk_init_data pclksel_init = {
> + .parent_data = disp_cc_parent_data_0,
> + .num_parents = 3,
> + .name = "dp_phy_pll_vco_div_clk",

So the dp_phy_pll_vco_div_clk has a potential parent that is
dp_phy_pll_vco_div_clk. Huh?

> + .ops = &dp_10nm_pclksel_clk_ops,
> + };
> + int ret;
> +
> + pll_pclksel = devm_kzalloc(dev, sizeof(*pll_pclksel), GFP_KERNEL);
> + if (!pll_pclksel)
> + return ERR_PTR(-ENOMEM);
> +
> + pll_pclksel->pll = pll_10nm;
> + pll_pclksel->shift = 0;
> + pll_pclksel->width = 4;
> + pll_pclksel->flags = CLK_DIVIDER_ONE_BASED;

Is this flag used?

> + pll_pclksel->hw.init = &pclksel_init;
> +
> + ret = clk_hw_register(dev, &pll_pclksel->hw);
> + if (ret)
> + return ERR_PTR(ret);
> +
> + return &pll_pclksel->hw;
> +}
> +
> +static int dp_pll_10nm_register(struct dp_pll_db *pll_10nm)
> +{
> + struct clk_hw_onecell_data *hw_data;
> + int ret;
> + struct clk_hw *hw;
> +
> + struct msm_dp_pll *pll = pll_10nm->base;
> + struct device *dev = &pll_10nm->pdev->dev;
> + struct clk_hw **hws = pll_10nm->hws;
> + int num = 0;
> + struct clk_init_data vco_init = {
> + .parent_data = &(const struct clk_parent_data){
> + .fw_name = "bi_tcxo",
> + },
> + .num_parents = 1,
> + .name = "dp_vco_clk",
> + .ops = &dp_10nm_vco_clk_ops,
> + };

I thought the plan was to not have a vco clk? Just expose the two clks
for the link and the vco divider. Furthermore, drop the divider
"parents" and implement a single clk that programs the right divider
value for the various link rates chosen.

> +
> + DRM_DEBUG_DP("DP->id = %d", pll_10nm->id);
> +
> + hw_data = devm_kzalloc(dev, sizeof(*hw_data) +
> + NUM_PROVIDED_CLKS * sizeof(struct clk_hw *),
> + GFP_KERNEL);
> + if (!hw_data)
> + return -ENOMEM;
> +
> + dp_vco_clk.hw.init = &vco_init;
> + ret = clk_hw_register(dev, &dp_vco_clk.hw);
> + if (ret)
> + return ret;
> + hws[num++] = &dp_vco_clk.hw;
> +
> + hw = clk_hw_register_fixed_factor(dev, "dp_phy_pll_link_clk",
> + "dp_vco_clk", CLK_SET_RATE_PARENT, 1, 10);
> +
> + if (IS_ERR(hw))
> + return PTR_ERR(hw);
> + hws[num++] = hw;
> + hw_data->hws[DP_LINK_CLK_SRC] = hw;
> +
> + hw = clk_hw_register_fixed_factor(dev, "dp_vco_divsel_two_clk_src",
> + "dp_vco_clk", 0, 1, 2);
> + if (IS_ERR(hw))
> + return PTR_ERR(hw);
> + hws[num++] = hw;
> +
> + hw = clk_hw_register_fixed_factor(dev, "dp_vco_divsel_four_clk_src",
> + "dp_vco_clk", 0, 1, 4);
> + if (IS_ERR(hw))
> + return PTR_ERR(hw);
> + hws[num++] = hw;
> +
> + hw = clk_hw_register_fixed_factor(dev, "dp_vco_divsel_six_clk_src",
> + "dp_vco_clk", 0, 1, 6);
> + if (IS_ERR(hw))
> + return PTR_ERR(hw);
> + hws[num++] = hw;
> +
> + hw = dp_pll_10nm_pixel_clk_sel(pll_10nm);
> + if (IS_ERR(hw))
> + return PTR_ERR(hw);
> +
> + hws[num++] = hw;
> + hw_data->hws[DP_PIXEL_CLK_SRC] = hw;
> +
> + pll_10nm->num_hws = num;
> +
> + hw_data->num = NUM_PROVIDED_CLKS;
> + pll->hw_data = hw_data;
> +
> + ret = of_clk_add_hw_provider(dev->of_node, of_clk_hw_onecell_get,
> + pll->hw_data);
> + if (ret) {
> + DRM_DEV_ERROR(dev, "failed to register clk provider: %d\n",
> + ret);
> + return ret;
> + }
> +
> + return ret;
> +}
> +
> +int msm_dp_pll_10nm_init(struct msm_dp_pll *pll, int id)
> +{
> + struct dp_pll_db *dp_10nm_pll;
> + struct platform_device *pdev = pll->pdev;
> + int ret;
> +
> + dp_10nm_pll = devm_kzalloc(&pdev->dev,
> + sizeof(*dp_10nm_pll), GFP_KERNEL);
> + if (!dp_10nm_pll)
> + return -ENOMEM;
> +
> + DRM_DEBUG_DP("DP PLL%d", id);
> +
> + dp_10nm_pll->base = pll;
> + dp_10nm_pll->pdev = pll->pdev;
> + dp_10nm_pll->id = id;
> + dp_pdb = dp_10nm_pll;
> + pll->priv = (void *)dp_10nm_pll;
> + dp_vco_clk.priv = pll;
> +
> + ret = of_property_read_u32(pdev->dev.of_node, "cell-index",
> + &dp_10nm_pll->index);
> + if (ret) {
> + DRM_ERROR("Unable to get the cell-index ret=%d\n", ret);
> + dp_10nm_pll->index = 0;
> + }

Is the cell-index used for anything?

> +
> + ret = dp_pll_10nm_register(dp_10nm_pll);
> + if (ret) {
> + DRM_DEV_ERROR(&pdev->dev, "failed to register PLL: %d\n", ret);
> + return ret;
> + }
> +
> + pll->get_provider = dp_pll_10nm_get_provider;
> +
> + return ret;
> +}