Re: [PATCH 11/15] drm/sun4i: Add support for H3 HDMI PHY variant

From: Jernej Åkrabec
Date: Mon Feb 26 2018 - 11:24:27 EST


Hi all,

Dne sobota, 24. februar 2018 ob 22:45:41 CET je Jernej Skrabec napisal(a):
> While A83T HDMI PHY seems to be just customized Synopsys HDMI PHY, H3
> HDMI PHY is completely custom PHY.
>
> However, they still have many things in common like clock and reset
> setup, setting sync polarity and more.
>
> Add support for H3 HDMI PHY variant.
>
> While documentation exists for this PHY variant, it doesn't go in great
> details. Because of that, almost all settings are copied from BSP linux
> 4.4. Interestingly, those settings are slightly different to those found
> in a older BSP with Linux 3.4. For now, no user visible difference was
> found between them.
>
> Signed-off-by: Jernej Skrabec <jernej.skrabec@xxxxxxxx>
> ---
> drivers/gpu/drm/sun4i/Makefile | 1 +
> drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h | 6 +
> drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c | 263
> ++++++++++++++++++++++++++++- drivers/gpu/drm/sun4i/sun8i_hdmi_phy_clk.c |
> 130 ++++++++++++++
> 4 files changed, 397 insertions(+), 3 deletions(-)
> create mode 100644 drivers/gpu/drm/sun4i/sun8i_hdmi_phy_clk.c
>
[...]
> diff --git a/drivers/gpu/drm/sun4i/sun8i_hdmi_phy_clk.c
> b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy_clk.c new file mode 100644
> index 000000000000..3c34ec5ff4af
> --- /dev/null
> +++ b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy_clk.c
> @@ -0,0 +1,130 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright (C) 2018 Jernej Skrabec <jernej.skrabec@xxxxxxxx>
> + */
> +
> +#include <linux/clk-provider.h>
> +
> +#include "sun8i_dw_hdmi.h"
> +
> +struct sun8i_phy_clk {
> + struct clk_hw hw;
> + struct sun8i_hdmi_phy *phy;
> +};
> +
> +static inline struct sun8i_phy_clk *hw_to_phy_clk(struct clk_hw *hw)
> +{
> + return container_of(hw, struct sun8i_phy_clk, hw);
> +}
> +
> +static int sun8i_phy_clk_determine_rate(struct clk_hw *hw,
> + struct clk_rate_request *req)
> +{
> + unsigned long rate = req->rate;
> + unsigned long best_rate = 0;
> + struct clk_hw *parent;
> + int best_div = 1;
> + int i;
> +
> + parent = clk_hw_get_parent(hw);
> +
> + for (i = 1; i <= 16; i++) {
> + unsigned long ideal = rate * i;
> + unsigned long rounded;
> +
> + rounded = clk_hw_round_rate(parent, ideal);
> +
> + if (rounded == ideal) {
> + best_rate = rounded;
> + best_div = i;
> + break;
> + }
> +
> + if (abs(rate - rounded) < abs(rate - best_rate / best_div)) {

Here is a bug. Above line should be:
if (abs(rate - rounded / i) < abs(rate - best_rate / best_div)) {

I guess this could solve the issue described in cover letter.

Best regards,
Jernej

> + best_rate = rounded;
> + best_div = i;
> + }
> + }
> +
> + req->rate = best_rate / best_div;
> + req->best_parent_rate = best_rate;
> + req->best_parent_hw = parent;
> +
> + return 0;
> +}
> +
> +static unsigned long sun8i_phy_clk_recalc_rate(struct clk_hw *hw,
> + unsigned long parent_rate)
> +{
> + struct sun8i_phy_clk *priv = hw_to_phy_clk(hw);
> + u32 reg;
> +
> + regmap_read(priv->phy->regs, SUN8I_HDMI_PHY_PLL_CFG2_REG, &reg);
> + reg = ((reg >> SUN8I_HDMI_PHY_PLL_CFG2_PREDIV_SHIFT) &
> + SUN8I_HDMI_PHY_PLL_CFG2_PREDIV_MSK) + 1;
> +
> + return parent_rate / reg;
> +}
> +
> +static int sun8i_phy_clk_set_rate(struct clk_hw *hw, unsigned long rate,
> + unsigned long parent_rate)
> +{
> + struct sun8i_phy_clk *priv = hw_to_phy_clk(hw);
> + unsigned long best_rate = 0;
> + u8 best_m = 0, m;
> +
> + for (m = 1; m <= 16; m++) {
> + unsigned long tmp_rate = parent_rate / m;
> +
> + if (tmp_rate > rate)
> + continue;
> +
> + if (!best_rate ||
> + (rate - tmp_rate) < (rate - best_rate)) {
> + best_rate = tmp_rate;
> + best_m = m;
> + }
> + }
> +
> + regmap_update_bits(priv->phy->regs, SUN8I_HDMI_PHY_PLL_CFG2_REG,
> + SUN8I_HDMI_PHY_PLL_CFG2_PREDIV_MSK,
> + SUN8I_HDMI_PHY_PLL_CFG2_PREDIV(best_m));
> +
> + return 0;
> +}
> +
> +static const struct clk_ops sun8i_phy_clk_ops = {
> + .determine_rate = sun8i_phy_clk_determine_rate,
> + .recalc_rate = sun8i_phy_clk_recalc_rate,
> + .set_rate = sun8i_phy_clk_set_rate,
> +};
> +
> +int sun8i_phy_clk_create(struct sun8i_hdmi_phy *phy, struct device *dev)
> +{
> + struct clk_init_data init;
> + struct sun8i_phy_clk *priv;
> + const char *parents[1];
> +
> + parents[0] = __clk_get_name(phy->clk_pll0);
> + if (!parents[0])
> + return -ENODEV;
> +
> + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> + if (!priv)
> + return -ENOMEM;
> +
> + init.name = "hdmi-phy-clk";
> + init.ops = &sun8i_phy_clk_ops;
> + init.parent_names = parents;
> + init.num_parents = 1;
> + init.flags = CLK_SET_RATE_PARENT;
> +
> + priv->phy = phy;
> + priv->hw.init = &init;
> +
> + phy->clk_phy = devm_clk_register(dev, &priv->hw);
> + if (IS_ERR(phy->clk_phy))
> + return PTR_ERR(phy->clk_phy);
> +
> + return 0;
> +}
> --
> 2.16.2