455 lines
10 KiB
C
455 lines
10 KiB
C
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
|
|
/*
|
|
* Copyright (c) 2023 Rockchip Electronics Co., Ltd.
|
|
* Author: Sandy Huang <hjc@rock-chips.com>
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/clk-provider.h>
|
|
#include <linux/clk/clk-conf.h>
|
|
|
|
#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;
|
|
}
|