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/regmap.h>
0011 
0012 #include "sun4i_tcon.h"
0013 #include "sun4i_dotclock.h"
0014 
0015 struct sun4i_dclk {
0016     struct clk_hw       hw;
0017     struct regmap       *regmap;
0018     struct sun4i_tcon   *tcon;
0019 };
0020 
0021 static inline struct sun4i_dclk *hw_to_dclk(struct clk_hw *hw)
0022 {
0023     return container_of(hw, struct sun4i_dclk, hw);
0024 }
0025 
0026 static void sun4i_dclk_disable(struct clk_hw *hw)
0027 {
0028     struct sun4i_dclk *dclk = hw_to_dclk(hw);
0029 
0030     regmap_update_bits(dclk->regmap, SUN4I_TCON0_DCLK_REG,
0031                BIT(SUN4I_TCON0_DCLK_GATE_BIT), 0);
0032 }
0033 
0034 static int sun4i_dclk_enable(struct clk_hw *hw)
0035 {
0036     struct sun4i_dclk *dclk = hw_to_dclk(hw);
0037 
0038     return regmap_update_bits(dclk->regmap, SUN4I_TCON0_DCLK_REG,
0039                   BIT(SUN4I_TCON0_DCLK_GATE_BIT),
0040                   BIT(SUN4I_TCON0_DCLK_GATE_BIT));
0041 }
0042 
0043 static int sun4i_dclk_is_enabled(struct clk_hw *hw)
0044 {
0045     struct sun4i_dclk *dclk = hw_to_dclk(hw);
0046     u32 val;
0047 
0048     regmap_read(dclk->regmap, SUN4I_TCON0_DCLK_REG, &val);
0049 
0050     return val & BIT(SUN4I_TCON0_DCLK_GATE_BIT);
0051 }
0052 
0053 static unsigned long sun4i_dclk_recalc_rate(struct clk_hw *hw,
0054                         unsigned long parent_rate)
0055 {
0056     struct sun4i_dclk *dclk = hw_to_dclk(hw);
0057     u32 val;
0058 
0059     regmap_read(dclk->regmap, SUN4I_TCON0_DCLK_REG, &val);
0060 
0061     val >>= SUN4I_TCON0_DCLK_DIV_SHIFT;
0062     val &= (1 << SUN4I_TCON0_DCLK_DIV_WIDTH) - 1;
0063 
0064     if (!val)
0065         val = 1;
0066 
0067     return parent_rate / val;
0068 }
0069 
0070 static long sun4i_dclk_round_rate(struct clk_hw *hw, unsigned long rate,
0071                   unsigned long *parent_rate)
0072 {
0073     struct sun4i_dclk *dclk = hw_to_dclk(hw);
0074     struct sun4i_tcon *tcon = dclk->tcon;
0075     unsigned long best_parent = 0;
0076     u8 best_div = 1;
0077     int i;
0078 
0079     for (i = tcon->dclk_min_div; i <= tcon->dclk_max_div; i++) {
0080         u64 ideal = (u64)rate * i;
0081         unsigned long rounded;
0082 
0083         /*
0084          * ideal has overflowed the max value that can be stored in an
0085          * unsigned long, and every clk operation we might do on a
0086          * truncated u64 value will give us incorrect results.
0087          * Let's just stop there since bigger dividers will result in
0088          * the same overflow issue.
0089          */
0090         if (ideal > ULONG_MAX)
0091             goto out;
0092 
0093         rounded = clk_hw_round_rate(clk_hw_get_parent(hw),
0094                         ideal);
0095 
0096         if (rounded == ideal) {
0097             best_parent = rounded;
0098             best_div = i;
0099             goto out;
0100         }
0101 
0102         if (abs(rate - rounded / i) <
0103             abs(rate - best_parent / best_div)) {
0104             best_parent = rounded;
0105             best_div = i;
0106         }
0107     }
0108 
0109 out:
0110     *parent_rate = best_parent;
0111 
0112     return best_parent / best_div;
0113 }
0114 
0115 static int sun4i_dclk_set_rate(struct clk_hw *hw, unsigned long rate,
0116                    unsigned long parent_rate)
0117 {
0118     struct sun4i_dclk *dclk = hw_to_dclk(hw);
0119     u8 div = parent_rate / rate;
0120 
0121     return regmap_update_bits(dclk->regmap, SUN4I_TCON0_DCLK_REG,
0122                   GENMASK(6, 0), div);
0123 }
0124 
0125 static int sun4i_dclk_get_phase(struct clk_hw *hw)
0126 {
0127     struct sun4i_dclk *dclk = hw_to_dclk(hw);
0128     u32 val;
0129 
0130     regmap_read(dclk->regmap, SUN4I_TCON0_IO_POL_REG, &val);
0131 
0132     val >>= 28;
0133     val &= 3;
0134 
0135     return val * 120;
0136 }
0137 
0138 static int sun4i_dclk_set_phase(struct clk_hw *hw, int degrees)
0139 {
0140     struct sun4i_dclk *dclk = hw_to_dclk(hw);
0141     u32 val = degrees / 120;
0142 
0143     val <<= 28;
0144 
0145     regmap_update_bits(dclk->regmap, SUN4I_TCON0_IO_POL_REG,
0146                GENMASK(29, 28),
0147                val);
0148 
0149     return 0;
0150 }
0151 
0152 static const struct clk_ops sun4i_dclk_ops = {
0153     .disable    = sun4i_dclk_disable,
0154     .enable     = sun4i_dclk_enable,
0155     .is_enabled = sun4i_dclk_is_enabled,
0156 
0157     .recalc_rate    = sun4i_dclk_recalc_rate,
0158     .round_rate = sun4i_dclk_round_rate,
0159     .set_rate   = sun4i_dclk_set_rate,
0160 
0161     .get_phase  = sun4i_dclk_get_phase,
0162     .set_phase  = sun4i_dclk_set_phase,
0163 };
0164 
0165 int sun4i_dclk_create(struct device *dev, struct sun4i_tcon *tcon)
0166 {
0167     const char *clk_name, *parent_name;
0168     struct clk_init_data init;
0169     struct sun4i_dclk *dclk;
0170     int ret;
0171 
0172     parent_name = __clk_get_name(tcon->sclk0);
0173     ret = of_property_read_string_index(dev->of_node,
0174                         "clock-output-names", 0,
0175                         &clk_name);
0176     if (ret)
0177         return ret;
0178 
0179     dclk = devm_kzalloc(dev, sizeof(*dclk), GFP_KERNEL);
0180     if (!dclk)
0181         return -ENOMEM;
0182     dclk->tcon = tcon;
0183 
0184     init.name = clk_name;
0185     init.ops = &sun4i_dclk_ops;
0186     init.parent_names = &parent_name;
0187     init.num_parents = 1;
0188     init.flags = CLK_SET_RATE_PARENT;
0189 
0190     dclk->regmap = tcon->regs;
0191     dclk->hw.init = &init;
0192 
0193     tcon->dclk = clk_register(dev, &dclk->hw);
0194     if (IS_ERR(tcon->dclk))
0195         return PTR_ERR(tcon->dclk);
0196 
0197     return 0;
0198 }
0199 EXPORT_SYMBOL(sun4i_dclk_create);
0200 
0201 int sun4i_dclk_free(struct sun4i_tcon *tcon)
0202 {
0203     clk_unregister(tcon->dclk);
0204     return 0;
0205 }
0206 EXPORT_SYMBOL(sun4i_dclk_free);