Commit | Line | Data |
---|---|---|
2874c5fd | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
5fba62ea BB |
2 | /* |
3 | * Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com> | |
5fba62ea BB |
4 | */ |
5 | ||
6 | #include <linux/clk-provider.h> | |
7 | #include <linux/clkdev.h> | |
8 | #include <linux/clk/at91_pmc.h> | |
9 | #include <linux/of.h> | |
1bdf0232 BB |
10 | #include <linux/mfd/syscon.h> |
11 | #include <linux/regmap.h> | |
5fba62ea BB |
12 | |
13 | #include "pmc.h" | |
14 | ||
15 | #define SYSTEM_MAX_ID 31 | |
16 | ||
17 | #define SYSTEM_MAX_NAME_SZ 32 | |
18 | ||
19 | #define to_clk_system(hw) container_of(hw, struct clk_system, hw) | |
20 | struct clk_system { | |
21 | struct clk_hw hw; | |
1bdf0232 | 22 | struct regmap *regmap; |
36971566 | 23 | struct at91_clk_pms pms; |
5fba62ea BB |
24 | u8 id; |
25 | }; | |
26 | ||
cce6db80 JJH |
27 | static inline int is_pck(int id) |
28 | { | |
29 | return (id >= 8) && (id <= 15); | |
30 | } | |
cce6db80 | 31 | |
1bdf0232 BB |
32 | static inline bool clk_system_ready(struct regmap *regmap, int id) |
33 | { | |
34 | unsigned int status; | |
35 | ||
36 | regmap_read(regmap, AT91_PMC_SR, &status); | |
37 | ||
42324d95 | 38 | return !!(status & (1 << id)); |
1bdf0232 BB |
39 | } |
40 | ||
cce6db80 | 41 | static int clk_system_prepare(struct clk_hw *hw) |
5fba62ea BB |
42 | { |
43 | struct clk_system *sys = to_clk_system(hw); | |
5fba62ea | 44 | |
1bdf0232 | 45 | regmap_write(sys->regmap, AT91_PMC_SCER, 1 << sys->id); |
cce6db80 JJH |
46 | |
47 | if (!is_pck(sys->id)) | |
48 | return 0; | |
49 | ||
99a81706 AB |
50 | while (!clk_system_ready(sys->regmap, sys->id)) |
51 | cpu_relax(); | |
52 | ||
5fba62ea BB |
53 | return 0; |
54 | } | |
55 | ||
cce6db80 | 56 | static void clk_system_unprepare(struct clk_hw *hw) |
5fba62ea BB |
57 | { |
58 | struct clk_system *sys = to_clk_system(hw); | |
5fba62ea | 59 | |
1bdf0232 | 60 | regmap_write(sys->regmap, AT91_PMC_SCDR, 1 << sys->id); |
5fba62ea BB |
61 | } |
62 | ||
cce6db80 | 63 | static int clk_system_is_prepared(struct clk_hw *hw) |
5fba62ea BB |
64 | { |
65 | struct clk_system *sys = to_clk_system(hw); | |
1bdf0232 BB |
66 | unsigned int status; |
67 | ||
68 | regmap_read(sys->regmap, AT91_PMC_SCSR, &status); | |
5fba62ea | 69 | |
1bdf0232 | 70 | if (!(status & (1 << sys->id))) |
cce6db80 JJH |
71 | return 0; |
72 | ||
73 | if (!is_pck(sys->id)) | |
74 | return 1; | |
75 | ||
1bdf0232 BB |
76 | regmap_read(sys->regmap, AT91_PMC_SR, &status); |
77 | ||
42324d95 | 78 | return !!(status & (1 << sys->id)); |
5fba62ea BB |
79 | } |
80 | ||
36971566 CB |
81 | static int clk_system_save_context(struct clk_hw *hw) |
82 | { | |
83 | struct clk_system *sys = to_clk_system(hw); | |
84 | ||
85 | sys->pms.status = clk_system_is_prepared(hw); | |
86 | ||
87 | return 0; | |
88 | } | |
89 | ||
90 | static void clk_system_restore_context(struct clk_hw *hw) | |
91 | { | |
92 | struct clk_system *sys = to_clk_system(hw); | |
93 | ||
94 | if (sys->pms.status) | |
95 | clk_system_prepare(&sys->hw); | |
96 | } | |
97 | ||
5fba62ea | 98 | static const struct clk_ops system_ops = { |
cce6db80 JJH |
99 | .prepare = clk_system_prepare, |
100 | .unprepare = clk_system_unprepare, | |
101 | .is_prepared = clk_system_is_prepared, | |
36971566 CB |
102 | .save_context = clk_system_save_context, |
103 | .restore_context = clk_system_restore_context, | |
5fba62ea BB |
104 | }; |
105 | ||
b2e39dc0 | 106 | struct clk_hw * __init |
1bdf0232 | 107 | at91_clk_register_system(struct regmap *regmap, const char *name, |
68b3b6f1 | 108 | const char *parent_name, u8 id, unsigned long flags) |
5fba62ea BB |
109 | { |
110 | struct clk_system *sys; | |
f5644f10 | 111 | struct clk_hw *hw; |
5fba62ea | 112 | struct clk_init_data init; |
f5644f10 | 113 | int ret; |
5fba62ea BB |
114 | |
115 | if (!parent_name || id > SYSTEM_MAX_ID) | |
116 | return ERR_PTR(-EINVAL); | |
117 | ||
118 | sys = kzalloc(sizeof(*sys), GFP_KERNEL); | |
119 | if (!sys) | |
120 | return ERR_PTR(-ENOMEM); | |
121 | ||
122 | init.name = name; | |
123 | init.ops = &system_ops; | |
124 | init.parent_names = &parent_name; | |
125 | init.num_parents = 1; | |
68b3b6f1 | 126 | init.flags = CLK_SET_RATE_PARENT | flags; |
5fba62ea BB |
127 | |
128 | sys->id = id; | |
129 | sys->hw.init = &init; | |
1bdf0232 | 130 | sys->regmap = regmap; |
5fba62ea | 131 | |
f5644f10 SB |
132 | hw = &sys->hw; |
133 | ret = clk_hw_register(NULL, &sys->hw); | |
134 | if (ret) { | |
5fba62ea | 135 | kfree(sys); |
f5644f10 SB |
136 | hw = ERR_PTR(ret); |
137 | } | |
5fba62ea | 138 | |
f5644f10 | 139 | return hw; |
5fba62ea | 140 | } |