Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0
0002 /*
0003  * Copyright (C) 2015 Chen-Yu Tsai
0004  *
0005  * Chen-Yu Tsai <wens@csie.org>
0006  *
0007  * Allwinner A80 CPUS clock driver
0008  *
0009  */
0010 
0011 #include <linux/clk.h>
0012 #include <linux/clk-provider.h>
0013 #include <linux/io.h>
0014 #include <linux/slab.h>
0015 #include <linux/spinlock.h>
0016 #include <linux/of.h>
0017 #include <linux/of_address.h>
0018 
0019 static DEFINE_SPINLOCK(sun9i_a80_cpus_lock);
0020 
0021 /**
0022  * sun9i_a80_cpus_clk_setup() - Setup function for a80 cpus composite clk
0023  */
0024 
0025 #define SUN9I_CPUS_MAX_PARENTS      4
0026 #define SUN9I_CPUS_MUX_PARENT_PLL4  3
0027 #define SUN9I_CPUS_MUX_SHIFT        16
0028 #define SUN9I_CPUS_MUX_MASK     GENMASK(17, 16)
0029 #define SUN9I_CPUS_MUX_GET_PARENT(reg)  ((reg & SUN9I_CPUS_MUX_MASK) >> \
0030                         SUN9I_CPUS_MUX_SHIFT)
0031 
0032 #define SUN9I_CPUS_DIV_SHIFT        4
0033 #define SUN9I_CPUS_DIV_MASK     GENMASK(5, 4)
0034 #define SUN9I_CPUS_DIV_GET(reg)     ((reg & SUN9I_CPUS_DIV_MASK) >> \
0035                         SUN9I_CPUS_DIV_SHIFT)
0036 #define SUN9I_CPUS_DIV_SET(reg, div)    ((reg & ~SUN9I_CPUS_DIV_MASK) | \
0037                         (div << SUN9I_CPUS_DIV_SHIFT))
0038 #define SUN9I_CPUS_PLL4_DIV_SHIFT   8
0039 #define SUN9I_CPUS_PLL4_DIV_MASK    GENMASK(12, 8)
0040 #define SUN9I_CPUS_PLL4_DIV_GET(reg)    ((reg & SUN9I_CPUS_PLL4_DIV_MASK) >> \
0041                         SUN9I_CPUS_PLL4_DIV_SHIFT)
0042 #define SUN9I_CPUS_PLL4_DIV_SET(reg, div) ((reg & ~SUN9I_CPUS_PLL4_DIV_MASK) | \
0043                         (div << SUN9I_CPUS_PLL4_DIV_SHIFT))
0044 
0045 struct sun9i_a80_cpus_clk {
0046     struct clk_hw hw;
0047     void __iomem *reg;
0048 };
0049 
0050 #define to_sun9i_a80_cpus_clk(_hw) container_of(_hw, struct sun9i_a80_cpus_clk, hw)
0051 
0052 static unsigned long sun9i_a80_cpus_clk_recalc_rate(struct clk_hw *hw,
0053                             unsigned long parent_rate)
0054 {
0055     struct sun9i_a80_cpus_clk *cpus = to_sun9i_a80_cpus_clk(hw);
0056     unsigned long rate;
0057     u32 reg;
0058 
0059     /* Fetch the register value */
0060     reg = readl(cpus->reg);
0061 
0062     /* apply pre-divider first if parent is pll4 */
0063     if (SUN9I_CPUS_MUX_GET_PARENT(reg) == SUN9I_CPUS_MUX_PARENT_PLL4)
0064         parent_rate /= SUN9I_CPUS_PLL4_DIV_GET(reg) + 1;
0065 
0066     /* clk divider */
0067     rate = parent_rate / (SUN9I_CPUS_DIV_GET(reg) + 1);
0068 
0069     return rate;
0070 }
0071 
0072 static long sun9i_a80_cpus_clk_round(unsigned long rate, u8 *divp, u8 *pre_divp,
0073                      u8 parent, unsigned long parent_rate)
0074 {
0075     u8 div, pre_div = 1;
0076 
0077     /*
0078      * clock can only divide, so we will never be able to achieve
0079      * frequencies higher than the parent frequency
0080      */
0081     if (parent_rate && rate > parent_rate)
0082         rate = parent_rate;
0083 
0084     div = DIV_ROUND_UP(parent_rate, rate);
0085 
0086     /* calculate pre-divider if parent is pll4 */
0087     if (parent == SUN9I_CPUS_MUX_PARENT_PLL4 && div > 4) {
0088         /* pre-divider is 1 ~ 32 */
0089         if (div < 32) {
0090             pre_div = div;
0091             div = 1;
0092         } else if (div < 64) {
0093             pre_div = DIV_ROUND_UP(div, 2);
0094             div = 2;
0095         } else if (div < 96) {
0096             pre_div = DIV_ROUND_UP(div, 3);
0097             div = 3;
0098         } else {
0099             pre_div = DIV_ROUND_UP(div, 4);
0100             div = 4;
0101         }
0102     }
0103 
0104     /* we were asked to pass back divider values */
0105     if (divp) {
0106         *divp = div - 1;
0107         *pre_divp = pre_div - 1;
0108     }
0109 
0110     return parent_rate / pre_div / div;
0111 }
0112 
0113 static int sun9i_a80_cpus_clk_determine_rate(struct clk_hw *clk,
0114                          struct clk_rate_request *req)
0115 {
0116     struct clk_hw *parent, *best_parent = NULL;
0117     int i, num_parents;
0118     unsigned long parent_rate, best = 0, child_rate, best_child_rate = 0;
0119     unsigned long rate = req->rate;
0120 
0121     /* find the parent that can help provide the fastest rate <= rate */
0122     num_parents = clk_hw_get_num_parents(clk);
0123     for (i = 0; i < num_parents; i++) {
0124         parent = clk_hw_get_parent_by_index(clk, i);
0125         if (!parent)
0126             continue;
0127         if (clk_hw_get_flags(clk) & CLK_SET_RATE_PARENT)
0128             parent_rate = clk_hw_round_rate(parent, rate);
0129         else
0130             parent_rate = clk_hw_get_rate(parent);
0131 
0132         child_rate = sun9i_a80_cpus_clk_round(rate, NULL, NULL, i,
0133                               parent_rate);
0134 
0135         if (child_rate <= rate && child_rate > best_child_rate) {
0136             best_parent = parent;
0137             best = parent_rate;
0138             best_child_rate = child_rate;
0139         }
0140     }
0141 
0142     if (!best_parent)
0143         return -EINVAL;
0144 
0145     req->best_parent_hw = best_parent;
0146     req->best_parent_rate = best;
0147     req->rate = best_child_rate;
0148 
0149     return 0;
0150 }
0151 
0152 static int sun9i_a80_cpus_clk_set_rate(struct clk_hw *hw, unsigned long rate,
0153                        unsigned long parent_rate)
0154 {
0155     struct sun9i_a80_cpus_clk *cpus = to_sun9i_a80_cpus_clk(hw);
0156     unsigned long flags;
0157     u8 div, pre_div, parent;
0158     u32 reg;
0159 
0160     spin_lock_irqsave(&sun9i_a80_cpus_lock, flags);
0161 
0162     reg = readl(cpus->reg);
0163 
0164     /* need to know which parent is used to apply pre-divider */
0165     parent = SUN9I_CPUS_MUX_GET_PARENT(reg);
0166     sun9i_a80_cpus_clk_round(rate, &div, &pre_div, parent, parent_rate);
0167 
0168     reg = SUN9I_CPUS_DIV_SET(reg, div);
0169     reg = SUN9I_CPUS_PLL4_DIV_SET(reg, pre_div);
0170     writel(reg, cpus->reg);
0171 
0172     spin_unlock_irqrestore(&sun9i_a80_cpus_lock, flags);
0173 
0174     return 0;
0175 }
0176 
0177 static const struct clk_ops sun9i_a80_cpus_clk_ops = {
0178     .determine_rate = sun9i_a80_cpus_clk_determine_rate,
0179     .recalc_rate    = sun9i_a80_cpus_clk_recalc_rate,
0180     .set_rate   = sun9i_a80_cpus_clk_set_rate,
0181 };
0182 
0183 static void sun9i_a80_cpus_setup(struct device_node *node)
0184 {
0185     const char *clk_name = node->name;
0186     const char *parents[SUN9I_CPUS_MAX_PARENTS];
0187     struct resource res;
0188     struct sun9i_a80_cpus_clk *cpus;
0189     struct clk_mux *mux;
0190     struct clk *clk;
0191     int ret;
0192 
0193     cpus = kzalloc(sizeof(*cpus), GFP_KERNEL);
0194     if (!cpus)
0195         return;
0196 
0197     cpus->reg = of_io_request_and_map(node, 0, of_node_full_name(node));
0198     if (IS_ERR(cpus->reg))
0199         goto err_free_cpus;
0200 
0201     of_property_read_string(node, "clock-output-names", &clk_name);
0202 
0203     /* we have a mux, we will have >1 parents */
0204     ret = of_clk_parent_fill(node, parents, SUN9I_CPUS_MAX_PARENTS);
0205 
0206     mux = kzalloc(sizeof(*mux), GFP_KERNEL);
0207     if (!mux)
0208         goto err_unmap;
0209 
0210     /* set up clock properties */
0211     mux->reg = cpus->reg;
0212     mux->shift = SUN9I_CPUS_MUX_SHIFT;
0213     /* un-shifted mask is what mux_clk expects */
0214     mux->mask = SUN9I_CPUS_MUX_MASK >> SUN9I_CPUS_MUX_SHIFT;
0215     mux->lock = &sun9i_a80_cpus_lock;
0216 
0217     clk = clk_register_composite(NULL, clk_name, parents, ret,
0218                      &mux->hw, &clk_mux_ops,
0219                      &cpus->hw, &sun9i_a80_cpus_clk_ops,
0220                      NULL, NULL, 0);
0221     if (IS_ERR(clk))
0222         goto err_free_mux;
0223 
0224     ret = of_clk_add_provider(node, of_clk_src_simple_get, clk);
0225     if (ret)
0226         goto err_unregister;
0227 
0228     return;
0229 
0230 err_unregister:
0231     clk_unregister(clk);
0232 err_free_mux:
0233     kfree(mux);
0234 err_unmap:
0235     iounmap(cpus->reg);
0236     of_address_to_resource(node, 0, &res);
0237     release_mem_region(res.start, resource_size(&res));
0238 err_free_cpus:
0239     kfree(cpus);
0240 }
0241 CLK_OF_DECLARE(sun9i_a80_cpus, "allwinner,sun9i-a80-cpus-clk",
0242            sun9i_a80_cpus_setup);