// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2021 Rockchip Electronics Co. Ltd. * Author: Elaine Zhang */ #include #include #include static int cru_debug; #define cru_dbg(format, ...) do { \ if (cru_debug) \ pr_info("%s: " format, __func__, ## __VA_ARGS__); \ } while (0) #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; cru_dbg("%s rate: %ld\n", clk_hw_get_name(hw), 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 u8 vop2_mux_get_parent(struct clk_hw *hw) { struct vop2_clk *vop2_clk = to_vop2_clk(hw); cru_dbg("%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; cru_dbg("%s index: %d\n", clk_hw_get_name(hw), index); return 0; } static int vop2_clk_mux_determine_rate(struct clk_hw *hw, struct clk_rate_request *req) { cru_dbg("%s %ld(min: %ld max: %ld)\n", clk_hw_get_name(hw), req->rate, req->min_rate, req->max_rate); return __clk_mux_determine_rate(hw, req); } static const struct clk_ops vop2_mux_clk_ops = { .get_parent = vop2_mux_get_parent, .set_parent = vop2_mux_set_parent, .determine_rate = vop2_clk_mux_determine_rate, }; #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; cru_dbg("%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; } cru_dbg("%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; cru_dbg("%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 = devm_kzalloc(vop2->dev, sizeof(*vop2_clk), GFP_KERNEL); if (!vop2_clk) return ERR_PTR(-ENOMEM); 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 = devm_clk_register(vop2->dev, &vop2_clk->hw); if (!IS_ERR(clk)) list_add_tail(&vop2_clk->list, &vop2->clk_list_head); else DRM_DEV_ERROR(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->version != VOP_VERSION_RK3588) 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; }