[Freedreno] [PATCH v6 4/5] drm/msm/dp: add support for DP PLL driver
tanmay at codeaurora.org
tanmay at codeaurora.org
Thu Jun 11 20:31:30 UTC 2020
Hi Stephen,
Thanks for reviews.
Please ignore previous response to this patch. Here, I have re-organized
it.
Thanks,
On 2020-06-11 13:07, tanmay at codeaurora.org wrote:
> On 2020-06-09 19:06, Stephen Boyd wrote:
>> 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.
>>
> Sure. I will remove it as part of DP base driver patch.
>>> 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.
>>
>
> As mentioned in above diagram, there is an additional divider in the
> PLL after the VCO (vco_divided_clk_src).
> The input rate to the dispcc branch is (vco_rate * 10)/
> vco_dividied_clk_src.
> In order to know the MNDs at the dispcc, we need to know the input
> rate.
> This register read is to figure out which divider value is currently
> being set in the PLL.
> This input rate is not the same as the link rate. When we move the
> PHY/PLL to a separate driver,
> we would have take care of finding a different way to get this input
> rate.
>>> + 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.
>>
> Yes. currently it is included in dp_parser.h but I will include here as
> well.
>>> +
>>> +#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.
>>
> Sure. Use of clk_set_parent is redundant here and I will remove it.
>>> + 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?
>>
> Thats right. I will remove dp_phy_pll_vco_div_clk from parent list
> disp_cc_parent_data_0.
>
>>> + .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?
>>
> No it is redundant. I will remove.
>>> + 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.
>>
This will be taken care at later point of time.
>>> +
>>> + 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?
>>
> No it is redundant and will be removed in next patch.
>>> +
>>> + 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;
>>> +}
>> _______________________________________________
>> dri-devel mailing list
>> dri-devel at lists.freedesktop.org
>> https://lists.freedesktop.org/mailman/listinfo/dri-devel
> _______________________________________________
> dri-devel mailing list
> dri-devel at lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel
More information about the Freedreno
mailing list