Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 // Copyright (C) 2014 Broadcom Corporation
0003 
0004 #include <linux/kernel.h>
0005 #include <linux/err.h>
0006 #include <linux/clk-provider.h>
0007 #include <linux/io.h>
0008 #include <linux/of.h>
0009 #include <linux/clkdev.h>
0010 #include <linux/of_address.h>
0011 #include <linux/delay.h>
0012 
0013 #include "clk-iproc.h"
0014 
0015 struct iproc_asiu;
0016 
0017 struct iproc_asiu_clk {
0018     struct clk_hw hw;
0019     const char *name;
0020     struct iproc_asiu *asiu;
0021     unsigned long rate;
0022     struct iproc_asiu_div div;
0023     struct iproc_asiu_gate gate;
0024 };
0025 
0026 struct iproc_asiu {
0027     void __iomem *div_base;
0028     void __iomem *gate_base;
0029 
0030     struct clk_hw_onecell_data *clk_data;
0031     struct iproc_asiu_clk *clks;
0032 };
0033 
0034 #define to_asiu_clk(hw) container_of(hw, struct iproc_asiu_clk, hw)
0035 
0036 static int iproc_asiu_clk_enable(struct clk_hw *hw)
0037 {
0038     struct iproc_asiu_clk *clk = to_asiu_clk(hw);
0039     struct iproc_asiu *asiu = clk->asiu;
0040     u32 val;
0041 
0042     /* some clocks at the ASIU level are always enabled */
0043     if (clk->gate.offset == IPROC_CLK_INVALID_OFFSET)
0044         return 0;
0045 
0046     val = readl(asiu->gate_base + clk->gate.offset);
0047     val |= (1 << clk->gate.en_shift);
0048     writel(val, asiu->gate_base + clk->gate.offset);
0049 
0050     return 0;
0051 }
0052 
0053 static void iproc_asiu_clk_disable(struct clk_hw *hw)
0054 {
0055     struct iproc_asiu_clk *clk = to_asiu_clk(hw);
0056     struct iproc_asiu *asiu = clk->asiu;
0057     u32 val;
0058 
0059     /* some clocks at the ASIU level are always enabled */
0060     if (clk->gate.offset == IPROC_CLK_INVALID_OFFSET)
0061         return;
0062 
0063     val = readl(asiu->gate_base + clk->gate.offset);
0064     val &= ~(1 << clk->gate.en_shift);
0065     writel(val, asiu->gate_base + clk->gate.offset);
0066 }
0067 
0068 static unsigned long iproc_asiu_clk_recalc_rate(struct clk_hw *hw,
0069                         unsigned long parent_rate)
0070 {
0071     struct iproc_asiu_clk *clk = to_asiu_clk(hw);
0072     struct iproc_asiu *asiu = clk->asiu;
0073     u32 val;
0074     unsigned int div_h, div_l;
0075 
0076     if (parent_rate == 0) {
0077         clk->rate = 0;
0078         return 0;
0079     }
0080 
0081     /* if clock divisor is not enabled, simply return parent rate */
0082     val = readl(asiu->div_base + clk->div.offset);
0083     if ((val & (1 << clk->div.en_shift)) == 0) {
0084         clk->rate = parent_rate;
0085         return parent_rate;
0086     }
0087 
0088     /* clock rate = parent rate / (high_div + 1) + (low_div + 1) */
0089     div_h = (val >> clk->div.high_shift) & bit_mask(clk->div.high_width);
0090     div_h++;
0091     div_l = (val >> clk->div.low_shift) & bit_mask(clk->div.low_width);
0092     div_l++;
0093 
0094     clk->rate = parent_rate / (div_h + div_l);
0095     pr_debug("%s: rate: %lu. parent rate: %lu div_h: %u div_l: %u\n",
0096          __func__, clk->rate, parent_rate, div_h, div_l);
0097 
0098     return clk->rate;
0099 }
0100 
0101 static long iproc_asiu_clk_round_rate(struct clk_hw *hw, unsigned long rate,
0102                       unsigned long *parent_rate)
0103 {
0104     unsigned int div;
0105 
0106     if (rate == 0 || *parent_rate == 0)
0107         return -EINVAL;
0108 
0109     if (rate == *parent_rate)
0110         return *parent_rate;
0111 
0112     div = DIV_ROUND_CLOSEST(*parent_rate, rate);
0113     if (div < 2)
0114         return *parent_rate;
0115 
0116     return *parent_rate / div;
0117 }
0118 
0119 static int iproc_asiu_clk_set_rate(struct clk_hw *hw, unsigned long rate,
0120                    unsigned long parent_rate)
0121 {
0122     struct iproc_asiu_clk *clk = to_asiu_clk(hw);
0123     struct iproc_asiu *asiu = clk->asiu;
0124     unsigned int div, div_h, div_l;
0125     u32 val;
0126 
0127     if (rate == 0 || parent_rate == 0)
0128         return -EINVAL;
0129 
0130     /* simply disable the divisor if one wants the same rate as parent */
0131     if (rate == parent_rate) {
0132         val = readl(asiu->div_base + clk->div.offset);
0133         val &= ~(1 << clk->div.en_shift);
0134         writel(val, asiu->div_base + clk->div.offset);
0135         return 0;
0136     }
0137 
0138     div = DIV_ROUND_CLOSEST(parent_rate, rate);
0139     if (div < 2)
0140         return -EINVAL;
0141 
0142     div_h = div_l = div >> 1;
0143     div_h--;
0144     div_l--;
0145 
0146     val = readl(asiu->div_base + clk->div.offset);
0147     val |= 1 << clk->div.en_shift;
0148     if (div_h) {
0149         val &= ~(bit_mask(clk->div.high_width)
0150              << clk->div.high_shift);
0151         val |= div_h << clk->div.high_shift;
0152     } else {
0153         val &= ~(bit_mask(clk->div.high_width)
0154              << clk->div.high_shift);
0155     }
0156     if (div_l) {
0157         val &= ~(bit_mask(clk->div.low_width) << clk->div.low_shift);
0158         val |= div_l << clk->div.low_shift;
0159     } else {
0160         val &= ~(bit_mask(clk->div.low_width) << clk->div.low_shift);
0161     }
0162     writel(val, asiu->div_base + clk->div.offset);
0163 
0164     return 0;
0165 }
0166 
0167 static const struct clk_ops iproc_asiu_ops = {
0168     .enable = iproc_asiu_clk_enable,
0169     .disable = iproc_asiu_clk_disable,
0170     .recalc_rate = iproc_asiu_clk_recalc_rate,
0171     .round_rate = iproc_asiu_clk_round_rate,
0172     .set_rate = iproc_asiu_clk_set_rate,
0173 };
0174 
0175 void __init iproc_asiu_setup(struct device_node *node,
0176                  const struct iproc_asiu_div *div,
0177                  const struct iproc_asiu_gate *gate,
0178                  unsigned int num_clks)
0179 {
0180     int i, ret;
0181     struct iproc_asiu *asiu;
0182 
0183     if (WARN_ON(!gate || !div))
0184         return;
0185 
0186     asiu = kzalloc(sizeof(*asiu), GFP_KERNEL);
0187     if (WARN_ON(!asiu))
0188         return;
0189 
0190     asiu->clk_data = kzalloc(struct_size(asiu->clk_data, hws, num_clks),
0191                  GFP_KERNEL);
0192     if (WARN_ON(!asiu->clk_data))
0193         goto err_clks;
0194     asiu->clk_data->num = num_clks;
0195 
0196     asiu->clks = kcalloc(num_clks, sizeof(*asiu->clks), GFP_KERNEL);
0197     if (WARN_ON(!asiu->clks))
0198         goto err_asiu_clks;
0199 
0200     asiu->div_base = of_iomap(node, 0);
0201     if (WARN_ON(!asiu->div_base))
0202         goto err_iomap_div;
0203 
0204     asiu->gate_base = of_iomap(node, 1);
0205     if (WARN_ON(!asiu->gate_base))
0206         goto err_iomap_gate;
0207 
0208     for (i = 0; i < num_clks; i++) {
0209         struct clk_init_data init;
0210         const char *parent_name;
0211         struct iproc_asiu_clk *asiu_clk;
0212         const char *clk_name;
0213 
0214         ret = of_property_read_string_index(node, "clock-output-names",
0215                             i, &clk_name);
0216         if (WARN_ON(ret))
0217             goto err_clk_register;
0218 
0219         asiu_clk = &asiu->clks[i];
0220         asiu_clk->name = clk_name;
0221         asiu_clk->asiu = asiu;
0222         asiu_clk->div = div[i];
0223         asiu_clk->gate = gate[i];
0224         init.name = clk_name;
0225         init.ops = &iproc_asiu_ops;
0226         init.flags = 0;
0227         parent_name = of_clk_get_parent_name(node, 0);
0228         init.parent_names = (parent_name ? &parent_name : NULL);
0229         init.num_parents = (parent_name ? 1 : 0);
0230         asiu_clk->hw.init = &init;
0231 
0232         ret = clk_hw_register(NULL, &asiu_clk->hw);
0233         if (WARN_ON(ret))
0234             goto err_clk_register;
0235         asiu->clk_data->hws[i] = &asiu_clk->hw;
0236     }
0237 
0238     ret = of_clk_add_hw_provider(node, of_clk_hw_onecell_get,
0239                      asiu->clk_data);
0240     if (WARN_ON(ret))
0241         goto err_clk_register;
0242 
0243     return;
0244 
0245 err_clk_register:
0246     while (--i >= 0)
0247         clk_hw_unregister(asiu->clk_data->hws[i]);
0248     iounmap(asiu->gate_base);
0249 
0250 err_iomap_gate:
0251     iounmap(asiu->div_base);
0252 
0253 err_iomap_div:
0254     kfree(asiu->clks);
0255 
0256 err_asiu_clks:
0257     kfree(asiu->clk_data);
0258 
0259 err_clks:
0260     kfree(asiu);
0261 }