0001
0002
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
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
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
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
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
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 }