1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com> |
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> |
10 | #include <linux/mfd/syscon.h> |
11 | #include <linux/regmap.h> |
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; |
22 | struct regmap *regmap; |
23 | struct at91_clk_pms pms; |
24 | u8 id; |
25 | }; |
26 | |
27 | static inline int is_pck(int id) |
28 | { |
29 | return (id >= 8) && (id <= 15); |
30 | } |
31 | |
32 | static inline bool clk_system_ready(struct regmap *regmap, int id) |
33 | { |
34 | unsigned int status; |
35 | |
36 | regmap_read(map: regmap, AT91_PMC_SR, val: &status); |
37 | |
38 | return !!(status & (1 << id)); |
39 | } |
40 | |
41 | static int clk_system_prepare(struct clk_hw *hw) |
42 | { |
43 | struct clk_system *sys = to_clk_system(hw); |
44 | |
45 | regmap_write(map: sys->regmap, AT91_PMC_SCER, val: 1 << sys->id); |
46 | |
47 | if (!is_pck(id: sys->id)) |
48 | return 0; |
49 | |
50 | while (!clk_system_ready(regmap: sys->regmap, id: sys->id)) |
51 | cpu_relax(); |
52 | |
53 | return 0; |
54 | } |
55 | |
56 | static void clk_system_unprepare(struct clk_hw *hw) |
57 | { |
58 | struct clk_system *sys = to_clk_system(hw); |
59 | |
60 | regmap_write(map: sys->regmap, AT91_PMC_SCDR, val: 1 << sys->id); |
61 | } |
62 | |
63 | static int clk_system_is_prepared(struct clk_hw *hw) |
64 | { |
65 | struct clk_system *sys = to_clk_system(hw); |
66 | unsigned int status; |
67 | |
68 | regmap_read(map: sys->regmap, AT91_PMC_SCSR, val: &status); |
69 | |
70 | if (!(status & (1 << sys->id))) |
71 | return 0; |
72 | |
73 | if (!is_pck(id: sys->id)) |
74 | return 1; |
75 | |
76 | regmap_read(map: sys->regmap, AT91_PMC_SR, val: &status); |
77 | |
78 | return !!(status & (1 << sys->id)); |
79 | } |
80 | |
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(hw: &sys->hw); |
96 | } |
97 | |
98 | static const struct clk_ops system_ops = { |
99 | .prepare = clk_system_prepare, |
100 | .unprepare = clk_system_unprepare, |
101 | .is_prepared = clk_system_is_prepared, |
102 | .save_context = clk_system_save_context, |
103 | .restore_context = clk_system_restore_context, |
104 | }; |
105 | |
106 | struct clk_hw * __init |
107 | at91_clk_register_system(struct regmap *regmap, const char *name, |
108 | const char *parent_name, struct clk_hw *parent_hw, u8 id, |
109 | unsigned long flags) |
110 | { |
111 | struct clk_system *sys; |
112 | struct clk_hw *hw; |
113 | struct clk_init_data init = {}; |
114 | int ret; |
115 | |
116 | if (!(parent_name || parent_hw) || id > SYSTEM_MAX_ID) |
117 | return ERR_PTR(error: -EINVAL); |
118 | |
119 | sys = kzalloc(size: sizeof(*sys), GFP_KERNEL); |
120 | if (!sys) |
121 | return ERR_PTR(error: -ENOMEM); |
122 | |
123 | init.name = name; |
124 | init.ops = &system_ops; |
125 | if (parent_hw) |
126 | init.parent_hws = (const struct clk_hw **)&parent_hw; |
127 | else |
128 | init.parent_names = &parent_name; |
129 | init.num_parents = 1; |
130 | init.flags = CLK_SET_RATE_PARENT | flags; |
131 | |
132 | sys->id = id; |
133 | sys->hw.init = &init; |
134 | sys->regmap = regmap; |
135 | |
136 | hw = &sys->hw; |
137 | ret = clk_hw_register(NULL, hw: &sys->hw); |
138 | if (ret) { |
139 | kfree(objp: sys); |
140 | hw = ERR_PTR(error: ret); |
141 | } |
142 | |
143 | return hw; |
144 | } |
145 | |