clk: renesas: rzv2h: Add PLLDSI clk mux support

Add PLLDSI clk mux support to select PLLDSI clock from different clock
sources.

Introduce the DEF_PLLDSI_SMUX() macro to define these muxes and register
them in the clock driver.

Extend the determine_rate callback to calculate and propagate PLL
parameters via rzv2h_get_pll_dtable_pars() when LVDS output is selected,
using a new helper function rzv2h_cpg_plldsi_smux_lvds_determine_rate().

The CLK_SMUX2_DSI{0,1}_CLK clock multiplexers select between two paths
with different duty cycles:

- CDIV7_DSIx_CLK (LVDS path, parent index 0): asymmetric H/L=4/3 duty (4/7)
- CSDIV_DSIx (DSI/RGB path, parent index 1): symmetric 50% duty (1/2)

Implement rzv2h_cpg_plldsi_smux_{get,set}_duty_cycle clock operations to
allow the DRM driver to query and configure the appropriate clock path
based on the required output duty cycle.

Signed-off-by: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
Reviewed-by: Geert Uytterhoeven <geert+renesas@glider.be>
Link: https://patch.msgid.link/24d3853ca2522df21e6a071a23e23ba4ca4b7276.1775636898.git.tommaso.merciai.xr@bp.renesas.com
Signed-off-by: Geert Uytterhoeven <geert+renesas@glider.be>
This commit is contained in:
Tommaso Merciai 2026-04-08 12:36:46 +02:00 committed by Geert Uytterhoeven
parent 254f49634e
commit 3afccee4a3
2 changed files with 189 additions and 0 deletions

View File

@ -76,6 +76,11 @@
/* On RZ/G3E SoC we have two DSI PLLs */
#define MAX_CPG_DSI_PLL 2
#define CPG_PLLDSI_SMUX_LVDS_DUTY_NUM 4
#define CPG_PLLDSI_SMUX_LVDS_DUTY_DEN 7
#define CPG_PLLDSI_SMUX_DSI_RGB_DUTY_NUM 1
#define CPG_PLLDSI_SMUX_DSI_RGB_DUTY_DEN 2
/**
* struct rzv2h_pll_dsi_info - PLL DSI information, holds the limits and parameters
*
@ -418,6 +423,20 @@ bool rzv2h_get_pll_divs_pars(const struct rzv2h_pll_limits *limits,
}
EXPORT_SYMBOL_NS_GPL(rzv2h_get_pll_divs_pars, "RZV2H_CPG");
/**
* struct rzv2h_plldsi_mux_clk - PLL DSI MUX clock
*
* @priv: CPG private data
* @mux: mux clk
*/
struct rzv2h_plldsi_mux_clk {
struct rzv2h_cpg_priv *priv;
struct clk_mux mux;
};
#define to_plldsi_clk_mux(_mux) \
container_of(_mux, struct rzv2h_plldsi_mux_clk, mux)
static unsigned long rzv2h_cpg_plldsi_div_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
@ -649,6 +668,165 @@ static int rzv2h_cpg_plldsi_set_rate(struct clk_hw *hw, unsigned long rate,
return rzv2h_cpg_pll_set_rate(pll_clk, &dsi_info->pll_dsi_parameters.pll, true);
}
static u8 rzv2h_cpg_plldsi_smux_get_parent(struct clk_hw *hw)
{
return clk_mux_ops.get_parent(hw);
}
static int rzv2h_cpg_plldsi_smux_set_parent(struct clk_hw *hw, u8 index)
{
return clk_mux_ops.set_parent(hw, index);
}
static int rzv2h_cpg_plldsi_smux_lvds_determine_rate(struct rzv2h_cpg_priv *priv,
struct pll_clk *pll_clk,
struct clk_rate_request *req)
{
struct rzv2h_pll_div_pars *dsi_params;
struct rzv2h_pll_dsi_info *dsi_info;
u8 lvds_table[] = { 7 };
u64 rate_millihz;
dsi_info = &priv->pll_dsi_info[pll_clk->pll.instance];
dsi_params = &dsi_info->pll_dsi_parameters;
rate_millihz = mul_u32_u32(req->rate, MILLI);
if (!rzv2h_get_pll_divs_pars(dsi_info->pll_dsi_limits, dsi_params,
lvds_table, ARRAY_SIZE(lvds_table), rate_millihz)) {
dev_err(priv->dev, "failed to determine rate for req->rate: %lu\n",
req->rate);
return -EINVAL;
}
req->rate = DIV_ROUND_CLOSEST_ULL(dsi_params->div.freq_millihz, MILLI);
req->best_parent_rate = req->rate;
dsi_info->req_pll_dsi_rate = req->best_parent_rate * dsi_params->div.divider_value;
return 0;
}
static int rzv2h_cpg_plldsi_smux_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
struct clk_mux *mux = to_clk_mux(hw);
struct rzv2h_plldsi_mux_clk *dsi_mux = to_plldsi_clk_mux(mux);
struct pll_clk *pll_clk = to_pll(clk_hw_get_parent(hw));
struct rzv2h_cpg_priv *priv = dsi_mux->priv;
/*
* For LVDS output (parent index 0), calculate PLL parameters with
* fixed divider value of 7. For DSI/RGB output (parent index 1) skip
* PLL calculation here as it's handled by determine_rate of the
* divider (up one level).
*/
if (!clk_mux_ops.get_parent(hw))
return rzv2h_cpg_plldsi_smux_lvds_determine_rate(priv, pll_clk, req);
req->best_parent_rate = req->rate;
return 0;
}
static int rzv2h_cpg_plldsi_smux_get_duty_cycle(struct clk_hw *hw,
struct clk_duty *duty)
{
u8 parent = clk_mux_ops.get_parent(hw);
/*
* CDIV7_DSIx_CLK - LVDS path (div7) - duty 4/7.
* CSDIV_DSIx - DSI/RGB path (csdiv) - duty 1/2.
*/
if (parent == 0) {
duty->num = CPG_PLLDSI_SMUX_LVDS_DUTY_NUM;
duty->den = CPG_PLLDSI_SMUX_LVDS_DUTY_DEN;
} else {
duty->num = CPG_PLLDSI_SMUX_DSI_RGB_DUTY_NUM;
duty->den = CPG_PLLDSI_SMUX_DSI_RGB_DUTY_DEN;
}
return 0;
}
static int rzv2h_cpg_plldsi_smux_set_duty_cycle(struct clk_hw *hw,
struct clk_duty *duty)
{
struct clk_hw *parent_hw;
u8 parent_idx;
/*
* Select parent based on requested duty cycle:
* - If duty > 50% (num/den > 1/2), select LVDS path (parent 0)
* - Otherwise, select DSI/RGB path (parent 1)
*/
if (duty->num * CPG_PLLDSI_SMUX_DSI_RGB_DUTY_DEN >
duty->den * CPG_PLLDSI_SMUX_DSI_RGB_DUTY_NUM)
parent_idx = 0;
else
parent_idx = 1;
if (parent_idx >= clk_hw_get_num_parents(hw))
return -EINVAL;
parent_hw = clk_hw_get_parent_by_index(hw, parent_idx);
if (!parent_hw)
return -EINVAL;
return clk_hw_set_parent(hw, parent_hw);
}
static const struct clk_ops rzv2h_cpg_plldsi_smux_ops = {
.determine_rate = rzv2h_cpg_plldsi_smux_determine_rate,
.get_parent = rzv2h_cpg_plldsi_smux_get_parent,
.set_parent = rzv2h_cpg_plldsi_smux_set_parent,
.get_duty_cycle = rzv2h_cpg_plldsi_smux_get_duty_cycle,
.set_duty_cycle = rzv2h_cpg_plldsi_smux_set_duty_cycle,
};
static struct clk * __init
rzv2h_cpg_plldsi_smux_clk_register(const struct cpg_core_clk *core,
struct rzv2h_cpg_priv *priv)
{
struct rzv2h_plldsi_mux_clk *clk_hw_data;
struct clk_init_data init;
struct clk_hw *clk_hw;
struct smuxed smux;
int ret;
smux = core->cfg.smux;
if (smux.shift + smux.width > 16) {
dev_err(priv->dev, "mux value exceeds LOWORD field\n");
return ERR_PTR(-EINVAL);
}
clk_hw_data = devm_kzalloc(priv->dev, sizeof(*clk_hw_data), GFP_KERNEL);
if (!clk_hw_data)
return ERR_PTR(-ENOMEM);
clk_hw_data->priv = priv;
init.name = core->name;
init.ops = &rzv2h_cpg_plldsi_smux_ops;
init.flags = core->flag;
init.parent_names = core->parent_names;
init.num_parents = core->num_parents;
clk_hw_data->mux.reg = priv->base + smux.offset;
clk_hw_data->mux.shift = smux.shift;
clk_hw_data->mux.mask = clk_div_mask(smux.width);
clk_hw_data->mux.flags = core->mux_flags;
clk_hw_data->mux.lock = &priv->rmw_lock;
clk_hw = &clk_hw_data->mux.hw;
clk_hw->init = &init;
ret = devm_clk_hw_register(priv->dev, clk_hw);
if (ret)
return ERR_PTR(ret);
return clk_hw->clk;
}
static int rzv2h_cpg_pll_clk_is_enabled(struct clk_hw *hw)
{
struct pll_clk *pll_clk = to_pll(hw);
@ -1085,6 +1263,9 @@ rzv2h_cpg_register_core_clk(const struct cpg_core_clk *core,
case CLK_TYPE_PLLDSI_DIV:
clk = rzv2h_cpg_plldsi_div_clk_register(core, priv);
break;
case CLK_TYPE_PLLDSI_SMUX:
clk = rzv2h_cpg_plldsi_smux_clk_register(core, priv);
break;
default:
goto fail;
}

View File

@ -203,6 +203,7 @@ enum clk_types {
CLK_TYPE_SMUX, /* Static Mux */
CLK_TYPE_PLLDSI, /* PLLDSI */
CLK_TYPE_PLLDSI_DIV, /* PLLDSI divider */
CLK_TYPE_PLLDSI_SMUX, /* PLLDSI Static Mux */
};
#define DEF_TYPE(_name, _id, _type...) \
@ -241,6 +242,13 @@ enum clk_types {
.dtable = _dtable, \
.parent = _parent, \
.flag = CLK_SET_RATE_PARENT)
#define DEF_PLLDSI_SMUX(_name, _id, _smux_packed, _parent_names) \
DEF_TYPE(_name, _id, CLK_TYPE_PLLDSI_SMUX, \
.cfg.smux = _smux_packed, \
.parent_names = _parent_names, \
.num_parents = ARRAY_SIZE(_parent_names), \
.flag = CLK_SET_RATE_PARENT, \
.mux_flags = CLK_MUX_HIWORD_MASK)
/**
* struct rzv2h_mod_clk - Module Clocks definitions