.. | .. |
---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only |
---|
1 | 2 | /* |
---|
2 | 3 | * Copyright (c) 2014 MundoReader S.L. |
---|
3 | 4 | * Author: Heiko Stuebner <heiko@sntech.de> |
---|
.. | .. |
---|
5 | 6 | * based on clk/samsung/clk-cpu.c |
---|
6 | 7 | * Copyright (c) 2014 Samsung Electronics Co., Ltd. |
---|
7 | 8 | * Author: Thomas Abraham <thomas.ab@samsung.com> |
---|
8 | | - * |
---|
9 | | - * This program is free software; you can redistribute it and/or modify |
---|
10 | | - * it under the terms of the GNU General Public License version 2 as |
---|
11 | | - * published by the Free Software Foundation. |
---|
12 | 9 | * |
---|
13 | 10 | * A CPU clock is defined as a clock supplied to a CPU or a group of CPUs. |
---|
14 | 11 | * The CPU clock is typically derived from a hierarchy of clock |
---|
.. | .. |
---|
196 | 193 | alt_div = reg_data->div_core_mask[0]; |
---|
197 | 194 | } |
---|
198 | 195 | |
---|
| 196 | + /* |
---|
| 197 | + * Change parents and add dividers in a single transaction. |
---|
| 198 | + * |
---|
| 199 | + * NOTE: we do this in a single transaction so we're never |
---|
| 200 | + * dividing the primary parent by the extra dividers that were |
---|
| 201 | + * needed for the alt. |
---|
| 202 | + */ |
---|
199 | 203 | pr_debug("%s: setting div %lu as alt-rate %lu > old-rate %lu\n", |
---|
200 | 204 | __func__, alt_div, alt_prate, ndata->old_rate); |
---|
201 | 205 | |
---|
.. | .. |
---|
209 | 213 | if (IS_ENABLED(CONFIG_ROCKCHIP_CLK_BOOST)) |
---|
210 | 214 | rockchip_boost_add_core_div(cpuclk->pll_hw, alt_prate); |
---|
211 | 215 | |
---|
212 | | - /* select alternate parent */ |
---|
213 | | - writel(HIWORD_UPDATE(reg_data->mux_core_alt, |
---|
214 | | - reg_data->mux_core_mask, |
---|
215 | | - reg_data->mux_core_shift), |
---|
216 | | - cpuclk->reg_base + reg_data->core_reg[0]); |
---|
217 | | - |
---|
218 | 216 | rockchip_cpuclk_set_pre_muxs(cpuclk, rate); |
---|
| 217 | + |
---|
| 218 | + /* select alternate parent */ |
---|
| 219 | + if (reg_data->mux_core_reg) |
---|
| 220 | + writel(HIWORD_UPDATE(reg_data->mux_core_alt, |
---|
| 221 | + reg_data->mux_core_mask, |
---|
| 222 | + reg_data->mux_core_shift), |
---|
| 223 | + cpuclk->reg_base + reg_data->mux_core_reg); |
---|
| 224 | + else |
---|
| 225 | + writel(HIWORD_UPDATE(reg_data->mux_core_alt, |
---|
| 226 | + reg_data->mux_core_mask, |
---|
| 227 | + reg_data->mux_core_shift), |
---|
| 228 | + cpuclk->reg_base + reg_data->core_reg[0]); |
---|
219 | 229 | |
---|
220 | 230 | spin_unlock_irqrestore(cpuclk->lock, flags); |
---|
221 | 231 | return 0; |
---|
.. | .. |
---|
241 | 251 | if (ndata->old_rate < ndata->new_rate) |
---|
242 | 252 | rockchip_cpuclk_set_dividers(cpuclk, rate); |
---|
243 | 253 | |
---|
244 | | - /* re-mux to primary parent */ |
---|
245 | | - writel(HIWORD_UPDATE(reg_data->mux_core_main, |
---|
246 | | - reg_data->mux_core_mask, |
---|
247 | | - reg_data->mux_core_shift), |
---|
248 | | - cpuclk->reg_base + reg_data->core_reg[0]); |
---|
| 254 | + /* |
---|
| 255 | + * post-rate change event, re-mux to primary parent and remove dividers. |
---|
| 256 | + * |
---|
| 257 | + * NOTE: we do this in a single transaction so we're never dividing the |
---|
| 258 | + * primary parent by the extra dividers that were needed for the alt. |
---|
| 259 | + */ |
---|
| 260 | + |
---|
| 261 | + if (reg_data->mux_core_reg) |
---|
| 262 | + writel(HIWORD_UPDATE(reg_data->mux_core_main, |
---|
| 263 | + reg_data->mux_core_mask, |
---|
| 264 | + reg_data->mux_core_shift), |
---|
| 265 | + cpuclk->reg_base + reg_data->mux_core_reg); |
---|
| 266 | + else |
---|
| 267 | + writel(HIWORD_UPDATE(reg_data->mux_core_main, |
---|
| 268 | + reg_data->mux_core_mask, |
---|
| 269 | + reg_data->mux_core_shift), |
---|
| 270 | + cpuclk->reg_base + reg_data->core_reg[0]); |
---|
249 | 271 | |
---|
250 | 272 | rockchip_cpuclk_set_post_muxs(cpuclk, rate); |
---|
251 | 273 | |
---|
.. | .. |
---|
290 | 312 | } |
---|
291 | 313 | |
---|
292 | 314 | struct clk *rockchip_clk_register_cpuclk(const char *name, |
---|
293 | | - const char *const *parent_names, u8 num_parents, |
---|
| 315 | + u8 num_parents, |
---|
| 316 | + struct clk *parent, struct clk *alt_parent, |
---|
294 | 317 | const struct rockchip_cpuclk_reg_data *reg_data, |
---|
295 | 318 | const struct rockchip_cpuclk_rate_table *rates, |
---|
296 | 319 | int nrates, void __iomem *reg_base, spinlock_t *lock) |
---|
297 | 320 | { |
---|
298 | 321 | struct rockchip_cpuclk *cpuclk; |
---|
299 | | - struct clk_init_data init = {}; |
---|
| 322 | + struct clk_init_data init; |
---|
300 | 323 | struct clk *clk, *cclk, *pll_clk; |
---|
| 324 | + const char *parent_name; |
---|
301 | 325 | int ret; |
---|
302 | 326 | |
---|
303 | 327 | if (num_parents < 2) { |
---|
.. | .. |
---|
305 | 329 | return ERR_PTR(-EINVAL); |
---|
306 | 330 | } |
---|
307 | 331 | |
---|
| 332 | + if (IS_ERR(parent) || IS_ERR(alt_parent)) { |
---|
| 333 | + pr_err("%s: invalid parent clock(s)\n", __func__); |
---|
| 334 | + return ERR_PTR(-EINVAL); |
---|
| 335 | + } |
---|
| 336 | + |
---|
308 | 337 | cpuclk = kzalloc(sizeof(*cpuclk), GFP_KERNEL); |
---|
309 | 338 | if (!cpuclk) |
---|
310 | 339 | return ERR_PTR(-ENOMEM); |
---|
311 | 340 | |
---|
| 341 | + parent_name = clk_hw_get_name(__clk_get_hw(parent)); |
---|
312 | 342 | init.name = name; |
---|
313 | | - init.parent_names = &parent_names[reg_data->mux_core_main]; |
---|
| 343 | + init.parent_names = &parent_name; |
---|
314 | 344 | init.num_parents = 1; |
---|
315 | 345 | init.ops = &rockchip_cpuclk_ops; |
---|
316 | 346 | |
---|
.. | .. |
---|
328 | 358 | cpuclk->clk_nb.notifier_call = rockchip_cpuclk_notifier_cb; |
---|
329 | 359 | cpuclk->hw.init = &init; |
---|
330 | 360 | if (IS_ENABLED(CONFIG_ROCKCHIP_CLK_BOOST) && reg_data->pll_name) { |
---|
331 | | - pll_clk = __clk_lookup(reg_data->pll_name); |
---|
| 361 | + pll_clk = clk_get_parent(parent); |
---|
332 | 362 | if (!pll_clk) { |
---|
333 | 363 | pr_err("%s: could not lookup pll clock: (%s)\n", |
---|
334 | 364 | __func__, reg_data->pll_name); |
---|
.. | .. |
---|
339 | 369 | rockchip_boost_init(cpuclk->pll_hw); |
---|
340 | 370 | } |
---|
341 | 371 | |
---|
342 | | - cpuclk->alt_parent = __clk_lookup(parent_names[reg_data->mux_core_alt]); |
---|
| 372 | + cpuclk->alt_parent = alt_parent; |
---|
343 | 373 | if (!cpuclk->alt_parent) { |
---|
344 | 374 | pr_err("%s: could not lookup alternate parent: (%d)\n", |
---|
345 | 375 | __func__, reg_data->mux_core_alt); |
---|
.. | .. |
---|
354 | 384 | goto free_cpuclk; |
---|
355 | 385 | } |
---|
356 | 386 | |
---|
357 | | - clk = __clk_lookup(parent_names[reg_data->mux_core_main]); |
---|
| 387 | + clk = parent; |
---|
358 | 388 | if (!clk) { |
---|
359 | 389 | pr_err("%s: could not lookup parent clock: (%d) %s\n", |
---|
360 | 390 | __func__, reg_data->mux_core_main, |
---|
361 | | - parent_names[reg_data->mux_core_main]); |
---|
| 391 | + parent_name); |
---|
362 | 392 | ret = -EINVAL; |
---|
363 | 393 | goto free_alt_parent; |
---|
364 | 394 | } |
---|
.. | .. |
---|
400 | 430 | kfree(cpuclk); |
---|
401 | 431 | return ERR_PTR(ret); |
---|
402 | 432 | } |
---|
| 433 | + |
---|
| 434 | +static int rockchip_cpuclk_v2_pre_rate_change(struct rockchip_cpuclk *cpuclk, |
---|
| 435 | + struct clk_notifier_data *ndata) |
---|
| 436 | +{ |
---|
| 437 | + unsigned long new_rate = roundup(ndata->new_rate, 1000); |
---|
| 438 | + const struct rockchip_cpuclk_rate_table *rate; |
---|
| 439 | + unsigned long flags; |
---|
| 440 | + |
---|
| 441 | + rate = rockchip_get_cpuclk_settings(cpuclk, new_rate); |
---|
| 442 | + if (!rate) { |
---|
| 443 | + pr_err("%s: Invalid rate : %lu for cpuclk\n", |
---|
| 444 | + __func__, new_rate); |
---|
| 445 | + return -EINVAL; |
---|
| 446 | + } |
---|
| 447 | + |
---|
| 448 | + if (new_rate > ndata->old_rate) { |
---|
| 449 | + spin_lock_irqsave(cpuclk->lock, flags); |
---|
| 450 | + rockchip_cpuclk_set_dividers(cpuclk, rate); |
---|
| 451 | + spin_unlock_irqrestore(cpuclk->lock, flags); |
---|
| 452 | + } |
---|
| 453 | + |
---|
| 454 | + return 0; |
---|
| 455 | +} |
---|
| 456 | + |
---|
| 457 | +static int rockchip_cpuclk_v2_post_rate_change(struct rockchip_cpuclk *cpuclk, |
---|
| 458 | + struct clk_notifier_data *ndata) |
---|
| 459 | +{ |
---|
| 460 | + unsigned long new_rate = roundup(ndata->new_rate, 1000); |
---|
| 461 | + const struct rockchip_cpuclk_rate_table *rate; |
---|
| 462 | + unsigned long flags; |
---|
| 463 | + |
---|
| 464 | + rate = rockchip_get_cpuclk_settings(cpuclk, new_rate); |
---|
| 465 | + if (!rate) { |
---|
| 466 | + pr_err("%s: Invalid rate : %lu for cpuclk\n", |
---|
| 467 | + __func__, new_rate); |
---|
| 468 | + return -EINVAL; |
---|
| 469 | + } |
---|
| 470 | + |
---|
| 471 | + if (new_rate < ndata->old_rate) { |
---|
| 472 | + spin_lock_irqsave(cpuclk->lock, flags); |
---|
| 473 | + rockchip_cpuclk_set_dividers(cpuclk, rate); |
---|
| 474 | + spin_unlock_irqrestore(cpuclk->lock, flags); |
---|
| 475 | + } |
---|
| 476 | + |
---|
| 477 | + return 0; |
---|
| 478 | +} |
---|
| 479 | + |
---|
| 480 | +static int rockchip_cpuclk_v2_notifier_cb(struct notifier_block *nb, |
---|
| 481 | + unsigned long event, void *data) |
---|
| 482 | +{ |
---|
| 483 | + struct clk_notifier_data *ndata = data; |
---|
| 484 | + struct rockchip_cpuclk *cpuclk = to_rockchip_cpuclk_nb(nb); |
---|
| 485 | + int ret = 0; |
---|
| 486 | + |
---|
| 487 | + pr_debug("%s: event %lu, old_rate %lu, new_rate: %lu\n", |
---|
| 488 | + __func__, event, ndata->old_rate, ndata->new_rate); |
---|
| 489 | + if (event == PRE_RATE_CHANGE) |
---|
| 490 | + ret = rockchip_cpuclk_v2_pre_rate_change(cpuclk, ndata); |
---|
| 491 | + else if (event == POST_RATE_CHANGE) |
---|
| 492 | + ret = rockchip_cpuclk_v2_post_rate_change(cpuclk, ndata); |
---|
| 493 | + |
---|
| 494 | + return notifier_from_errno(ret); |
---|
| 495 | +} |
---|
| 496 | + |
---|
| 497 | +struct clk *rockchip_clk_register_cpuclk_v2(const char *name, |
---|
| 498 | + const char *const *parent_names, |
---|
| 499 | + u8 num_parents, void __iomem *base, |
---|
| 500 | + int muxdiv_offset, u8 mux_shift, |
---|
| 501 | + u8 mux_width, u8 mux_flags, |
---|
| 502 | + int div_offset, u8 div_shift, |
---|
| 503 | + u8 div_width, u8 div_flags, |
---|
| 504 | + unsigned long flags, spinlock_t *lock, |
---|
| 505 | + const struct rockchip_cpuclk_rate_table *rates, |
---|
| 506 | + int nrates) |
---|
| 507 | +{ |
---|
| 508 | + struct rockchip_cpuclk *cpuclk; |
---|
| 509 | + struct clk_hw *hw; |
---|
| 510 | + struct clk_mux *mux = NULL; |
---|
| 511 | + struct clk_divider *div = NULL; |
---|
| 512 | + const struct clk_ops *mux_ops = NULL, *div_ops = NULL; |
---|
| 513 | + int ret; |
---|
| 514 | + |
---|
| 515 | + if (num_parents > 1) { |
---|
| 516 | + mux = kzalloc(sizeof(*mux), GFP_KERNEL); |
---|
| 517 | + if (!mux) |
---|
| 518 | + return ERR_PTR(-ENOMEM); |
---|
| 519 | + |
---|
| 520 | + mux->reg = base + muxdiv_offset; |
---|
| 521 | + mux->shift = mux_shift; |
---|
| 522 | + mux->mask = BIT(mux_width) - 1; |
---|
| 523 | + mux->flags = mux_flags; |
---|
| 524 | + mux->lock = lock; |
---|
| 525 | + mux_ops = (mux_flags & CLK_MUX_READ_ONLY) ? &clk_mux_ro_ops |
---|
| 526 | + : &clk_mux_ops; |
---|
| 527 | + } |
---|
| 528 | + |
---|
| 529 | + if (div_width > 0) { |
---|
| 530 | + div = kzalloc(sizeof(*div), GFP_KERNEL); |
---|
| 531 | + if (!div) { |
---|
| 532 | + ret = -ENOMEM; |
---|
| 533 | + goto free_mux; |
---|
| 534 | + } |
---|
| 535 | + |
---|
| 536 | + div->flags = div_flags; |
---|
| 537 | + if (div_offset) |
---|
| 538 | + div->reg = base + div_offset; |
---|
| 539 | + else |
---|
| 540 | + div->reg = base + muxdiv_offset; |
---|
| 541 | + div->shift = div_shift; |
---|
| 542 | + div->width = div_width; |
---|
| 543 | + div->lock = lock; |
---|
| 544 | + div_ops = (div_flags & CLK_DIVIDER_READ_ONLY) |
---|
| 545 | + ? &clk_divider_ro_ops |
---|
| 546 | + : &clk_divider_ops; |
---|
| 547 | + } |
---|
| 548 | + |
---|
| 549 | + hw = clk_hw_register_composite(NULL, name, parent_names, num_parents, |
---|
| 550 | + mux ? &mux->hw : NULL, mux_ops, |
---|
| 551 | + div ? &div->hw : NULL, div_ops, |
---|
| 552 | + NULL, NULL, flags); |
---|
| 553 | + if (IS_ERR(hw)) { |
---|
| 554 | + ret = PTR_ERR(hw); |
---|
| 555 | + goto free_div; |
---|
| 556 | + } |
---|
| 557 | + |
---|
| 558 | + cpuclk = kzalloc(sizeof(*cpuclk), GFP_KERNEL); |
---|
| 559 | + if (!cpuclk) { |
---|
| 560 | + ret = -ENOMEM; |
---|
| 561 | + goto unregister_clk; |
---|
| 562 | + } |
---|
| 563 | + |
---|
| 564 | + cpuclk->reg_base = base; |
---|
| 565 | + cpuclk->lock = lock; |
---|
| 566 | + cpuclk->clk_nb.notifier_call = rockchip_cpuclk_v2_notifier_cb; |
---|
| 567 | + ret = clk_notifier_register(hw->clk, &cpuclk->clk_nb); |
---|
| 568 | + if (ret) { |
---|
| 569 | + pr_err("%s: failed to register clock notifier for %s\n", |
---|
| 570 | + __func__, name); |
---|
| 571 | + goto free_cpuclk; |
---|
| 572 | + } |
---|
| 573 | + |
---|
| 574 | + if (nrates > 0) { |
---|
| 575 | + cpuclk->rate_count = nrates; |
---|
| 576 | + cpuclk->rate_table = kmemdup(rates, |
---|
| 577 | + sizeof(*rates) * nrates, |
---|
| 578 | + GFP_KERNEL); |
---|
| 579 | + if (!cpuclk->rate_table) { |
---|
| 580 | + ret = -ENOMEM; |
---|
| 581 | + goto free_cpuclk; |
---|
| 582 | + } |
---|
| 583 | + } |
---|
| 584 | + |
---|
| 585 | + return hw->clk; |
---|
| 586 | + |
---|
| 587 | +free_cpuclk: |
---|
| 588 | + kfree(cpuclk); |
---|
| 589 | +unregister_clk: |
---|
| 590 | + clk_hw_unregister_composite(hw); |
---|
| 591 | +free_div: |
---|
| 592 | + kfree(div); |
---|
| 593 | +free_mux: |
---|
| 594 | + kfree(mux); |
---|
| 595 | + |
---|
| 596 | + return ERR_PTR(ret); |
---|
| 597 | +} |
---|