// SPDX-License-Identifier: (GPL-2.0+ OR MIT) /* * Copyright (c) 2023 Rockchip Electronics Co., Ltd. * Author: Sandy Huang */ #include #include #include #include "rockchip_drm_vop.h" #include "rockchip_drm_drv.h" #define VOP2_PLL_LIMIT_FREQ 594000000 #define VOP2_PLL_MIN_FREQ 40000000 static long rockchip_rk3562_drm_dclk_round_rate(struct clk *dclk, unsigned long rate) { struct clk_hw *hw; struct clk_hw *p_hw; unsigned long round_rate; const char *name; hw = __clk_get_hw(dclk); if (!hw) return -EINVAL; p_hw = clk_hw_get_parent(hw); if (!p_hw) return -EINVAL; name = clk_hw_get_name(p_hw); if (!strcmp(name, "vpll")) round_rate = rate; else round_rate = clk_round_rate(dclk, rate); return round_rate; } static long rockchip_rk3568_drm_dclk_round_rate(struct clk *dclk, unsigned long rate) { struct clk_hw *hw; struct clk_hw *p_hw; unsigned long round_rate; const char *name; hw = __clk_get_hw(dclk); if (!hw) return -EINVAL; p_hw = clk_hw_get_parent(hw); if (!p_hw) return -EINVAL; name = clk_hw_get_name(p_hw); if (!strcmp(name, "vpll")) round_rate = rate; else if (!strcmp(name, "hpll")) round_rate = rate; else round_rate = clk_round_rate(dclk, rate); return round_rate; } static long rockchip_rk3576_vopl_drm_dclk_round_rate(struct clk *dclk, unsigned long rate) { struct clk_hw *hw; struct clk_hw *p_hw; unsigned long round_rate; const char *name; hw = __clk_get_hw(dclk); if (!hw) return -EINVAL; p_hw = clk_hw_get_parent(hw); if (!p_hw) return -EINVAL; name = clk_hw_get_name(p_hw); if (!strcmp(name, "vpll")) round_rate = rate; else if (!strcmp(name, "dclk_ebc_frac_src")) round_rate = rate; else round_rate = clk_round_rate(dclk, rate); return round_rate; } static long rockchip_rk3576_drm_dclk_round_rate(struct clk *dclk, unsigned long rate) { struct clk_hw *hw; struct clk_hw *p_hw; unsigned long round_rate; const char *name; hw = __clk_get_hw(dclk); if (!hw) return -EINVAL; p_hw = clk_hw_get_parent(hw); if (!p_hw) return -EINVAL; name = clk_hw_get_name(p_hw); if (!strcmp(name, "vpll")) round_rate = rate; else round_rate = clk_round_rate(dclk, rate); return round_rate; } static long rockchip_rk3588_drm_dclk_round_rate(struct clk *dclk, unsigned long rate) { struct clk_hw *hw; struct clk_hw *p_hw; unsigned long round_rate; const char *name; hw = __clk_get_hw(dclk); if (!hw) return -EINVAL; p_hw = clk_hw_get_parent(hw); if (!p_hw) return -EINVAL; name = clk_hw_get_name(p_hw); if (!strcmp(name, "v0pll")) round_rate = rate; else round_rate = clk_round_rate(dclk, rate); return round_rate; } /* * The rk3562 is a single display, exclusive to vpll */ static int rockchip_rk3562_drm_dclk_set_rate(struct clk *dclk, unsigned long rate) { struct clk_hw *hw; struct clk_hw *p_hw; unsigned long pll_rate; const char *name; int div = 0; hw = __clk_get_hw(dclk); if (!hw) return -EINVAL; p_hw = clk_hw_get_parent(hw); if (!p_hw) return -EINVAL; name = clk_hw_get_name(p_hw); if (!strcmp(name, "vpll")) { pll_rate = clk_hw_get_rate(p_hw); if (pll_rate >= VOP2_PLL_LIMIT_FREQ && pll_rate % rate == 0) { clk_set_rate(dclk, rate); } else { div = DIV_ROUND_UP(VOP2_PLL_LIMIT_FREQ, rate); if (div % 2) div += 1; clk_set_rate(p_hw->clk, rate * div); clk_set_rate(dclk, rate); } } else { clk_set_rate(dclk, rate); } pr_debug("%s:request rate = %ld, %s = %ld, %s = %ld\n", __func__, rate, clk_hw_get_name(hw), clk_hw_get_rate(hw), clk_hw_get_name(p_hw), clk_hw_get_rate(p_hw)); return 0; } /* * The rk3568 has three ports, dclk_vop0/dclk_vop1/dclk_vop2 * For the dclk used by hdmi, the parent clock must be specified in hpll. * There is also a dclk that can be specified on the vpll. * The last dclk can only choose the nearest frequency division, * and cannot support accurate frequency setting. */ static int rockchip_rk3568_drm_dclk_set_rate(struct clk *dclk, unsigned long rate) { struct clk_hw *hw; struct clk_hw *p_hw; unsigned long pll_rate; const char *name; int div = 0; hw = __clk_get_hw(dclk); if (!hw) return -EINVAL; p_hw = clk_hw_get_parent(hw); if (!p_hw) return -EINVAL; name = clk_hw_get_name(p_hw); if (!strcmp(name, "vpll")) { pll_rate = clk_hw_get_rate(p_hw); if (pll_rate >= VOP2_PLL_LIMIT_FREQ && pll_rate % rate == 0) { clk_set_rate(dclk, rate); } else { div = DIV_ROUND_UP(VOP2_PLL_LIMIT_FREQ, rate); if (div % 2) div += 1; clk_set_rate(p_hw->clk, rate * div); clk_set_rate(dclk, rate); } } else if (!strcmp(name, "hpll")) { if (rate < VOP2_PLL_MIN_FREQ) pr_warn("%s: Warning: rate is low than pll min limit!\n", __func__); clk_set_rate(p_hw->clk, rate); clk_set_rate(dclk, rate); } else { clk_set_rate(dclk, rate); } pr_debug("%s:request rate = %ld, %s = %ld %s = %ld\n", __func__, rate, clk_hw_get_name(hw), clk_hw_get_rate(hw), clk_hw_get_name(p_hw), clk_hw_get_rate(p_hw)); return 0; } /* * The rk3576 ebc setting clk rule. * The dclk_ebc can select vpll, the vpll is ebc exclusive. * The dclk_ebc can select dclk_ebc_frac_src, use digital decimal divider, the recommended frequency is less than 60M. * The dclk_ebc can select gpll or cpll, can only choose the nearest frequency division(gpll:1188M,cpll:1000M), * and can't support accurate frequency setting. * */ static int rockchip_rk3576_vopl_drm_dclk_set_rate(struct clk *dclk, unsigned long rate) { struct clk_hw *hw; struct clk_hw *p_hw; unsigned long pll_rate; const char *name; int div = 0; hw = __clk_get_hw(dclk); if (!hw) return -EINVAL; p_hw = clk_hw_get_parent(hw); if (!p_hw) return -EINVAL; name = clk_hw_get_name(p_hw); if (!strcmp(name, "vpll")) { pll_rate = clk_hw_get_rate(p_hw); if (pll_rate >= VOP2_PLL_LIMIT_FREQ && pll_rate % rate == 0) { clk_set_rate(dclk, rate); } else { div = DIV_ROUND_UP(VOP2_PLL_LIMIT_FREQ, rate); if (div % 2) div += 1; clk_set_rate(p_hw->clk, rate * div); clk_set_rate(dclk, rate); } } else if (!strcmp(name, "dclk_ebc_frac_src")) { clk_set_rate(p_hw->clk, rate); clk_set_rate(dclk, rate); } else { clk_set_rate(dclk, rate); } pr_debug("%s:request rate = %ld, %s = %ld %s = %ld\n", __func__, rate, clk_hw_get_name(hw), clk_hw_get_rate(hw), clk_hw_get_name(p_hw), clk_hw_get_rate(p_hw)); return 0; } /* * The rk3576 has three ports, dclk_vp0\1\2. * The dclk_vp0\1\2 can select 1 port specified on clk_hdmiphy_pixelx. * The dclk_vp0\1\2 can select 1 port specified on vpll. * The last dclk can only choose the nearest frequency division(gpll:1188M,cpll:1000M), * and can't support accurate frequency setting. * * For HDMI 2.1[dclk bigger than 597M], the HDMI work at FRL mode, so the dclk for * vp can't from hdmi phy, only can be from vpll/gpll/cpll. */ static int rockchip_rk3576_drm_dclk_set_rate(struct clk *dclk, unsigned long rate) { struct clk_hw *hw; struct clk_hw *p_hw; unsigned long pll_rate; const char *name; int div = 0; hw = __clk_get_hw(dclk); if (!hw) return -EINVAL; p_hw = clk_hw_get_parent(hw); if (!p_hw) return -EINVAL; p_hw = clk_hw_get_parent(p_hw); if (!p_hw) return -EINVAL; name = clk_hw_get_name(p_hw); if (!strcmp(name, "vpll")) { pll_rate = clk_hw_get_rate(p_hw); if (pll_rate >= VOP2_PLL_LIMIT_FREQ && pll_rate % rate == 0) { clk_set_rate(dclk, rate); } else { div = DIV_ROUND_UP(VOP2_PLL_LIMIT_FREQ, rate); if (div % 2) div += 1; clk_set_rate(p_hw->clk, rate * div); clk_set_rate(dclk, rate); } } else { clk_set_rate(dclk, rate); } pr_debug("%s:request rate = %ld, %s = %ld %s = %ld\n", __func__, rate, clk_hw_get_name(hw), clk_hw_get_rate(hw), clk_hw_get_name(p_hw), clk_hw_get_rate(p_hw)); return 0; } /* * The rk3588 has four ports, dclk_vop0\1\2\3. * The dclk_vop0\1\2 can select 2 ports specified on clk_hdmiphy_pixelx. * The dclk_vop0\1\2\3 can select 1 ports specified on v0pll. * The last dclk can only choose the nearest frequency division, * and cannot support accurate frequency setting. */ static int rockchip_rk3588_drm_dclk_set_rate(struct clk *dclk, unsigned long rate) { struct clk_hw *hw; struct clk_hw *p_hw; unsigned long pll_rate; const char *name; int div = 0; hw = __clk_get_hw(dclk); if (!hw) return -EINVAL; name = clk_hw_get_name(hw); if (!strcmp(name, "dclk_vop3")) { p_hw = clk_hw_get_parent(hw); } else { p_hw = clk_hw_get_parent(hw); if (!p_hw) return -EINVAL; p_hw = clk_hw_get_parent(p_hw); } if (!p_hw) return -EINVAL; name = clk_hw_get_name(p_hw); if (!strcmp(name, "v0pll")) { pll_rate = clk_hw_get_rate(p_hw); if (pll_rate >= VOP2_PLL_LIMIT_FREQ && pll_rate % rate == 0) { clk_set_rate(dclk, rate); } else { div = DIV_ROUND_UP(VOP2_PLL_LIMIT_FREQ, rate); if (div % 2) div += 1; clk_set_rate(p_hw->clk, rate * div); clk_set_rate(dclk, rate); } } else { clk_set_rate(dclk, rate); } pr_debug("%s:request rate = %ld, %s = %ld %s = %ld\n", __func__, rate, clk_hw_get_name(hw), clk_hw_get_rate(hw), clk_hw_get_name(p_hw), clk_hw_get_rate(p_hw)); return 0; } long rockchip_drm_dclk_round_rate(u32 version, struct clk *dclk, unsigned long rate) { long round_rate; switch (version) { case VOP_VERSION_RK3562: round_rate = rockchip_rk3562_drm_dclk_round_rate(dclk, rate); break; case VOP_VERSION_RK3568: round_rate = rockchip_rk3568_drm_dclk_round_rate(dclk, rate); break; case VOP_VERSION_RK3576: round_rate = rockchip_rk3576_drm_dclk_round_rate(dclk, rate); break; case VOP_VERSION_RK3576_LITE: round_rate = rockchip_rk3576_vopl_drm_dclk_round_rate(dclk, rate); break; case VOP_VERSION_RK3588: round_rate = rockchip_rk3588_drm_dclk_round_rate(dclk, rate); break; default: round_rate = clk_round_rate(dclk, rate); break; } if (round_rate < 0) pr_warn("%s:the clk_hw of dclk or parent of dclk may be NULL\n", __func__); return round_rate; } int rockchip_drm_dclk_set_rate(u32 version, struct clk *dclk, unsigned long rate) { int ret; switch (version) { case VOP_VERSION_RK3562: ret = rockchip_rk3562_drm_dclk_set_rate(dclk, rate); break; case VOP_VERSION_RK3568: ret = rockchip_rk3568_drm_dclk_set_rate(dclk, rate); break; case VOP_VERSION_RK3576: ret = rockchip_rk3576_drm_dclk_set_rate(dclk, rate); break; case VOP_VERSION_RK3576_LITE: ret = rockchip_rk3576_vopl_drm_dclk_set_rate(dclk, rate); break; case VOP_VERSION_RK3588: ret = rockchip_rk3588_drm_dclk_set_rate(dclk, rate); break; default: ret = clk_set_rate(dclk, rate); break; } if (ret < 0) pr_warn("%s:the clk_hw of dclk or parent of dclk may be NULL\n", __func__); return ret; }