1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * Copyright (C) 2018 Jernej Skrabec <jernej.skrabec@siol.net> 4 */ 5 6 #include <linux/clk-provider.h> 7 8 #include "sun8i_dw_hdmi.h" 9 10 struct sun8i_phy_clk { 11 struct clk_hw hw; 12 struct sun8i_hdmi_phy *phy; 13 }; 14 15 static inline struct sun8i_phy_clk *hw_to_phy_clk(struct clk_hw *hw) 16 { 17 return container_of(hw, struct sun8i_phy_clk, hw); 18 } 19 20 static int sun8i_phy_clk_determine_rate(struct clk_hw *hw, 21 struct clk_rate_request *req) 22 { 23 unsigned long rate = req->rate; 24 unsigned long best_rate = 0; 25 struct clk_hw *parent; 26 int best_div = 1; 27 int i; 28 29 parent = clk_hw_get_parent(hw); 30 31 for (i = 1; i <= 16; i++) { 32 unsigned long ideal = rate * i; 33 unsigned long rounded; 34 35 rounded = clk_hw_round_rate(parent, ideal); 36 37 if (rounded == ideal) { 38 best_rate = rounded; 39 best_div = i; 40 break; 41 } 42 43 if (!best_rate || 44 abs(rate - rounded / i) < 45 abs(rate - best_rate / best_div)) { 46 best_rate = rounded; 47 best_div = i; 48 } 49 } 50 51 req->rate = best_rate / best_div; 52 req->best_parent_rate = best_rate; 53 req->best_parent_hw = parent; 54 55 return 0; 56 } 57 58 static unsigned long sun8i_phy_clk_recalc_rate(struct clk_hw *hw, 59 unsigned long parent_rate) 60 { 61 struct sun8i_phy_clk *priv = hw_to_phy_clk(hw); 62 u32 reg; 63 64 regmap_read(priv->phy->regs, SUN8I_HDMI_PHY_PLL_CFG2_REG, ®); 65 reg = ((reg >> SUN8I_HDMI_PHY_PLL_CFG2_PREDIV_SHIFT) & 66 SUN8I_HDMI_PHY_PLL_CFG2_PREDIV_MSK) + 1; 67 68 return parent_rate / reg; 69 } 70 71 static int sun8i_phy_clk_set_rate(struct clk_hw *hw, unsigned long rate, 72 unsigned long parent_rate) 73 { 74 struct sun8i_phy_clk *priv = hw_to_phy_clk(hw); 75 unsigned long best_rate = 0; 76 u8 best_m = 0, m; 77 78 for (m = 1; m <= 16; m++) { 79 unsigned long tmp_rate = parent_rate / m; 80 81 if (tmp_rate > rate) 82 continue; 83 84 if (!best_rate || 85 (rate - tmp_rate) < (rate - best_rate)) { 86 best_rate = tmp_rate; 87 best_m = m; 88 } 89 } 90 91 regmap_update_bits(priv->phy->regs, SUN8I_HDMI_PHY_PLL_CFG2_REG, 92 SUN8I_HDMI_PHY_PLL_CFG2_PREDIV_MSK, 93 SUN8I_HDMI_PHY_PLL_CFG2_PREDIV(best_m)); 94 95 return 0; 96 } 97 98 static const struct clk_ops sun8i_phy_clk_ops = { 99 .determine_rate = sun8i_phy_clk_determine_rate, 100 .recalc_rate = sun8i_phy_clk_recalc_rate, 101 .set_rate = sun8i_phy_clk_set_rate, 102 }; 103 104 int sun8i_phy_clk_create(struct sun8i_hdmi_phy *phy, struct device *dev) 105 { 106 struct clk_init_data init; 107 struct sun8i_phy_clk *priv; 108 const char *parents[1]; 109 110 parents[0] = __clk_get_name(phy->clk_pll0); 111 if (!parents[0]) 112 return -ENODEV; 113 114 priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); 115 if (!priv) 116 return -ENOMEM; 117 118 init.name = "hdmi-phy-clk"; 119 init.ops = &sun8i_phy_clk_ops; 120 init.parent_names = parents; 121 init.num_parents = 1; 122 init.flags = CLK_SET_RATE_PARENT; 123 124 priv->phy = phy; 125 priv->hw.init = &init; 126 127 phy->clk_phy = devm_clk_register(dev, &priv->hw); 128 if (IS_ERR(phy->clk_phy)) 129 return PTR_ERR(phy->clk_phy); 130 131 return 0; 132 } 133