0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018
0019
0020
0021
0022
0023
0024
0025
0026
0027
0028
0029
0030
0031
0032 #include <linux/of.h>
0033 #include <linux/slab.h>
0034 #include <linux/io.h>
0035 #include <linux/clk.h>
0036 #include <linux/clk-provider.h>
0037 #include "clk.h"
0038
0039
0040
0041
0042
0043
0044
0045
0046
0047
0048
0049
0050
0051
0052 struct rockchip_cpuclk {
0053 struct clk_hw hw;
0054 struct clk *alt_parent;
0055 void __iomem *reg_base;
0056 struct notifier_block clk_nb;
0057 unsigned int rate_count;
0058 struct rockchip_cpuclk_rate_table *rate_table;
0059 const struct rockchip_cpuclk_reg_data *reg_data;
0060 spinlock_t *lock;
0061 };
0062
0063 #define to_rockchip_cpuclk_hw(hw) container_of(hw, struct rockchip_cpuclk, hw)
0064 #define to_rockchip_cpuclk_nb(nb) \
0065 container_of(nb, struct rockchip_cpuclk, clk_nb)
0066
0067 static const struct rockchip_cpuclk_rate_table *rockchip_get_cpuclk_settings(
0068 struct rockchip_cpuclk *cpuclk, unsigned long rate)
0069 {
0070 const struct rockchip_cpuclk_rate_table *rate_table =
0071 cpuclk->rate_table;
0072 int i;
0073
0074 for (i = 0; i < cpuclk->rate_count; i++) {
0075 if (rate == rate_table[i].prate)
0076 return &rate_table[i];
0077 }
0078
0079 return NULL;
0080 }
0081
0082 static unsigned long rockchip_cpuclk_recalc_rate(struct clk_hw *hw,
0083 unsigned long parent_rate)
0084 {
0085 struct rockchip_cpuclk *cpuclk = to_rockchip_cpuclk_hw(hw);
0086 const struct rockchip_cpuclk_reg_data *reg_data = cpuclk->reg_data;
0087 u32 clksel0 = readl_relaxed(cpuclk->reg_base + reg_data->core_reg[0]);
0088
0089 clksel0 >>= reg_data->div_core_shift[0];
0090 clksel0 &= reg_data->div_core_mask[0];
0091 return parent_rate / (clksel0 + 1);
0092 }
0093
0094 static const struct clk_ops rockchip_cpuclk_ops = {
0095 .recalc_rate = rockchip_cpuclk_recalc_rate,
0096 };
0097
0098 static void rockchip_cpuclk_set_dividers(struct rockchip_cpuclk *cpuclk,
0099 const struct rockchip_cpuclk_rate_table *rate)
0100 {
0101 int i;
0102
0103
0104 for (i = 0; i < ARRAY_SIZE(rate->divs); i++) {
0105 const struct rockchip_cpuclk_clksel *clksel = &rate->divs[i];
0106
0107 if (!clksel->reg)
0108 continue;
0109
0110 pr_debug("%s: setting reg 0x%x to 0x%x\n",
0111 __func__, clksel->reg, clksel->val);
0112 writel(clksel->val, cpuclk->reg_base + clksel->reg);
0113 }
0114 }
0115
0116 static int rockchip_cpuclk_pre_rate_change(struct rockchip_cpuclk *cpuclk,
0117 struct clk_notifier_data *ndata)
0118 {
0119 const struct rockchip_cpuclk_reg_data *reg_data = cpuclk->reg_data;
0120 const struct rockchip_cpuclk_rate_table *rate;
0121 unsigned long alt_prate, alt_div;
0122 unsigned long flags;
0123 int i = 0;
0124
0125
0126 rate = rockchip_get_cpuclk_settings(cpuclk, ndata->new_rate);
0127 if (!rate) {
0128 pr_err("%s: Invalid rate : %lu for cpuclk\n",
0129 __func__, ndata->new_rate);
0130 return -EINVAL;
0131 }
0132
0133 alt_prate = clk_get_rate(cpuclk->alt_parent);
0134
0135 spin_lock_irqsave(cpuclk->lock, flags);
0136
0137
0138
0139
0140
0141
0142
0143 if (alt_prate > ndata->old_rate) {
0144
0145 alt_div = DIV_ROUND_UP(alt_prate, ndata->old_rate) - 1;
0146 if (alt_div > reg_data->div_core_mask[0]) {
0147 pr_warn("%s: limiting alt-divider %lu to %d\n",
0148 __func__, alt_div, reg_data->div_core_mask[0]);
0149 alt_div = reg_data->div_core_mask[0];
0150 }
0151
0152
0153
0154
0155
0156
0157
0158
0159 pr_debug("%s: setting div %lu as alt-rate %lu > old-rate %lu\n",
0160 __func__, alt_div, alt_prate, ndata->old_rate);
0161
0162 for (i = 0; i < reg_data->num_cores; i++) {
0163 writel(HIWORD_UPDATE(alt_div, reg_data->div_core_mask[i],
0164 reg_data->div_core_shift[i]),
0165 cpuclk->reg_base + reg_data->core_reg[i]);
0166 }
0167 }
0168
0169 writel(HIWORD_UPDATE(reg_data->mux_core_alt,
0170 reg_data->mux_core_mask,
0171 reg_data->mux_core_shift),
0172 cpuclk->reg_base + reg_data->core_reg[0]);
0173
0174 spin_unlock_irqrestore(cpuclk->lock, flags);
0175 return 0;
0176 }
0177
0178 static int rockchip_cpuclk_post_rate_change(struct rockchip_cpuclk *cpuclk,
0179 struct clk_notifier_data *ndata)
0180 {
0181 const struct rockchip_cpuclk_reg_data *reg_data = cpuclk->reg_data;
0182 const struct rockchip_cpuclk_rate_table *rate;
0183 unsigned long flags;
0184 int i = 0;
0185
0186 rate = rockchip_get_cpuclk_settings(cpuclk, ndata->new_rate);
0187 if (!rate) {
0188 pr_err("%s: Invalid rate : %lu for cpuclk\n",
0189 __func__, ndata->new_rate);
0190 return -EINVAL;
0191 }
0192
0193 spin_lock_irqsave(cpuclk->lock, flags);
0194
0195 if (ndata->old_rate < ndata->new_rate)
0196 rockchip_cpuclk_set_dividers(cpuclk, rate);
0197
0198
0199
0200
0201
0202
0203
0204
0205 writel(HIWORD_UPDATE(reg_data->mux_core_main,
0206 reg_data->mux_core_mask,
0207 reg_data->mux_core_shift),
0208 cpuclk->reg_base + reg_data->core_reg[0]);
0209
0210
0211 for (i = 0; i < reg_data->num_cores; i++) {
0212 writel(HIWORD_UPDATE(0, reg_data->div_core_mask[i],
0213 reg_data->div_core_shift[i]),
0214 cpuclk->reg_base + reg_data->core_reg[i]);
0215 }
0216
0217 if (ndata->old_rate > ndata->new_rate)
0218 rockchip_cpuclk_set_dividers(cpuclk, rate);
0219
0220 spin_unlock_irqrestore(cpuclk->lock, flags);
0221 return 0;
0222 }
0223
0224
0225
0226
0227
0228
0229
0230 static int rockchip_cpuclk_notifier_cb(struct notifier_block *nb,
0231 unsigned long event, void *data)
0232 {
0233 struct clk_notifier_data *ndata = data;
0234 struct rockchip_cpuclk *cpuclk = to_rockchip_cpuclk_nb(nb);
0235 int ret = 0;
0236
0237 pr_debug("%s: event %lu, old_rate %lu, new_rate: %lu\n",
0238 __func__, event, ndata->old_rate, ndata->new_rate);
0239 if (event == PRE_RATE_CHANGE)
0240 ret = rockchip_cpuclk_pre_rate_change(cpuclk, ndata);
0241 else if (event == POST_RATE_CHANGE)
0242 ret = rockchip_cpuclk_post_rate_change(cpuclk, ndata);
0243
0244 return notifier_from_errno(ret);
0245 }
0246
0247 struct clk *rockchip_clk_register_cpuclk(const char *name,
0248 const char *const *parent_names, u8 num_parents,
0249 const struct rockchip_cpuclk_reg_data *reg_data,
0250 const struct rockchip_cpuclk_rate_table *rates,
0251 int nrates, void __iomem *reg_base, spinlock_t *lock)
0252 {
0253 struct rockchip_cpuclk *cpuclk;
0254 struct clk_init_data init;
0255 struct clk *clk, *cclk;
0256 int ret;
0257
0258 if (num_parents < 2) {
0259 pr_err("%s: needs at least two parent clocks\n", __func__);
0260 return ERR_PTR(-EINVAL);
0261 }
0262
0263 cpuclk = kzalloc(sizeof(*cpuclk), GFP_KERNEL);
0264 if (!cpuclk)
0265 return ERR_PTR(-ENOMEM);
0266
0267 init.name = name;
0268 init.parent_names = &parent_names[reg_data->mux_core_main];
0269 init.num_parents = 1;
0270 init.ops = &rockchip_cpuclk_ops;
0271
0272
0273 init.flags = (nrates > 0) ? CLK_SET_RATE_PARENT : 0;
0274
0275
0276 init.flags |= CLK_SET_RATE_NO_REPARENT;
0277
0278 init.flags |= CLK_GET_RATE_NOCACHE;
0279
0280 cpuclk->reg_base = reg_base;
0281 cpuclk->lock = lock;
0282 cpuclk->reg_data = reg_data;
0283 cpuclk->clk_nb.notifier_call = rockchip_cpuclk_notifier_cb;
0284 cpuclk->hw.init = &init;
0285
0286 cpuclk->alt_parent = __clk_lookup(parent_names[reg_data->mux_core_alt]);
0287 if (!cpuclk->alt_parent) {
0288 pr_err("%s: could not lookup alternate parent: (%d)\n",
0289 __func__, reg_data->mux_core_alt);
0290 ret = -EINVAL;
0291 goto free_cpuclk;
0292 }
0293
0294 ret = clk_prepare_enable(cpuclk->alt_parent);
0295 if (ret) {
0296 pr_err("%s: could not enable alternate parent\n",
0297 __func__);
0298 goto free_cpuclk;
0299 }
0300
0301 clk = __clk_lookup(parent_names[reg_data->mux_core_main]);
0302 if (!clk) {
0303 pr_err("%s: could not lookup parent clock: (%d) %s\n",
0304 __func__, reg_data->mux_core_main,
0305 parent_names[reg_data->mux_core_main]);
0306 ret = -EINVAL;
0307 goto free_alt_parent;
0308 }
0309
0310 ret = clk_notifier_register(clk, &cpuclk->clk_nb);
0311 if (ret) {
0312 pr_err("%s: failed to register clock notifier for %s\n",
0313 __func__, name);
0314 goto free_alt_parent;
0315 }
0316
0317 if (nrates > 0) {
0318 cpuclk->rate_count = nrates;
0319 cpuclk->rate_table = kmemdup(rates,
0320 sizeof(*rates) * nrates,
0321 GFP_KERNEL);
0322 if (!cpuclk->rate_table) {
0323 ret = -ENOMEM;
0324 goto unregister_notifier;
0325 }
0326 }
0327
0328 cclk = clk_register(NULL, &cpuclk->hw);
0329 if (IS_ERR(cclk)) {
0330 pr_err("%s: could not register cpuclk %s\n", __func__, name);
0331 ret = PTR_ERR(cclk);
0332 goto free_rate_table;
0333 }
0334
0335 return cclk;
0336
0337 free_rate_table:
0338 kfree(cpuclk->rate_table);
0339 unregister_notifier:
0340 clk_notifier_unregister(clk, &cpuclk->clk_nb);
0341 free_alt_parent:
0342 clk_disable_unprepare(cpuclk->alt_parent);
0343 free_cpuclk:
0344 kfree(cpuclk);
0345 return ERR_PTR(ret);
0346 }