Commit | Line | Data |
---|---|---|
cf8de5a7 AB |
1 | /* |
2 | * Copyright (c) 2014 Marvell Technology Group Ltd. | |
3 | * | |
4 | * Alexandre Belloni <alexandre.belloni@free-electrons.com> | |
5 | * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com> | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or modify it | |
8 | * under the terms and conditions of the GNU General Public License, | |
9 | * version 2, as published by the Free Software Foundation. | |
10 | * | |
11 | * This program is distributed in the hope it will be useful, but WITHOUT | |
12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
14 | * more details. | |
15 | * | |
16 | * You should have received a copy of the GNU General Public License along with | |
17 | * this program. If not, see <http://www.gnu.org/licenses/>. | |
18 | */ | |
19 | #include <linux/clk-provider.h> | |
20 | #include <linux/io.h> | |
21 | #include <linux/kernel.h> | |
22 | #include <linux/of.h> | |
23 | #include <linux/of_address.h> | |
24 | #include <linux/slab.h> | |
25 | #include <asm/div64.h> | |
26 | ||
27 | #include "berlin2-div.h" | |
47c18e4c | 28 | #include "berlin2-pll.h" |
cf8de5a7 AB |
29 | |
30 | struct berlin2_pll { | |
31 | struct clk_hw hw; | |
32 | void __iomem *base; | |
33 | struct berlin2_pll_map map; | |
34 | }; | |
35 | ||
36 | #define to_berlin2_pll(hw) container_of(hw, struct berlin2_pll, hw) | |
37 | ||
38 | #define SPLL_CTRL0 0x00 | |
39 | #define SPLL_CTRL1 0x04 | |
40 | #define SPLL_CTRL2 0x08 | |
41 | #define SPLL_CTRL3 0x0c | |
42 | #define SPLL_CTRL4 0x10 | |
43 | ||
44 | #define FBDIV_MASK 0x1ff | |
45 | #define RFDIV_MASK 0x1f | |
46 | #define DIVSEL_MASK 0xf | |
47 | ||
48 | /* | |
49 | * The output frequency formula for the pll is: | |
50 | * clkout = fbdiv / refdiv * parent / vcodiv | |
51 | */ | |
52 | static unsigned long | |
53 | berlin2_pll_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) | |
54 | { | |
55 | struct berlin2_pll *pll = to_berlin2_pll(hw); | |
56 | struct berlin2_pll_map *map = &pll->map; | |
57 | u32 val, fbdiv, rfdiv, vcodivsel, vcodiv; | |
58 | u64 rate = parent_rate; | |
59 | ||
60 | val = readl_relaxed(pll->base + SPLL_CTRL0); | |
61 | fbdiv = (val >> map->fbdiv_shift) & FBDIV_MASK; | |
62 | rfdiv = (val >> map->rfdiv_shift) & RFDIV_MASK; | |
63 | if (rfdiv == 0) { | |
836ee0f7 | 64 | pr_warn("%s has zero rfdiv\n", clk_hw_get_name(hw)); |
cf8de5a7 AB |
65 | rfdiv = 1; |
66 | } | |
67 | ||
68 | val = readl_relaxed(pll->base + SPLL_CTRL1); | |
69 | vcodivsel = (val >> map->divsel_shift) & DIVSEL_MASK; | |
70 | vcodiv = map->vcodiv[vcodivsel]; | |
71 | if (vcodiv == 0) { | |
72 | pr_warn("%s has zero vcodiv (index %d)\n", | |
836ee0f7 | 73 | clk_hw_get_name(hw), vcodivsel); |
cf8de5a7 AB |
74 | vcodiv = 1; |
75 | } | |
76 | ||
77 | rate *= fbdiv * map->mult; | |
78 | do_div(rate, rfdiv * vcodiv); | |
79 | ||
80 | return (unsigned long)rate; | |
81 | } | |
82 | ||
83 | static const struct clk_ops berlin2_pll_ops = { | |
84 | .recalc_rate = berlin2_pll_recalc_rate, | |
85 | }; | |
86 | ||
f6475e29 | 87 | int __init |
cf8de5a7 AB |
88 | berlin2_pll_register(const struct berlin2_pll_map *map, |
89 | void __iomem *base, const char *name, | |
90 | const char *parent_name, unsigned long flags) | |
91 | { | |
92 | struct clk_init_data init; | |
93 | struct berlin2_pll *pll; | |
94 | ||
95 | pll = kzalloc(sizeof(*pll), GFP_KERNEL); | |
96 | if (!pll) | |
f6475e29 | 97 | return -ENOMEM; |
cf8de5a7 AB |
98 | |
99 | /* copy pll_map to allow __initconst */ | |
100 | memcpy(&pll->map, map, sizeof(*map)); | |
101 | pll->base = base; | |
102 | pll->hw.init = &init; | |
103 | init.name = name; | |
104 | init.ops = &berlin2_pll_ops; | |
105 | init.parent_names = &parent_name; | |
106 | init.num_parents = 1; | |
107 | init.flags = flags; | |
108 | ||
f6475e29 | 109 | return clk_hw_register(NULL, &pll->hw); |
cf8de5a7 | 110 | } |