[PATCH 10/12] drm/rockchip: vop2: Improve display modes handling on rk3588
Sascha Hauer
s.hauer at pengutronix.de
Mon Oct 28 07:19:55 PDT 2024
Derived from
https://gitlab.collabora.com/hardware-enablement/rockchip-3588/linux:
| commit b98b7030e022fed4c0c122d5954a5546ffd360c2
| Author: Cristian Ciocaltea <cristian.ciocaltea at collabora.com>
| Date: Fri Nov 3 19:58:02 2023 +0200
|
| [WIP] drm/rockchip: vop2: Improve display modes handling on rk3588
|
| The initial vop2 support for rk3588 in mainline is not able to handle
| all display modes supported by connected displays, e.g.
| 2560x1440-75.00Hz, 2048x1152-60.00Hz, 1024x768-60.00Hz.
|
| Additionally, it doesn't cope with non-integer refresh rates like 59.94,
| 29.97, 23.98, etc.
|
| Improve HDMI0 clocking in order to support the additional display modes.
|
| Fixes: 5a028e8f062f ("drm/rockchip: vop2: Add support for rk3588")
| Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea at collabora.com>
Signed-off-by: Sascha Hauer <s.hauer at pengutronix.de>
---
drivers/video/rockchip/rockchip_drm_vop2.c | 465 +++++++++++++++++++++++++++++
1 file changed, 465 insertions(+)
diff --git a/drivers/video/rockchip/rockchip_drm_vop2.c b/drivers/video/rockchip/rockchip_drm_vop2.c
index 6464c27d68..dc47432757 100644
--- a/drivers/video/rockchip/rockchip_drm_vop2.c
+++ b/drivers/video/rockchip/rockchip_drm_vop2.c
@@ -219,6 +219,11 @@ struct vop2 {
struct clk *aclk;
struct clk *pclk;
+ // [CC:] hack to support additional display modes
+ struct clk *hdmi0_phy_pll;
+ /* list_head of internal clk */
+ struct list_head clk_list_head;
+
/* optional internal rgb encoder */
struct rockchip_rgb *rgb;
@@ -226,6 +231,19 @@ struct vop2 {
struct vop2_win win[];
};
+struct vop2_clk {
+ struct vop2 *vop2;
+ struct list_head list;
+ unsigned long rate;
+ struct clk_hw hw;
+ struct clk_divider div;
+ int div_val;
+ u8 parent_index;
+};
+
+#define to_vop2_clk(_hw) container_of(_hw, struct vop2_clk, hw)
+#define VOP2_MAX_DCLK_RATE 600000 /* kHz */
+
#define vop2_output_if_is_hdmi(x) ((x) == ROCKCHIP_VOP2_EP_HDMI0 || \
(x) == ROCKCHIP_VOP2_EP_HDMI1)
@@ -735,6 +753,32 @@ static unsigned long rk3588_calc_dclk(unsigned long child_clk, unsigned long max
return 0;
}
+static struct vop2_clk *vop2_clk_get(struct vop2 *vop2, const char *name);
+
+static int vop2_cru_set_rate(struct vop2_clk *if_pixclk, struct vop2_clk *if_dclk)
+{
+ struct vop2 *vop2 = if_pixclk->vop2;
+ int ret = 0;
+
+ if (if_pixclk) {
+ ret = clk_set_rate(&if_pixclk->hw.clk, if_pixclk->rate);
+ if (ret < 0) {
+ dev_err(vop2->dev, "set %s to %ld failed: %d\n",
+ clk_hw_get_name(&if_pixclk->hw), if_pixclk->rate, ret);
+ return ret;
+ }
+ }
+
+ if (if_dclk) {
+ ret = clk_set_rate(&if_dclk->hw.clk, if_dclk->rate);
+ if (ret < 0)
+ dev_err(vop2->dev, "set %s to %ld failed %d\n",
+ clk_hw_get_name(&if_dclk->hw), if_dclk->rate, ret);
+ }
+
+ return ret;
+}
+
/*
* 4 pixclk/cycle on rk3588
* RGB/eDP/HDMI: if_pixclk >= dclk_core
@@ -754,6 +798,67 @@ static unsigned long rk3588_calc_cru_cfg(struct vop2_video_port *vp, int id,
int K = 1;
if (vop2_output_if_is_hdmi(id)) {
+ if (vop2->data->soc_id == 3588 && id == ROCKCHIP_VOP2_EP_HDMI0 &&
+ vop2->hdmi0_phy_pll) {
+ const char *clk_src_name = "hdmi_edp0_clk_src";
+ const char *clk_parent_name = "dclk";
+ const char *pixclk_name = "hdmi_edp0_pixclk";
+ const char *dclk_name = "hdmi_edp0_dclk";
+ struct vop2_clk *if_clk_src, *if_clk_parent, *if_pixclk, *if_dclk, *dclk, *dclk_core, *dclk_out;
+ char clk_name[32];
+ int ret;
+
+ if_clk_src = vop2_clk_get(vop2, clk_src_name);
+ snprintf(clk_name, sizeof(clk_name), "%s%d", clk_parent_name, vp->id);
+ if_clk_parent = vop2_clk_get(vop2, clk_name);
+ if_pixclk = vop2_clk_get(vop2, pixclk_name);
+ if_dclk = vop2_clk_get(vop2, dclk_name);
+ if (!if_pixclk || !if_clk_parent) {
+ dev_err(vop2->dev, "failed to get connector interface clk\n");
+ return 0;
+ }
+
+ ret = clk_set_parent(&if_clk_src->hw.clk, &if_clk_parent->hw.clk);
+ if (ret < 0) {
+ dev_err(vop2->dev, "failed to set parent(%s) for %s: %d\n",
+ if_clk_parent->hw.clk.name,
+ if_clk_src->hw.clk.name, ret);
+ return 0;
+ }
+
+ if_pixclk->rate = (dclk_core_rate << 1) / K;
+ if_dclk->rate = dclk_core_rate / K;
+
+ snprintf(clk_name, sizeof(clk_name), "dclk_core%d", vp->id);
+ dclk_core = vop2_clk_get(vop2, clk_name);
+
+ snprintf(clk_name, sizeof(clk_name), "dclk_out%d", vp->id);
+ dclk_out = vop2_clk_get(vop2, clk_name);
+
+ snprintf(clk_name, sizeof(clk_name), "dclk%d", vp->id);
+ dclk = vop2_clk_get(vop2, clk_name);
+ if (v_pixclk <= (VOP2_MAX_DCLK_RATE * 1000)) {
+ } else {
+ v_pixclk = v_pixclk >> 2;
+ }
+ clk_set_rate(&dclk->hw.clk, v_pixclk);
+
+ if (dclk_core_rate > if_pixclk->rate) {
+ clk_set_rate(&dclk_core->hw.clk, dclk_core_rate);
+ ret = vop2_cru_set_rate(if_pixclk, if_dclk);
+ } else {
+ ret = vop2_cru_set_rate(if_pixclk, if_dclk);
+ clk_set_rate(&dclk_core->hw.clk, dclk_core_rate);
+ }
+
+ *dclk_core_div = dclk_core->div_val;
+ *dclk_out_div = dclk_out->div_val;
+ *if_pixclk_div = if_pixclk->div_val;
+ *if_dclk_div = if_dclk->div_val;
+
+ return dclk->rate;
+ }
+
if_pixclk_rate = (dclk_core_rate << 1) / K;
/*
* if_dclk_rate = dclk_core_rate / K;
@@ -974,6 +1079,22 @@ static int us_to_vertical_line(struct drm_display_mode *mode, int us)
return us * mode->clock / mode->htotal / 1000;
}
+// [CC:] rework virtual clock
+static struct vop2_clk *vop2_clk_get(struct vop2 *vop2, const char *name)
+{
+ struct vop2_clk *clk, *n;
+
+ if (!name)
+ return NULL;
+
+ list_for_each_entry_safe(clk, n, &vop2->clk_list_head, list) {
+ if (!strcmp(clk_hw_get_name(&clk->hw), name))
+ return clk;
+ }
+
+ return NULL;
+}
+
static int drm_mode_vrefresh(const struct drm_display_mode *mode)
{
unsigned int num, den;
@@ -1008,6 +1129,8 @@ static void vop2_crtc_atomic_enable(struct vop2_video_port *vp,
int act_end;
u32 val, polflags;
int ret;
+ char clk_name[32];
+ struct vop2_clk *dclk;
dev_dbg(vop2->dev, "Update mode to %dx%dp%d, type: %d for vp%d\n",
hdisplay, vdisplay,
@@ -1072,6 +1195,24 @@ static void vop2_crtc_atomic_enable(struct vop2_video_port *vp,
vop2_vp_write(vp, RK3568_VP_MIPI_CTRL, 0);
+ snprintf(clk_name, sizeof(clk_name), "dclk%d", vp->id);
+ dclk = vop2_clk_get(vop2, clk_name);
+ if (dclk) {
+ /*
+ * use HDMI_PHY_PLL as dclk source under 4K at 60 if it is available,
+ * otherwise use system cru as dclk source.
+ */
+ clk_get_rate(vop2->hdmi0_phy_pll);
+
+ if (mode->crtc_clock <= VOP2_MAX_DCLK_RATE) {
+ ret = clk_set_parent(vp->dclk, vop2->hdmi0_phy_pll);
+ if (ret < 0)
+ dev_warn(vop2->dev, "failed to set clock parent for %s\n",
+ __clk_get_name(vp->dclk));
+ }
+
+ clock = dclk->rate;
+ }
clk_set_rate(vp->dclk, clock);
vop2_post_config(vp, mode);
@@ -1937,6 +2078,320 @@ static const struct regmap_config vop2_regmap_config = {
.name = "vop2",
};
+#define PLL_RATE_MIN 30000000
+
+#define PNAME(x) static const char *const x[]
+
+enum vop_clk_branch_type {
+ branch_mux,
+ branch_divider,
+ branch_factor,
+ branch_virtual,
+};
+
+#define VIR(cname) \
+ { \
+ .branch_type = branch_virtual, \
+ .name = cname, \
+ }
+
+
+#define MUX(cname, pnames, f) \
+ { \
+ .branch_type = branch_mux, \
+ .name = cname, \
+ .parent_names = pnames, \
+ .num_parents = ARRAY_SIZE(pnames), \
+ .flags = f, \
+ }
+
+#define FACTOR(cname, pname, f) \
+ { \
+ .branch_type = branch_factor, \
+ .name = cname, \
+ .parent_names = (const char *[]){ pname }, \
+ .num_parents = 1, \
+ .flags = f, \
+ }
+
+#define DIV(cname, pname, f, w) \
+ { \
+ .branch_type = branch_divider, \
+ .name = cname, \
+ .parent_names = (const char *[]){ pname }, \
+ .num_parents = 1, \
+ .flags = f, \
+ .div_width = w, \
+ }
+
+struct vop2_clk_branch {
+ enum vop_clk_branch_type branch_type;
+ const char *name;
+ const char *const *parent_names;
+ u8 num_parents;
+ unsigned long flags;
+ u8 div_shift;
+ u8 div_width;
+ u8 div_flags;
+};
+
+PNAME(mux_port0_dclk_src_p) = { "dclk0", "dclk1" };
+PNAME(mux_port2_dclk_src_p) = { "dclk2", "dclk1" };
+PNAME(mux_dp_pixclk_p) = { "dclk_out0", "dclk_out1", "dclk_out2" };
+PNAME(mux_hdmi_edp_clk_src_p) = { "dclk0", "dclk1", "dclk2" };
+PNAME(mux_mipi_clk_src_p) = { "dclk_out1", "dclk_out2", "dclk_out3" };
+PNAME(mux_dsc_8k_clk_src_p) = { "dclk0", "dclk1", "dclk2", "dclk3" };
+PNAME(mux_dsc_4k_clk_src_p) = { "dclk0", "dclk1", "dclk2", "dclk3" };
+
+/*
+ * We only use this clk driver calculate the div
+ * of dclk_core/dclk_out/if_pixclk/if_dclk and
+ * the rate of the dclk from the soc.
+ *
+ * We don't touch the cru in the vop here, as
+ * these registers has special read andy write
+ * limits.
+ */
+static struct vop2_clk_branch rk3588_vop_clk_branches[] = {
+ VIR("dclk0"),
+ VIR("dclk1"),
+ VIR("dclk2"),
+ VIR("dclk3"),
+
+ MUX("port0_dclk_src", mux_port0_dclk_src_p, CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT),
+ DIV("dclk_core0", "port0_dclk_src", CLK_SET_RATE_PARENT, 2),
+ DIV("dclk_out0", "port0_dclk_src", CLK_SET_RATE_PARENT, 2),
+
+ FACTOR("port1_dclk_src", "dclk1", CLK_SET_RATE_PARENT),
+ DIV("dclk_core1", "port1_dclk_src", CLK_SET_RATE_PARENT, 2),
+ DIV("dclk_out1", "port1_dclk_src", CLK_SET_RATE_PARENT, 2),
+
+ MUX("port2_dclk_src", mux_port2_dclk_src_p, CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT),
+ DIV("dclk_core2", "port2_dclk_src", CLK_SET_RATE_PARENT, 2),
+ DIV("dclk_out2", "port2_dclk_src", CLK_SET_RATE_PARENT, 2),
+
+ FACTOR("port3_dclk_src", "dclk3", CLK_SET_RATE_PARENT),
+ DIV("dclk_core3", "port3_dclk_src", CLK_SET_RATE_PARENT, 2),
+ DIV("dclk_out3", "port3_dclk_src", CLK_SET_RATE_PARENT, 2),
+
+ MUX("dp0_pixclk", mux_dp_pixclk_p, CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT),
+ MUX("dp1_pixclk", mux_dp_pixclk_p, CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT),
+
+ MUX("hdmi_edp0_clk_src", mux_hdmi_edp_clk_src_p,
+ CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT),
+ DIV("hdmi_edp0_dclk", "hdmi_edp0_clk_src", 0, 2),
+ DIV("hdmi_edp0_pixclk", "hdmi_edp0_clk_src", CLK_SET_RATE_PARENT, 1),
+
+ MUX("hdmi_edp1_clk_src", mux_hdmi_edp_clk_src_p,
+ CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT),
+ DIV("hdmi_edp1_dclk", "hdmi_edp1_clk_src", 0, 2),
+ DIV("hdmi_edp1_pixclk", "hdmi_edp1_clk_src", CLK_SET_RATE_PARENT, 1),
+
+ MUX("mipi0_clk_src", mux_mipi_clk_src_p, CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT),
+ DIV("mipi0_pixclk", "mipi0_clk_src", CLK_SET_RATE_PARENT, 2),
+
+ MUX("mipi1_clk_src", mux_mipi_clk_src_p, CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT),
+ DIV("mipi1_pixclk", "mipi1_clk_src", CLK_SET_RATE_PARENT, 2),
+
+ FACTOR("rgb_pixclk", "port3_dclk_src", CLK_SET_RATE_PARENT),
+
+ MUX("dsc_8k_txp_clk_src", mux_dsc_8k_clk_src_p, CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT),
+ DIV("dsc_8k_txp_clk", "dsc_8k_txp_clk_src", 0, 2),
+ DIV("dsc_8k_pxl_clk", "dsc_8k_txp_clk_src", 0, 2),
+ DIV("dsc_8k_cds_clk", "dsc_8k_txp_clk_src", 0, 2),
+
+ MUX("dsc_4k_txp_clk_src", mux_dsc_4k_clk_src_p, CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT),
+ DIV("dsc_4k_txp_clk", "dsc_4k_txp_clk_src", 0, 2),
+ DIV("dsc_4k_pxl_clk", "dsc_4k_txp_clk_src", 0, 2),
+ DIV("dsc_4k_cds_clk", "dsc_4k_txp_clk_src", 0, 2),
+};
+
+static unsigned long clk_virtual_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct vop2_clk *vop2_clk = to_vop2_clk(hw);
+
+ return (unsigned long)vop2_clk->rate;
+}
+
+static long clk_virtual_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *prate)
+{
+ struct vop2_clk *vop2_clk = to_vop2_clk(hw);
+
+ vop2_clk->rate = rate;
+
+ return rate;
+}
+
+static int clk_virtual_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ return 0;
+}
+
+const struct clk_ops clk_virtual_ops = {
+ .round_rate = clk_virtual_round_rate,
+ .set_rate = clk_virtual_set_rate,
+ .recalc_rate = clk_virtual_recalc_rate,
+};
+
+static int vop2_mux_get_parent(struct clk_hw *hw)
+{
+ struct vop2_clk *vop2_clk = to_vop2_clk(hw);
+
+ dev_dbg(vop2_clk->vop2->dev, "%s index: %d\n", clk_hw_get_name(hw),
+ vop2_clk->parent_index);
+
+ return vop2_clk->parent_index;
+}
+
+static int vop2_mux_set_parent(struct clk_hw *hw, u8 index)
+{
+ struct vop2_clk *vop2_clk = to_vop2_clk(hw);
+
+ vop2_clk->parent_index = index;
+
+ dev_dbg(vop2_clk->vop2->dev, "%s index: %d\n", clk_hw_get_name(hw), index);
+
+ return 0;
+}
+
+static const struct clk_ops vop2_mux_clk_ops = {
+ .get_parent = vop2_mux_get_parent,
+ .set_parent = vop2_mux_set_parent,
+};
+
+#define div_mask(width) ((1 << (width)) - 1)
+
+static int vop2_div_get_val(unsigned long rate, unsigned long parent_rate)
+{
+ unsigned int div, value;
+
+ div = DIV_ROUND_UP_ULL((u64)parent_rate, rate);
+
+ value = ilog2(div);
+
+ return value;
+}
+
+static unsigned long vop2_clk_div_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct vop2_clk *vop2_clk = to_vop2_clk(hw);
+ unsigned long rate;
+ unsigned int div;
+
+ div = 1 << vop2_clk->div_val;
+ rate = parent_rate / div;
+
+ dev_dbg(vop2_clk->vop2->dev, "%s rate: %ld(prate: %ld)\n",
+ clk_hw_get_name(hw), rate, parent_rate);
+
+ return rate;
+}
+
+static long vop2_clk_div_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *prate)
+{
+ struct vop2_clk *vop2_clk = to_vop2_clk(hw);
+
+ if (clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT) {
+ if (*prate < rate)
+ *prate = rate;
+ if ((*prate >> vop2_clk->div.width) > rate)
+ *prate = rate;
+
+ if ((*prate % rate))
+ *prate = rate;
+
+ /* SOC PLL can't output a too low pll freq */
+ if (*prate < PLL_RATE_MIN)
+ *prate = rate << vop2_clk->div.width;
+ }
+
+ dev_dbg(vop2_clk->vop2->dev, "%s rate: %ld(prate: %ld)\n", clk_hw_get_name(hw), rate, *prate);
+
+ return rate;
+}
+
+static int vop2_clk_div_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate)
+{
+ struct vop2_clk *vop2_clk = to_vop2_clk(hw);
+ int div_val;
+
+ div_val = vop2_div_get_val(rate, parent_rate);
+ vop2_clk->div_val = div_val;
+
+ dev_dbg(vop2_clk->vop2->dev, "%s prate: %ld rate: %ld div_val: %d\n",
+ clk_hw_get_name(hw), parent_rate, rate, div_val);
+ return 0;
+}
+
+static const struct clk_ops vop2_div_clk_ops = {
+ .recalc_rate = vop2_clk_div_recalc_rate,
+ .round_rate = vop2_clk_div_round_rate,
+ .set_rate = vop2_clk_div_set_rate,
+};
+
+static struct clk *vop2_clk_register(struct vop2 *vop2, struct vop2_clk_branch *branch)
+{
+ struct clk_init_data init = {};
+ struct vop2_clk *vop2_clk;
+ struct clk *clk;
+
+ vop2_clk = xzalloc(sizeof(*vop2_clk));
+
+ vop2_clk->vop2 = vop2;
+ vop2_clk->hw.init = &init;
+ vop2_clk->div.shift = branch->div_shift;
+ vop2_clk->div.width = branch->div_width;
+
+ init.name = branch->name;
+ init.flags = branch->flags;
+ init.num_parents = branch->num_parents;
+ init.parent_names = branch->parent_names;
+ if (branch->branch_type == branch_divider) {
+ init.ops = &vop2_div_clk_ops;
+ } else if (branch->branch_type == branch_virtual) {
+ init.ops = &clk_virtual_ops;
+ init.num_parents = 0;
+ init.parent_names = NULL;
+ } else {
+ init.ops = &vop2_mux_clk_ops;
+ }
+
+ clk = clk_register(vop2->dev, &vop2_clk->hw);
+ if (!IS_ERR(clk))
+ list_add_tail(&vop2_clk->list, &vop2->clk_list_head);
+ else
+ dev_err(vop2->dev, "Register %s failed\n", branch->name);
+
+ return clk;
+}
+
+static int vop2_clk_init(struct vop2 *vop2)
+{
+ struct vop2_clk_branch *branch = rk3588_vop_clk_branches;
+ unsigned int nr_clk = ARRAY_SIZE(rk3588_vop_clk_branches);
+ unsigned int idx;
+ struct vop2_clk *clk, *n;
+
+ INIT_LIST_HEAD(&vop2->clk_list_head);
+
+ if (vop2->data->soc_id < 3588 || vop2->hdmi0_phy_pll == NULL)
+ return 0;
+
+ list_for_each_entry_safe(clk, n, &vop2->clk_list_head, list) {
+ list_del(&clk->list);
+ }
+
+ for (idx = 0; idx < nr_clk; idx++, branch++)
+ vop2_clk_register(vop2, branch);
+
+ return 0;
+}
+
int vop2_bind(struct device *dev)
{
const struct vop2_data *vop2_data;
@@ -2012,6 +2467,16 @@ int vop2_bind(struct device *dev)
return dev_err_probe(vop2->dev, PTR_ERR(vop2->pclk),
"failed to get pclk source\n");
+ vop2->hdmi0_phy_pll = clk_get_optional(vop2->dev, "hdmi0_phy_pll");
+ if (IS_ERR(vop2->hdmi0_phy_pll)) {
+ dev_err_probe(vop2->dev, PTR_ERR(vop2->hdmi0_phy_pll),
+ "failed to get hdmi0_phy_pll source\n");
+ return PTR_ERR(vop2->hdmi0_phy_pll);
+ }
+
+ // [CC:] rework virtual clock
+ vop2_clk_init(vop2);
+
ret = vop2_create_crtcs(vop2);
if (ret)
return ret;
--
2.39.5
More information about the barebox
mailing list