summaryrefslogtreecommitdiff
path: root/drivers/clk/renesas/rzg2l-cpg.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/clk/renesas/rzg2l-cpg.c')
-rw-r--r--drivers/clk/renesas/rzg2l-cpg.c189
1 files changed, 151 insertions, 38 deletions
diff --git a/drivers/clk/renesas/rzg2l-cpg.c b/drivers/clk/renesas/rzg2l-cpg.c
index 64d1ef6e4c94..c0584bab58a3 100644
--- a/drivers/clk/renesas/rzg2l-cpg.c
+++ b/drivers/clk/renesas/rzg2l-cpg.c
@@ -22,6 +22,7 @@
#include <linux/device.h>
#include <linux/init.h>
#include <linux/iopoll.h>
+#include <linux/math64.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/of.h>
@@ -74,6 +75,17 @@
#define MSTOP_OFF(conf) FIELD_GET(GENMASK(31, 16), (conf))
#define MSTOP_MASK(conf) FIELD_GET(GENMASK(15, 0), (conf))
+#define PLL5_FOUTVCO_MIN 800000000
+#define PLL5_FOUTVCO_MAX 3000000000
+#define PLL5_POSTDIV_MIN 1
+#define PLL5_POSTDIV_MAX 7
+#define PLL5_REFDIV_MIN 1
+#define PLL5_REFDIV_MAX 2
+#define PLL5_INTIN_MIN 20
+#define PLL5_INTIN_MAX 320
+#define PLL5_HSCLK_MIN 10000000
+#define PLL5_HSCLK_MAX 187500000
+
/**
* struct clk_hw_data - clock hardware data
* @hw: clock hw
@@ -122,13 +134,19 @@ struct div_hw_data {
struct rzg2l_pll5_param {
u32 pl5_fracin;
+ u16 pl5_intin;
u8 pl5_refdiv;
- u8 pl5_intin;
u8 pl5_postdiv1;
u8 pl5_postdiv2;
u8 pl5_spread;
};
+/* PLL5 output will be used for DPI or MIPI-DSI */
+static int dsi_div_target = PLL5_TARGET_DPI;
+
+/* Required division ratio for MIPI D-PHY clock depending on number of lanes and bpp. */
+static u8 dsi_div_ab_desired;
+
struct rzg2l_pll5_mux_dsi_div_param {
u8 clksrc;
u8 dsi_div_a;
@@ -170,6 +188,11 @@ struct rzg2l_cpg_priv {
struct rzg2l_pll5_mux_dsi_div_param mux_dsi_div_params;
};
+static inline u8 rzg2l_cpg_div_ab(u8 a, u8 b)
+{
+ return (b + 1) << a;
+}
+
static void rzg2l_cpg_del_clk_provider(void *data)
{
of_clk_del_provider(data);
@@ -556,23 +579,120 @@ rzg2l_cpg_sd_mux_clk_register(const struct cpg_core_clk *core,
return clk_hw->clk;
}
+/*
+ * VCO-->[POSTDIV1,2]--FOUTPOSTDIV--------------->|
+ * | |-->[1/(DSI DIV A * B)]--> MIPI_DSI_VCLK
+ * |-->[1/2]--FOUT1PH0-->|
+ * |
+ * |------->[1/16]--------------------------------> hsclk (MIPI-PHY)
+ */
static unsigned long
-rzg2l_cpg_get_foutpostdiv_rate(struct rzg2l_pll5_param *params,
+rzg2l_cpg_get_foutpostdiv_rate(struct rzg2l_cpg_priv *priv,
+ struct rzg2l_pll5_param *params,
unsigned long rate)
{
- unsigned long foutpostdiv_rate, foutvco_rate;
+ const u32 extal_hz = EXTAL_FREQ_IN_MEGA_HZ * MEGA;
+ unsigned long foutpostdiv_rate;
+ unsigned int a, b, odd;
+ unsigned long hsclk;
+ u8 dsi_div_ab_calc;
+ u64 foutvco_rate;
+
+ if (dsi_div_target == PLL5_TARGET_DSI) {
+ /* Check hsclk */
+ hsclk = rate * dsi_div_ab_desired / 16;
+ if (hsclk < PLL5_HSCLK_MIN || hsclk > PLL5_HSCLK_MAX) {
+ dev_err(priv->dev, "hsclk out of range\n");
+ return 0;
+ }
+
+ /* Determine the correct clock source based on even/odd of the divider */
+ odd = dsi_div_ab_desired & 1;
+ if (odd) {
+ priv->mux_dsi_div_params.clksrc = 0; /* FOUTPOSTDIV */
+ dsi_div_ab_calc = dsi_div_ab_desired;
+ } else {
+ priv->mux_dsi_div_params.clksrc = 1; /* FOUT1PH0 */
+ dsi_div_ab_calc = dsi_div_ab_desired / 2;
+ }
+
+ /* Calculate the DIV_DSI_A and DIV_DSI_B based on the desired divider */
+ for (a = 0; a < 4; a++) {
+ /* FOUT1PH0: Max output of DIV_DSI_A is 750MHz so at least 1/2 to be safe */
+ if (!odd && a == 0)
+ continue;
+
+ /* FOUTPOSTDIV: DIV_DSI_A must always be 1/1 */
+ if (odd && a != 0)
+ break;
+
+ for (b = 0; b < 16; b++) {
+ /* FOUTPOSTDIV: DIV_DSI_B must always be odd divider 1/(b+1) */
+ if (odd && b & 1)
+ continue;
+
+ if (rzg2l_cpg_div_ab(a, b) == dsi_div_ab_calc) {
+ priv->mux_dsi_div_params.dsi_div_a = a;
+ priv->mux_dsi_div_params.dsi_div_b = b;
+ goto calc_pll_clk;
+ }
+ }
+ }
+
+ dev_err(priv->dev, "Failed to calculate DIV_DSI_A,B\n");
+
+ return 0;
+ } else if (dsi_div_target == PLL5_TARGET_DPI) {
+ /* Fixed settings for DPI */
+ priv->mux_dsi_div_params.clksrc = 0;
+ priv->mux_dsi_div_params.dsi_div_a = 3; /* Divided by 8 */
+ priv->mux_dsi_div_params.dsi_div_b = 0; /* Divided by 1 */
+ dsi_div_ab_desired = rzg2l_cpg_div_ab(priv->mux_dsi_div_params.dsi_div_a,
+ priv->mux_dsi_div_params.dsi_div_b);
+ }
- params->pl5_intin = rate / MEGA;
- params->pl5_fracin = div_u64(((u64)rate % MEGA) << 24, MEGA);
- params->pl5_refdiv = 2;
- params->pl5_postdiv1 = 1;
- params->pl5_postdiv2 = 1;
+calc_pll_clk:
+ /* PLL5 (MIPI_DSI_PLLCLK) = VCO / POSTDIV1 / POSTDIV2 */
+ for (params->pl5_postdiv1 = PLL5_POSTDIV_MIN;
+ params->pl5_postdiv1 <= PLL5_POSTDIV_MAX;
+ params->pl5_postdiv1++) {
+ for (params->pl5_postdiv2 = PLL5_POSTDIV_MIN;
+ params->pl5_postdiv2 <= PLL5_POSTDIV_MAX;
+ params->pl5_postdiv2++) {
+ foutvco_rate = rate * params->pl5_postdiv1 * params->pl5_postdiv2 *
+ dsi_div_ab_desired;
+ if (foutvco_rate <= PLL5_FOUTVCO_MIN || foutvco_rate >= PLL5_FOUTVCO_MAX)
+ continue;
+
+ for (params->pl5_refdiv = PLL5_REFDIV_MIN;
+ params->pl5_refdiv <= PLL5_REFDIV_MAX;
+ params->pl5_refdiv++) {
+ u32 rem;
+
+ params->pl5_intin = div_u64_rem(foutvco_rate * params->pl5_refdiv,
+ extal_hz, &rem);
+
+ if (params->pl5_intin < PLL5_INTIN_MIN ||
+ params->pl5_intin > PLL5_INTIN_MAX)
+ continue;
+
+ params->pl5_fracin = div_u64((u64)rem << 24, extal_hz);
+
+ goto clk_valid;
+ }
+ }
+ }
+
+ dev_err(priv->dev, "Failed to calculate PLL5 settings\n");
+ return 0;
+
+clk_valid:
params->pl5_spread = 0x16;
foutvco_rate = div_u64(mul_u32_u32(EXTAL_FREQ_IN_MEGA_HZ * MEGA,
(params->pl5_intin << 24) + params->pl5_fracin),
params->pl5_refdiv) >> 24;
- foutpostdiv_rate = DIV_ROUND_CLOSEST_ULL(foutvco_rate,
+ foutpostdiv_rate = DIV_U64_ROUND_CLOSEST(foutvco_rate,
params->pl5_postdiv1 * params->pl5_postdiv2);
return foutpostdiv_rate;
@@ -607,7 +727,7 @@ static unsigned long rzg2l_cpg_get_vclk_parent_rate(struct clk_hw *hw,
struct rzg2l_pll5_param params;
unsigned long parent_rate;
- parent_rate = rzg2l_cpg_get_foutpostdiv_rate(&params, rate);
+ parent_rate = rzg2l_cpg_get_foutpostdiv_rate(priv, &params, rate);
if (priv->mux_dsi_div_params.clksrc)
parent_rate /= 2;
@@ -623,9 +743,19 @@ static int rzg2l_cpg_dsi_div_determine_rate(struct clk_hw *hw,
req->best_parent_rate = rzg2l_cpg_get_vclk_parent_rate(hw, req->rate);
+ if (!req->best_parent_rate)
+ return -EINVAL;
+
return 0;
}
+void rzg2l_cpg_dsi_div_set_divider(u8 divider, int target)
+{
+ dsi_div_ab_desired = divider;
+ dsi_div_target = target;
+}
+EXPORT_SYMBOL_GPL(rzg2l_cpg_dsi_div_set_divider);
+
static int rzg2l_cpg_dsi_div_set_rate(struct clk_hw *hw,
unsigned long rate,
unsigned long parent_rate)
@@ -796,22 +926,6 @@ struct sipll5 {
#define to_sipll5(_hw) container_of(_hw, struct sipll5, hw)
-static unsigned long rzg2l_cpg_get_vclk_rate(struct clk_hw *hw,
- unsigned long rate)
-{
- struct sipll5 *sipll5 = to_sipll5(hw);
- struct rzg2l_cpg_priv *priv = sipll5->priv;
- unsigned long vclk;
-
- vclk = rate / ((1 << priv->mux_dsi_div_params.dsi_div_a) *
- (priv->mux_dsi_div_params.dsi_div_b + 1));
-
- if (priv->mux_dsi_div_params.clksrc)
- vclk /= 2;
-
- return vclk;
-}
-
static unsigned long rzg2l_cpg_sipll5_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
@@ -856,16 +970,16 @@ static int rzg2l_cpg_sipll5_set_rate(struct clk_hw *hw,
if (!rate)
return -EINVAL;
- vclk_rate = rzg2l_cpg_get_vclk_rate(hw, rate);
+ vclk_rate = rate / dsi_div_ab_desired;
sipll5->foutpostdiv_rate =
- rzg2l_cpg_get_foutpostdiv_rate(&params, vclk_rate);
+ rzg2l_cpg_get_foutpostdiv_rate(priv, &params, vclk_rate);
/* Put PLL5 into standby mode */
writel(CPG_SIPLL5_STBY_RESETB_WEN, priv->base + CPG_SIPLL5_STBY);
ret = readl_poll_timeout(priv->base + CPG_SIPLL5_MON, val,
!(val & CPG_SIPLL5_MON_PLL5_LOCK), 100, 250000);
if (ret) {
- dev_err(priv->dev, "failed to release pll5 lock");
+ dev_err(priv->dev, "failed to release pll5 lock\n");
return ret;
}
@@ -892,7 +1006,7 @@ static int rzg2l_cpg_sipll5_set_rate(struct clk_hw *hw,
ret = readl_poll_timeout(priv->base + CPG_SIPLL5_MON, val,
(val & CPG_SIPLL5_MON_PLL5_LOCK), 100, 250000);
if (ret) {
- dev_err(priv->dev, "failed to lock pll5");
+ dev_err(priv->dev, "failed to lock pll5\n");
return ret;
}
@@ -945,9 +1059,7 @@ rzg2l_cpg_sipll5_register(const struct cpg_core_clk *core,
if (ret)
return ERR_PTR(ret);
- priv->mux_dsi_div_params.clksrc = 1; /* Use clk src 1 for DSI */
- priv->mux_dsi_div_params.dsi_div_a = 1; /* Divided by 2 */
- priv->mux_dsi_div_params.dsi_div_b = 2; /* Divided by 3 */
+ rzg2l_cpg_dsi_div_set_divider(8, PLL5_TARGET_DPI);
return clk_hw->clk;
}
@@ -1102,7 +1214,7 @@ static struct clk
}
if (IS_ERR(clk))
- dev_err(dev, "Cannot get %s clock %u: %ld", type, clkidx,
+ dev_err(dev, "Cannot get %s clock %u: %ld\n", type, clkidx,
PTR_ERR(clk));
else
dev_dbg(dev, "clock (%u, %u) is %pC at %lu Hz\n",
@@ -1647,6 +1759,7 @@ static int __rzg2l_cpg_assert(struct reset_controller_dev *rcdev,
u32 mask = BIT(info->resets[id].bit);
s8 monbit = info->resets[id].monbit;
u32 value = mask << 16;
+ u32 mon;
int ret;
dev_dbg(rcdev->dev, "%s id:%ld offset:0x%x\n",
@@ -1667,10 +1780,10 @@ static int __rzg2l_cpg_assert(struct reset_controller_dev *rcdev,
return 0;
}
- ret = readl_poll_timeout_atomic(priv->base + reg, value,
- assert == !!(value & mask), 10, 200);
- if (ret && !assert) {
- value = mask << 16;
+ ret = readl_poll_timeout_atomic(priv->base + reg, mon,
+ assert == !!(mon & mask), 10, 200);
+ if (ret) {
+ value ^= mask;
writel(value, priv->base + CLK_RST_R(info->resets[id].off));
}