Skip to content

Commit baf4fc7

Browse files
ziyao233pdp7
authored andcommitted
clk: thead: th1520-ap: Support CPU frequency scaling
On TH1520 SoC, c910_clk feeds the CPU cluster. It could be glitchlessly reparented to one of the two PLLs: either to cpu_pll0 indirectly through c910_i0_clk, or to cpu_pll1 directly. To achieve glitchless rate change, customized clock operations are implemented for c910_clk: on rate change, the PLL not currently in use is configured to the requested rate first, then c910_clk reparents to it. Additionally, c910_bus_clk, which in turn takes c910_clk as parent, has a frequency limit of 750MHz. A clock notifier is registered on c910_clk to adjust c910_bus_clk on c910_clk rate change. Reviewed-by: Drew Fustini <fustini@kernel.org> Signed-off-by: Yao Zi <ziyao@disroot.org> Signed-off-by: Drew Fustini <fustini@kernel.org>
1 parent 5dbee35 commit baf4fc7

1 file changed

Lines changed: 146 additions & 2 deletions

File tree

drivers/clk/thead/clk-th1520-ap.c

Lines changed: 146 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@
77

88
#include <dt-bindings/clock/thead,th1520-clk-ap.h>
99
#include <linux/bitfield.h>
10+
#include <linux/clk.h>
1011
#include <linux/clk-provider.h>
1112
#include <linux/delay.h>
1213
#include <linux/device.h>
14+
#include <linux/minmax.h>
1315
#include <linux/module.h>
1416
#include <linux/platform_device.h>
1517
#include <linux/regmap.h>
@@ -34,6 +36,9 @@
3436
#define TH1520_PLL_LOCK_TIMEOUT_US 44
3537
#define TH1520_PLL_STABLE_DELAY_US 30
3638

39+
/* c910_bus_clk must be kept below 750MHz for stability */
40+
#define TH1520_C910_BUS_MAX_RATE (750 * 1000 * 1000)
41+
3742
struct ccu_internal {
3843
u8 shift;
3944
u8 width;
@@ -472,6 +477,72 @@ static const struct clk_ops clk_pll_ops = {
472477
.set_rate = ccu_pll_set_rate,
473478
};
474479

480+
/*
481+
* c910_clk could be reparented glitchlessly for DVFS. There are two parents,
482+
* - c910_i0_clk, derived from cpu_pll0_clk or osc_24m.
483+
* - cpu_pll1_clk, which provides the exact same set of rates as cpu_pll0_clk.
484+
*
485+
* During rate setting, always forward the request to the unused parent, and
486+
* then switch c910_clk to it to avoid glitch.
487+
*/
488+
static u8 c910_clk_get_parent(struct clk_hw *hw)
489+
{
490+
return clk_mux_ops.get_parent(hw);
491+
}
492+
493+
static int c910_clk_set_parent(struct clk_hw *hw, u8 index)
494+
{
495+
return clk_mux_ops.set_parent(hw, index);
496+
}
497+
498+
static unsigned long c910_clk_recalc_rate(struct clk_hw *hw,
499+
unsigned long parent_rate)
500+
{
501+
return parent_rate;
502+
}
503+
504+
static int c910_clk_determine_rate(struct clk_hw *hw,
505+
struct clk_rate_request *req)
506+
{
507+
u8 alt_parent_index = !c910_clk_get_parent(hw);
508+
struct clk_hw *alt_parent;
509+
510+
alt_parent = clk_hw_get_parent_by_index(hw, alt_parent_index);
511+
512+
req->rate = clk_hw_round_rate(alt_parent, req->rate);
513+
req->best_parent_hw = alt_parent;
514+
req->best_parent_rate = req->rate;
515+
516+
return 0;
517+
}
518+
519+
static int c910_clk_set_rate(struct clk_hw *hw, unsigned long rate,
520+
unsigned long parent_rate)
521+
{
522+
return -EOPNOTSUPP;
523+
}
524+
525+
static int c910_clk_set_rate_and_parent(struct clk_hw *hw, unsigned long rate,
526+
unsigned long parent_rate, u8 index)
527+
{
528+
struct clk_hw *parent = clk_hw_get_parent_by_index(hw, index);
529+
530+
clk_set_rate(parent->clk, parent_rate);
531+
532+
c910_clk_set_parent(hw, index);
533+
534+
return 0;
535+
}
536+
537+
static const struct clk_ops c910_clk_ops = {
538+
.get_parent = c910_clk_get_parent,
539+
.set_parent = c910_clk_set_parent,
540+
.recalc_rate = c910_clk_recalc_rate,
541+
.determine_rate = c910_clk_determine_rate,
542+
.set_rate = c910_clk_set_rate,
543+
.set_rate_and_parent = c910_clk_set_rate_and_parent,
544+
};
545+
475546
static const struct clk_parent_data osc_24m_clk[] = {
476547
{ .index = 0 }
477548
};
@@ -672,7 +743,8 @@ static const struct clk_parent_data c910_i0_parents[] = {
672743
static struct ccu_mux c910_i0_clk = {
673744
.clkid = CLK_C910_I0,
674745
.reg = 0x100,
675-
.mux = TH_CCU_MUX("c910-i0", c910_i0_parents, 1, 1),
746+
.mux = TH_CCU_MUX_FLAGS("c910-i0", c910_i0_parents, 1, 1,
747+
CLK_SET_RATE_PARENT, CLK_MUX_ROUND_CLOSEST),
676748
};
677749

678750
static const struct clk_parent_data c910_parents[] = {
@@ -683,7 +755,14 @@ static const struct clk_parent_data c910_parents[] = {
683755
static struct ccu_mux c910_clk = {
684756
.clkid = CLK_C910,
685757
.reg = 0x100,
686-
.mux = TH_CCU_MUX("c910", c910_parents, 0, 1),
758+
.mux = {
759+
.mask = BIT(0),
760+
.shift = 0,
761+
.hw.init = CLK_HW_INIT_PARENTS_DATA("c910",
762+
c910_parents,
763+
&c910_clk_ops,
764+
CLK_SET_RATE_PARENT),
765+
},
687766
};
688767

689768
static struct ccu_div c910_bus_clk = {
@@ -1372,11 +1451,69 @@ static const struct th1520_plat_data th1520_vo_platdata = {
13721451
.nr_gate_clks = ARRAY_SIZE(th1520_vo_gate_clks),
13731452
};
13741453

1454+
/*
1455+
* Maintain clock rate of c910_bus_clk below TH1520_C910_BUS_MAX_RATE (750MHz)
1456+
* when its parent, c910_clk, changes the rate.
1457+
*
1458+
* Additionally, TRM is unclear about c910_bus_clk behavior when the divisor is
1459+
* set below 2, thus we should ensure the new divisor stays in (2, MAXDIVISOR).
1460+
*/
1461+
static unsigned long c910_bus_clk_divisor(struct ccu_div *cd,
1462+
unsigned long parent_rate)
1463+
{
1464+
return clamp(DIV_ROUND_UP(parent_rate, TH1520_C910_BUS_MAX_RATE),
1465+
2U, 1U << cd->div.width);
1466+
}
1467+
1468+
static int c910_clk_notifier_cb(struct notifier_block *nb,
1469+
unsigned long action, void *data)
1470+
{
1471+
struct clk_notifier_data *cnd = data;
1472+
unsigned long new_divisor, ref_rate;
1473+
1474+
if (action != PRE_RATE_CHANGE && action != POST_RATE_CHANGE)
1475+
return NOTIFY_DONE;
1476+
1477+
new_divisor = c910_bus_clk_divisor(&c910_bus_clk, cnd->new_rate);
1478+
1479+
if (cnd->new_rate > cnd->old_rate) {
1480+
/*
1481+
* Scaling up. Adjust c910_bus_clk divisor
1482+
* - before c910_clk rate change to ensure the constraints
1483+
* aren't broken after scaling to higher rates,
1484+
* - after c910_clk rate change to keep c910_bus_clk as high as
1485+
* possible
1486+
*/
1487+
ref_rate = action == PRE_RATE_CHANGE ?
1488+
cnd->old_rate : cnd->new_rate;
1489+
clk_set_rate(c910_bus_clk.common.hw.clk,
1490+
ref_rate / new_divisor);
1491+
} else if (cnd->new_rate < cnd->old_rate &&
1492+
action == POST_RATE_CHANGE) {
1493+
/*
1494+
* Scaling down. Adjust c910_bus_clk divisor only after
1495+
* c910_clk rate change to keep c910_bus_clk as high as
1496+
* possible, Scaling down never breaks the constraints.
1497+
*/
1498+
clk_set_rate(c910_bus_clk.common.hw.clk,
1499+
cnd->new_rate / new_divisor);
1500+
} else {
1501+
return NOTIFY_DONE;
1502+
}
1503+
1504+
return NOTIFY_OK;
1505+
}
1506+
1507+
static struct notifier_block c910_clk_notifier = {
1508+
.notifier_call = c910_clk_notifier_cb,
1509+
};
1510+
13751511
static int th1520_clk_probe(struct platform_device *pdev)
13761512
{
13771513
const struct th1520_plat_data *plat_data;
13781514
struct device *dev = &pdev->dev;
13791515
struct clk_hw_onecell_data *priv;
1516+
struct clk *notifier_clk;
13801517

13811518
struct regmap *map;
13821519
void __iomem *base;
@@ -1463,6 +1600,13 @@ static int th1520_clk_probe(struct platform_device *pdev)
14631600
ret = devm_clk_hw_register(dev, &emmc_sdio_ref_clk.hw);
14641601
if (ret)
14651602
return ret;
1603+
1604+
notifier_clk = devm_clk_hw_get_clk(dev, &c910_clk.mux.hw,
1605+
"dvfs");
1606+
ret = devm_clk_notifier_register(dev, notifier_clk,
1607+
&c910_clk_notifier);
1608+
if (ret)
1609+
return ret;
14661610
}
14671611

14681612
ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, priv);

0 commit comments

Comments
 (0)