[PATCH v2 4/4] phy: phy-rockchip-samsung-hdptx: Add clock provider support
Cristian Ciocaltea
cristian.ciocaltea at collabora.com
Wed Jun 19 17:36:25 PDT 2024
The HDMI PHY PLL can be used as an alternative dclk source to RK3588 SoC
CRU. It provides more accurate clock rates required by VOP2 to improve
existing support for display modes handling, which is known to be
problematic when dealing with non-integer refresh rates, among others.
It is worth noting this only works for HDMI 2.0 or below, e.g. cannot be
used to support HDMI 2.1 4K at 120Hz mode.
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea at collabora.com>
---
drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c | 195 +++++++++++++++++++---
1 file changed, 173 insertions(+), 22 deletions(-)
diff --git a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
index 72de287282eb..9f084697dd05 100644
--- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
+++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
@@ -8,6 +8,7 @@
*/
#include <linux/bitfield.h>
#include <linux/clk.h>
+#include <linux/clk-provider.h>
#include <linux/delay.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
@@ -191,6 +192,8 @@
#define LN3_TX_SER_RATE_SEL_HBR2 BIT(3)
#define LN3_TX_SER_RATE_SEL_HBR3 BIT(2)
+#define HDMI20_MAX_RATE 600000000
+
struct lcpll_config {
u32 bit_rate;
u8 lcvco_mode_en;
@@ -273,6 +276,12 @@ struct rk_hdptx_phy {
struct clk_bulk_data *clks;
int nr_clks;
struct reset_control_bulk_data rsts[RST_MAX];
+
+ /* clk provider */
+ struct clk_hw hw;
+ unsigned long rate;
+
+ atomic_t usage_count;
};
static const struct ropll_config ropll_tmds_cfg[] = {
@@ -760,6 +769,8 @@ static int rk_hdptx_ropll_tmds_cmn_config(struct rk_hdptx_phy *hdptx,
struct ropll_config rc = {0};
int i;
+ hdptx->rate = rate * 100;
+
for (i = 0; i < ARRAY_SIZE(ropll_tmds_cfg); i++)
if (rate == ropll_tmds_cfg[i].bit_rate) {
cfg = &ropll_tmds_cfg[i];
@@ -823,19 +834,6 @@ static int rk_hdptx_ropll_tmds_cmn_config(struct rk_hdptx_phy *hdptx,
static int rk_hdptx_ropll_tmds_mode_config(struct rk_hdptx_phy *hdptx,
unsigned int rate)
{
- u32 val;
- int ret;
-
- ret = regmap_read(hdptx->grf, GRF_HDPTX_STATUS, &val);
- if (ret)
- return ret;
-
- if (!(val & HDPTX_O_PLL_LOCK_DONE)) {
- ret = rk_hdptx_ropll_tmds_cmn_config(hdptx, rate);
- if (ret)
- return ret;
- }
-
rk_hdptx_multi_reg_write(hdptx, rk_hdtpx_common_sb_init_seq);
regmap_write(hdptx->regmap, LNTOP_REG(0200), 0x06);
@@ -857,10 +855,68 @@ static int rk_hdptx_ropll_tmds_mode_config(struct rk_hdptx_phy *hdptx,
return rk_hdptx_post_enable_lane(hdptx);
}
+static int rk_hdptx_phy_consumer_get(struct rk_hdptx_phy *hdptx,
+ unsigned int rate)
+{
+ u32 status;
+ int ret;
+
+ if (atomic_inc_return(&hdptx->usage_count) > 1)
+ return 0;
+
+ ret = regmap_read(hdptx->grf, GRF_HDPTX_STATUS, &status);
+ if (ret)
+ goto dec_usage;
+
+ if (status & HDPTX_O_PLL_LOCK_DONE)
+ dev_warn(hdptx->dev, "PLL locked by unknown consumer!\n");
+
+ if (rate) {
+ ret = rk_hdptx_ropll_tmds_cmn_config(hdptx, rate);
+ if (ret)
+ goto dec_usage;
+ }
+
+ return 0;
+
+dec_usage:
+ atomic_dec(&hdptx->usage_count);
+ return ret;
+}
+
+static int rk_hdptx_phy_consumer_put(struct rk_hdptx_phy *hdptx, bool force)
+{
+ u32 status;
+ int ret;
+
+ ret = atomic_dec_return(&hdptx->usage_count);
+ if (ret > 0)
+ return 0;
+
+ if (ret < 0) {
+ dev_warn(hdptx->dev, "Usage count underflow!\n");
+ ret = -EINVAL;
+ } else {
+ ret = regmap_read(hdptx->grf, GRF_HDPTX_STATUS, &status);
+ if (!ret) {
+ if (status & HDPTX_O_PLL_LOCK_DONE)
+ rk_hdptx_phy_disable(hdptx);
+ return 0;
+ } else if (force) {
+ return 0;
+ }
+ }
+
+ atomic_inc(&hdptx->usage_count);
+ return ret;
+}
+
static int rk_hdptx_phy_power_on(struct phy *phy)
{
struct rk_hdptx_phy *hdptx = phy_get_drvdata(phy);
int bus_width = phy_get_bus_width(hdptx->phy);
+ int ret;
+
/*
* FIXME: Temporary workaround to pass pixel_clk_rate
* from the HDMI bridge driver until phy_configure_opts_hdmi
@@ -871,20 +927,22 @@ static int rk_hdptx_phy_power_on(struct phy *phy)
dev_dbg(hdptx->dev, "%s bus_width=%x rate=%u\n",
__func__, bus_width, rate);
- return rk_hdptx_ropll_tmds_mode_config(hdptx, rate);
+ ret = rk_hdptx_phy_consumer_get(hdptx, rate);
+ if (ret)
+ return ret;
+
+ ret = rk_hdptx_ropll_tmds_mode_config(hdptx, rate);
+ if (ret)
+ rk_hdptx_phy_consumer_put(hdptx, true);
+
+ return ret;
}
static int rk_hdptx_phy_power_off(struct phy *phy)
{
struct rk_hdptx_phy *hdptx = phy_get_drvdata(phy);
- u32 val;
- int ret;
- ret = regmap_read(hdptx->grf, GRF_HDPTX_STATUS, &val);
- if (ret == 0 && (val & HDPTX_O_PLL_LOCK_DONE))
- rk_hdptx_phy_disable(hdptx);
-
- return ret;
+ return rk_hdptx_phy_consumer_put(hdptx, false);
}
static const struct phy_ops rk_hdptx_phy_ops = {
@@ -893,6 +951,99 @@ static const struct phy_ops rk_hdptx_phy_ops = {
.owner = THIS_MODULE,
};
+static struct rk_hdptx_phy *to_rk_hdptx_phy(struct clk_hw *hw)
+{
+ return container_of(hw, struct rk_hdptx_phy, hw);
+}
+
+static int rk_hdptx_phy_clk_prepare(struct clk_hw *hw)
+{
+ struct rk_hdptx_phy *hdptx = to_rk_hdptx_phy(hw);
+
+ return rk_hdptx_phy_consumer_get(hdptx, hdptx->rate / 100);
+}
+
+static void rk_hdptx_phy_clk_unprepare(struct clk_hw *hw)
+{
+ struct rk_hdptx_phy *hdptx = to_rk_hdptx_phy(hw);
+
+ rk_hdptx_phy_consumer_put(hdptx, true);
+}
+
+static unsigned long rk_hdptx_phy_clk_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct rk_hdptx_phy *hdptx = to_rk_hdptx_phy(hw);
+
+ return hdptx->rate;
+}
+
+static long rk_hdptx_phy_clk_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ u32 bit_rate = rate / 100;
+ int i;
+
+ if (rate > HDMI20_MAX_RATE)
+ return rate;
+
+ for (i = 0; i < ARRAY_SIZE(ropll_tmds_cfg); i++)
+ if (bit_rate == ropll_tmds_cfg[i].bit_rate)
+ break;
+
+ if (i == ARRAY_SIZE(ropll_tmds_cfg) &&
+ !rk_hdptx_phy_clk_pll_calc(bit_rate, NULL))
+ return -EINVAL;
+
+ return rate;
+}
+
+static int rk_hdptx_phy_clk_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct rk_hdptx_phy *hdptx = to_rk_hdptx_phy(hw);
+
+ return rk_hdptx_ropll_tmds_cmn_config(hdptx, rate / 100);
+}
+
+static const struct clk_ops hdptx_phy_clk_ops = {
+ .prepare = rk_hdptx_phy_clk_prepare,
+ .unprepare = rk_hdptx_phy_clk_unprepare,
+ .recalc_rate = rk_hdptx_phy_clk_recalc_rate,
+ .round_rate = rk_hdptx_phy_clk_round_rate,
+ .set_rate = rk_hdptx_phy_clk_set_rate,
+};
+
+static int rk_hdptx_phy_clk_register(struct rk_hdptx_phy *hdptx)
+{
+ struct device *dev = hdptx->dev;
+ const char *name, *pname;
+ struct clk *refclk;
+ int ret, id;
+
+ refclk = devm_clk_get(dev, "ref");
+ if (IS_ERR(refclk))
+ return dev_err_probe(dev, PTR_ERR(refclk),
+ "Failed to get ref clock\n");
+
+ id = of_alias_get_id(dev->of_node, "hdptxphy");
+ name = id > 0 ? "clk_hdmiphy_pixel1" : "clk_hdmiphy_pixel0";
+ pname = __clk_get_name(refclk);
+
+ hdptx->hw.init = CLK_HW_INIT(name, pname, &hdptx_phy_clk_ops,
+ CLK_GET_RATE_NOCACHE);
+
+ ret = devm_clk_hw_register(dev, &hdptx->hw);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to register clock\n");
+
+ ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, &hdptx->hw);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to register clk provider\n");
+ return 0;
+}
+
static int rk_hdptx_phy_runtime_suspend(struct device *dev)
{
struct rk_hdptx_phy *hdptx = dev_get_drvdata(dev);
@@ -987,7 +1138,7 @@ static int rk_hdptx_phy_probe(struct platform_device *pdev)
reset_control_deassert(hdptx->rsts[RST_CMN].rstc);
reset_control_deassert(hdptx->rsts[RST_INIT].rstc);
- return 0;
+ return rk_hdptx_phy_clk_register(hdptx);
}
static const struct dev_pm_ops rk_hdptx_phy_pm_ops = {
--
2.45.2
More information about the linux-phy
mailing list