1 /* 2 * Copyright (C) 2016 Free Electrons 3 * Copyright (C) 2016 NextThing Co 4 * 5 * Maxime Ripard <maxime.ripard@free-electrons.com> 6 * 7 * This program is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU General Public License as 9 * published by the Free Software Foundation; either version 2 of 10 * the License, or (at your option) any later version. 11 */ 12 13 #include <linux/clk-provider.h> 14 15 #include "sun4i_tcon.h" 16 #include "sun4i_hdmi.h" 17 18 struct sun4i_tmds { 19 struct clk_hw hw; 20 struct sun4i_hdmi *hdmi; 21 }; 22 23 static inline struct sun4i_tmds *hw_to_tmds(struct clk_hw *hw) 24 { 25 return container_of(hw, struct sun4i_tmds, hw); 26 } 27 28 29 static unsigned long sun4i_tmds_calc_divider(unsigned long rate, 30 unsigned long parent_rate, 31 u8 *div, 32 bool *half) 33 { 34 unsigned long best_rate = 0; 35 u8 best_m = 0, m; 36 bool is_double; 37 38 for (m = 1; m < 16; m++) { 39 u8 d; 40 41 for (d = 1; d < 3; d++) { 42 unsigned long tmp_rate; 43 44 tmp_rate = parent_rate / m / d; 45 46 if (tmp_rate > rate) 47 continue; 48 49 if (!best_rate || 50 (rate - tmp_rate) < (rate - best_rate)) { 51 best_rate = tmp_rate; 52 best_m = m; 53 is_double = d; 54 } 55 } 56 } 57 58 if (div && half) { 59 *div = best_m; 60 *half = is_double; 61 } 62 63 return best_rate; 64 } 65 66 67 static int sun4i_tmds_determine_rate(struct clk_hw *hw, 68 struct clk_rate_request *req) 69 { 70 struct clk_hw *parent; 71 unsigned long best_parent = 0; 72 unsigned long rate = req->rate; 73 int best_div = 1, best_half = 1; 74 int i, j; 75 76 /* 77 * We only consider PLL3, since the TCON is very likely to be 78 * clocked from it, and to have the same rate than our HDMI 79 * clock, so we should not need to do anything. 80 */ 81 82 parent = clk_hw_get_parent_by_index(hw, 0); 83 if (!parent) 84 return -EINVAL; 85 86 for (i = 1; i < 3; i++) { 87 for (j = 1; j < 16; j++) { 88 unsigned long ideal = rate * i * j; 89 unsigned long rounded; 90 91 rounded = clk_hw_round_rate(parent, ideal); 92 93 if (rounded == ideal) { 94 best_parent = rounded; 95 best_half = i; 96 best_div = j; 97 goto out; 98 } 99 100 if (abs(rate - rounded / i) < 101 abs(rate - best_parent / best_div)) { 102 best_parent = rounded; 103 best_div = i; 104 } 105 } 106 } 107 108 out: 109 req->rate = best_parent / best_half / best_div; 110 req->best_parent_rate = best_parent; 111 req->best_parent_hw = parent; 112 113 return 0; 114 } 115 116 static unsigned long sun4i_tmds_recalc_rate(struct clk_hw *hw, 117 unsigned long parent_rate) 118 { 119 struct sun4i_tmds *tmds = hw_to_tmds(hw); 120 u32 reg; 121 122 reg = readl(tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG); 123 if (reg & SUN4I_HDMI_PAD_CTRL1_HALVE_CLK) 124 parent_rate /= 2; 125 126 reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG); 127 reg = (reg >> 4) & 0xf; 128 if (!reg) 129 reg = 1; 130 131 return parent_rate / reg; 132 } 133 134 static int sun4i_tmds_set_rate(struct clk_hw *hw, unsigned long rate, 135 unsigned long parent_rate) 136 { 137 struct sun4i_tmds *tmds = hw_to_tmds(hw); 138 bool half; 139 u32 reg; 140 u8 div; 141 142 sun4i_tmds_calc_divider(rate, parent_rate, &div, &half); 143 144 reg = readl(tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG); 145 reg &= ~SUN4I_HDMI_PAD_CTRL1_HALVE_CLK; 146 if (half) 147 reg |= SUN4I_HDMI_PAD_CTRL1_HALVE_CLK; 148 writel(reg, tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG); 149 150 reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG); 151 reg &= ~SUN4I_HDMI_PLL_CTRL_DIV_MASK; 152 writel(reg | SUN4I_HDMI_PLL_CTRL_DIV(div), 153 tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG); 154 155 return 0; 156 } 157 158 static u8 sun4i_tmds_get_parent(struct clk_hw *hw) 159 { 160 struct sun4i_tmds *tmds = hw_to_tmds(hw); 161 u32 reg; 162 163 reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG); 164 return ((reg & SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK) >> 165 SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_SHIFT); 166 } 167 168 static int sun4i_tmds_set_parent(struct clk_hw *hw, u8 index) 169 { 170 struct sun4i_tmds *tmds = hw_to_tmds(hw); 171 u32 reg; 172 173 if (index > 1) 174 return -EINVAL; 175 176 reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG); 177 reg &= ~SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK; 178 writel(reg | SUN4I_HDMI_PLL_DBG0_TMDS_PARENT(index), 179 tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG); 180 181 return 0; 182 } 183 184 static const struct clk_ops sun4i_tmds_ops = { 185 .determine_rate = sun4i_tmds_determine_rate, 186 .recalc_rate = sun4i_tmds_recalc_rate, 187 .set_rate = sun4i_tmds_set_rate, 188 189 .get_parent = sun4i_tmds_get_parent, 190 .set_parent = sun4i_tmds_set_parent, 191 }; 192 193 int sun4i_tmds_create(struct sun4i_hdmi *hdmi) 194 { 195 struct clk_init_data init; 196 struct sun4i_tmds *tmds; 197 const char *parents[2]; 198 199 parents[0] = __clk_get_name(hdmi->pll0_clk); 200 if (!parents[0]) 201 return -ENODEV; 202 203 parents[1] = __clk_get_name(hdmi->pll1_clk); 204 if (!parents[1]) 205 return -ENODEV; 206 207 tmds = devm_kzalloc(hdmi->dev, sizeof(*tmds), GFP_KERNEL); 208 if (!tmds) 209 return -ENOMEM; 210 211 init.name = "hdmi-tmds"; 212 init.ops = &sun4i_tmds_ops; 213 init.parent_names = parents; 214 init.num_parents = 2; 215 init.flags = CLK_SET_RATE_PARENT; 216 217 tmds->hdmi = hdmi; 218 tmds->hw.init = &init; 219 220 hdmi->tmds_clk = devm_clk_register(hdmi->dev, &tmds->hw); 221 if (IS_ERR(hdmi->tmds_clk)) 222 return PTR_ERR(hdmi->tmds_clk); 223 224 return 0; 225 } 226