0001
0002
0003
0004
0005
0006
0007
0008
0009 #include <linux/clk-provider.h>
0010 #include <linux/io.h>
0011
0012 #include "sun4i_hdmi.h"
0013
0014 struct sun4i_tmds {
0015 struct clk_hw hw;
0016 struct sun4i_hdmi *hdmi;
0017
0018 u8 div_offset;
0019 };
0020
0021 static inline struct sun4i_tmds *hw_to_tmds(struct clk_hw *hw)
0022 {
0023 return container_of(hw, struct sun4i_tmds, hw);
0024 }
0025
0026
0027 static unsigned long sun4i_tmds_calc_divider(unsigned long rate,
0028 unsigned long parent_rate,
0029 u8 div_offset,
0030 u8 *div,
0031 bool *half)
0032 {
0033 unsigned long best_rate = 0;
0034 u8 best_m = 0, m;
0035 bool is_double = false;
0036
0037 for (m = div_offset ?: 1; m < (16 + div_offset); m++) {
0038 u8 d;
0039
0040 for (d = 1; d < 3; d++) {
0041 unsigned long tmp_rate;
0042
0043 tmp_rate = parent_rate / m / d;
0044
0045 if (tmp_rate > rate)
0046 continue;
0047
0048 if (!best_rate ||
0049 (rate - tmp_rate) < (rate - best_rate)) {
0050 best_rate = tmp_rate;
0051 best_m = m;
0052 is_double = (d == 2) ? true : false;
0053 }
0054 }
0055 }
0056
0057 if (div && half) {
0058 *div = best_m;
0059 *half = is_double;
0060 }
0061
0062 return best_rate;
0063 }
0064
0065
0066 static int sun4i_tmds_determine_rate(struct clk_hw *hw,
0067 struct clk_rate_request *req)
0068 {
0069 struct sun4i_tmds *tmds = hw_to_tmds(hw);
0070 struct clk_hw *parent = NULL;
0071 unsigned long best_parent = 0;
0072 unsigned long rate = req->rate;
0073 int best_div = 1, best_half = 1;
0074 int i, j, p;
0075
0076
0077
0078
0079
0080
0081
0082 for (p = 0; p < clk_hw_get_num_parents(hw); p++) {
0083 parent = clk_hw_get_parent_by_index(hw, p);
0084 if (!parent)
0085 continue;
0086
0087 for (i = 1; i < 3; i++) {
0088 for (j = tmds->div_offset ?: 1;
0089 j < (16 + tmds->div_offset); j++) {
0090 unsigned long ideal = rate * i * j;
0091 unsigned long rounded;
0092
0093 rounded = clk_hw_round_rate(parent, ideal);
0094
0095 if (rounded == ideal) {
0096 best_parent = rounded;
0097 best_half = i;
0098 best_div = j;
0099 goto out;
0100 }
0101
0102 if (!best_parent ||
0103 abs(rate - rounded / i / j) <
0104 abs(rate - best_parent / best_half /
0105 best_div)) {
0106 best_parent = rounded;
0107 best_half = i;
0108 best_div = j;
0109 }
0110 }
0111 }
0112 }
0113
0114 if (!parent)
0115 return -EINVAL;
0116
0117 out:
0118 req->rate = best_parent / best_half / best_div;
0119 req->best_parent_rate = best_parent;
0120 req->best_parent_hw = parent;
0121
0122 return 0;
0123 }
0124
0125 static unsigned long sun4i_tmds_recalc_rate(struct clk_hw *hw,
0126 unsigned long parent_rate)
0127 {
0128 struct sun4i_tmds *tmds = hw_to_tmds(hw);
0129 u32 reg;
0130
0131 reg = readl(tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
0132 if (reg & SUN4I_HDMI_PAD_CTRL1_HALVE_CLK)
0133 parent_rate /= 2;
0134
0135 reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
0136 reg = ((reg >> 4) & 0xf) + tmds->div_offset;
0137 if (!reg)
0138 reg = 1;
0139
0140 return parent_rate / reg;
0141 }
0142
0143 static int sun4i_tmds_set_rate(struct clk_hw *hw, unsigned long rate,
0144 unsigned long parent_rate)
0145 {
0146 struct sun4i_tmds *tmds = hw_to_tmds(hw);
0147 bool half;
0148 u32 reg;
0149 u8 div;
0150
0151 sun4i_tmds_calc_divider(rate, parent_rate, tmds->div_offset,
0152 &div, &half);
0153
0154 reg = readl(tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
0155 reg &= ~SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
0156 if (half)
0157 reg |= SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
0158 writel(reg, tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
0159
0160 reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
0161 reg &= ~SUN4I_HDMI_PLL_CTRL_DIV_MASK;
0162 writel(reg | SUN4I_HDMI_PLL_CTRL_DIV(div - tmds->div_offset),
0163 tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
0164
0165 return 0;
0166 }
0167
0168 static u8 sun4i_tmds_get_parent(struct clk_hw *hw)
0169 {
0170 struct sun4i_tmds *tmds = hw_to_tmds(hw);
0171 u32 reg;
0172
0173 reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG);
0174 return ((reg & SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK) >>
0175 SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_SHIFT);
0176 }
0177
0178 static int sun4i_tmds_set_parent(struct clk_hw *hw, u8 index)
0179 {
0180 struct sun4i_tmds *tmds = hw_to_tmds(hw);
0181 u32 reg;
0182
0183 if (index > 1)
0184 return -EINVAL;
0185
0186 reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG);
0187 reg &= ~SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK;
0188 writel(reg | SUN4I_HDMI_PLL_DBG0_TMDS_PARENT(index),
0189 tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG);
0190
0191 return 0;
0192 }
0193
0194 static const struct clk_ops sun4i_tmds_ops = {
0195 .determine_rate = sun4i_tmds_determine_rate,
0196 .recalc_rate = sun4i_tmds_recalc_rate,
0197 .set_rate = sun4i_tmds_set_rate,
0198
0199 .get_parent = sun4i_tmds_get_parent,
0200 .set_parent = sun4i_tmds_set_parent,
0201 };
0202
0203 int sun4i_tmds_create(struct sun4i_hdmi *hdmi)
0204 {
0205 struct clk_init_data init;
0206 struct sun4i_tmds *tmds;
0207 const char *parents[2];
0208
0209 parents[0] = __clk_get_name(hdmi->pll0_clk);
0210 if (!parents[0])
0211 return -ENODEV;
0212
0213 parents[1] = __clk_get_name(hdmi->pll1_clk);
0214 if (!parents[1])
0215 return -ENODEV;
0216
0217 tmds = devm_kzalloc(hdmi->dev, sizeof(*tmds), GFP_KERNEL);
0218 if (!tmds)
0219 return -ENOMEM;
0220
0221 init.name = "hdmi-tmds";
0222 init.ops = &sun4i_tmds_ops;
0223 init.parent_names = parents;
0224 init.num_parents = 2;
0225 init.flags = CLK_SET_RATE_PARENT;
0226
0227 tmds->hdmi = hdmi;
0228 tmds->hw.init = &init;
0229 tmds->div_offset = hdmi->variant->tmds_clk_div_offset;
0230
0231 hdmi->tmds_clk = devm_clk_register(hdmi->dev, &tmds->hw);
0232 if (IS_ERR(hdmi->tmds_clk))
0233 return PTR_ERR(hdmi->tmds_clk);
0234
0235 return 0;
0236 }