Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-or-later
0002 /*
0003  * Copyright (C) 2016 Free Electrons
0004  * Copyright (C) 2016 NextThing Co
0005  *
0006  * Maxime Ripard <maxime.ripard@free-electrons.com>
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      * We only consider PLL3, since the TCON is very likely to be
0078      * clocked from it, and to have the same rate than our HDMI
0079      * clock, so we should not need to do anything.
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 }