1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Toshiba Visconti clock controller |
4 | * |
5 | * Copyright (c) 2021 TOSHIBA CORPORATION |
6 | * Copyright (c) 2021 Toshiba Electronic Devices & Storage Corporation |
7 | * |
8 | * Nobuhiro Iwamatsu <nobuhiro1.iwamatsu@toshiba.co.jp> |
9 | */ |
10 | |
11 | #include <linux/clk-provider.h> |
12 | #include <linux/delay.h> |
13 | #include <linux/device.h> |
14 | #include <linux/io.h> |
15 | #include <linux/of.h> |
16 | #include <linux/of_address.h> |
17 | #include <linux/regmap.h> |
18 | #include <linux/slab.h> |
19 | #include <linux/string.h> |
20 | |
21 | #include "clkc.h" |
22 | |
23 | static inline struct visconti_clk_gate *to_visconti_clk_gate(struct clk_hw *hw) |
24 | { |
25 | return container_of(hw, struct visconti_clk_gate, hw); |
26 | } |
27 | |
28 | static int visconti_gate_clk_is_enabled(struct clk_hw *hw) |
29 | { |
30 | struct visconti_clk_gate *gate = to_visconti_clk_gate(hw); |
31 | u32 clk = BIT(gate->ck_idx); |
32 | u32 val; |
33 | |
34 | regmap_read(map: gate->regmap, reg: gate->ckon_offset, val: &val); |
35 | return (val & clk) ? 1 : 0; |
36 | } |
37 | |
38 | static void visconti_gate_clk_disable(struct clk_hw *hw) |
39 | { |
40 | struct visconti_clk_gate *gate = to_visconti_clk_gate(hw); |
41 | u32 clk = BIT(gate->ck_idx); |
42 | unsigned long flags; |
43 | |
44 | spin_lock_irqsave(gate->lock, flags); |
45 | |
46 | if (!visconti_gate_clk_is_enabled(hw)) { |
47 | spin_unlock_irqrestore(lock: gate->lock, flags); |
48 | return; |
49 | } |
50 | |
51 | regmap_update_bits(map: gate->regmap, reg: gate->ckoff_offset, mask: clk, val: clk); |
52 | spin_unlock_irqrestore(lock: gate->lock, flags); |
53 | } |
54 | |
55 | static int visconti_gate_clk_enable(struct clk_hw *hw) |
56 | { |
57 | struct visconti_clk_gate *gate = to_visconti_clk_gate(hw); |
58 | u32 clk = BIT(gate->ck_idx); |
59 | unsigned long flags; |
60 | |
61 | spin_lock_irqsave(gate->lock, flags); |
62 | regmap_update_bits(map: gate->regmap, reg: gate->ckon_offset, mask: clk, val: clk); |
63 | spin_unlock_irqrestore(lock: gate->lock, flags); |
64 | |
65 | return 0; |
66 | } |
67 | |
68 | static const struct clk_ops visconti_clk_gate_ops = { |
69 | .enable = visconti_gate_clk_enable, |
70 | .disable = visconti_gate_clk_disable, |
71 | .is_enabled = visconti_gate_clk_is_enabled, |
72 | }; |
73 | |
74 | static struct clk_hw *visconti_clk_register_gate(struct device *dev, |
75 | const char *name, |
76 | const char *parent_name, |
77 | struct regmap *regmap, |
78 | const struct visconti_clk_gate_table *clks, |
79 | u32 rson_offset, |
80 | u32 rsoff_offset, |
81 | u8 rs_idx, |
82 | spinlock_t *lock) |
83 | { |
84 | struct visconti_clk_gate *gate; |
85 | struct clk_parent_data *pdata; |
86 | struct clk_init_data init; |
87 | struct clk_hw *hw; |
88 | int ret; |
89 | |
90 | pdata = devm_kzalloc(dev, size: sizeof(*pdata), GFP_KERNEL); |
91 | if (!pdata) |
92 | return ERR_PTR(error: -ENOMEM); |
93 | |
94 | pdata->name = pdata->fw_name = parent_name; |
95 | |
96 | gate = devm_kzalloc(dev, size: sizeof(*gate), GFP_KERNEL); |
97 | if (!gate) |
98 | return ERR_PTR(error: -ENOMEM); |
99 | |
100 | init.name = name; |
101 | init.ops = &visconti_clk_gate_ops; |
102 | init.flags = clks->flags; |
103 | init.parent_data = pdata; |
104 | init.num_parents = 1; |
105 | |
106 | gate->regmap = regmap; |
107 | gate->ckon_offset = clks->ckon_offset; |
108 | gate->ckoff_offset = clks->ckoff_offset; |
109 | gate->ck_idx = clks->ck_idx; |
110 | gate->rson_offset = rson_offset; |
111 | gate->rsoff_offset = rsoff_offset; |
112 | gate->rs_idx = rs_idx; |
113 | gate->lock = lock; |
114 | gate->hw.init = &init; |
115 | |
116 | hw = &gate->hw; |
117 | ret = devm_clk_hw_register(dev, hw); |
118 | if (ret) |
119 | hw = ERR_PTR(error: ret); |
120 | |
121 | return hw; |
122 | } |
123 | |
124 | int visconti_clk_register_gates(struct visconti_clk_provider *ctx, |
125 | const struct visconti_clk_gate_table *clks, |
126 | int num_gate, |
127 | const struct visconti_reset_data *reset, |
128 | spinlock_t *lock) |
129 | { |
130 | struct device *dev = ctx->dev; |
131 | int i; |
132 | |
133 | for (i = 0; i < num_gate; i++) { |
134 | const char *parent_div_name = clks[i].parent_data[0].name; |
135 | struct clk_parent_data *pdata; |
136 | u32 rson_offset, rsoff_offset; |
137 | struct clk_hw *gate_clk; |
138 | struct clk_hw *div_clk; |
139 | char *dev_name; |
140 | u8 rs_idx; |
141 | |
142 | pdata = devm_kzalloc(dev, size: sizeof(*pdata), GFP_KERNEL); |
143 | if (!pdata) |
144 | return -ENOMEM; |
145 | |
146 | dev_name = devm_kasprintf(dev, GFP_KERNEL, fmt: "%s_div" , clks[i].name); |
147 | if (!dev_name) |
148 | return -ENOMEM; |
149 | |
150 | if (clks[i].rs_id != NO_RESET) { |
151 | rson_offset = reset[clks[i].rs_id].rson_offset; |
152 | rsoff_offset = reset[clks[i].rs_id].rsoff_offset; |
153 | rs_idx = reset[clks[i].rs_id].rs_idx; |
154 | } else { |
155 | rson_offset = rsoff_offset = rs_idx = -1; |
156 | } |
157 | |
158 | div_clk = devm_clk_hw_register_fixed_factor(dev, |
159 | name: dev_name, |
160 | parent_name: parent_div_name, |
161 | flags: 0, mult: 1, |
162 | div: clks[i].div); |
163 | if (IS_ERR(ptr: div_clk)) |
164 | return PTR_ERR(ptr: div_clk); |
165 | |
166 | gate_clk = visconti_clk_register_gate(dev, |
167 | name: clks[i].name, |
168 | parent_name: dev_name, |
169 | regmap: ctx->regmap, |
170 | clks: &clks[i], |
171 | rson_offset, |
172 | rsoff_offset, |
173 | rs_idx, |
174 | lock); |
175 | if (IS_ERR(ptr: gate_clk)) { |
176 | dev_err(dev, "%s: failed to register clock %s\n" , |
177 | __func__, clks[i].name); |
178 | return PTR_ERR(ptr: gate_clk); |
179 | } |
180 | |
181 | ctx->clk_data.hws[clks[i].id] = gate_clk; |
182 | } |
183 | |
184 | return 0; |
185 | } |
186 | |
187 | struct visconti_clk_provider *visconti_init_clk(struct device *dev, |
188 | struct regmap *regmap, |
189 | unsigned long nr_clks) |
190 | { |
191 | struct visconti_clk_provider *ctx; |
192 | int i; |
193 | |
194 | ctx = devm_kzalloc(dev, struct_size(ctx, clk_data.hws, nr_clks), GFP_KERNEL); |
195 | if (!ctx) |
196 | return ERR_PTR(error: -ENOMEM); |
197 | |
198 | for (i = 0; i < nr_clks; ++i) |
199 | ctx->clk_data.hws[i] = ERR_PTR(error: -ENOENT); |
200 | ctx->clk_data.num = nr_clks; |
201 | |
202 | ctx->dev = dev; |
203 | ctx->regmap = regmap; |
204 | |
205 | return ctx; |
206 | } |
207 | |