1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * DRA7 ATL (Audio Tracking Logic) clock driver |
4 | * |
5 | * Copyright (C) 2013 Texas Instruments, Inc. |
6 | * |
7 | * Peter Ujfalusi <peter.ujfalusi@ti.com> |
8 | */ |
9 | |
10 | #include <linux/init.h> |
11 | #include <linux/clk.h> |
12 | #include <linux/clk-provider.h> |
13 | #include <linux/slab.h> |
14 | #include <linux/io.h> |
15 | #include <linux/of.h> |
16 | #include <linux/of_address.h> |
17 | #include <linux/platform_device.h> |
18 | #include <linux/pm_runtime.h> |
19 | #include <linux/clk/ti.h> |
20 | |
21 | #include "clock.h" |
22 | |
23 | #define DRA7_ATL_INSTANCES 4 |
24 | |
25 | #define DRA7_ATL_PPMR_REG(id) (0x200 + (id * 0x80)) |
26 | #define DRA7_ATL_BBSR_REG(id) (0x204 + (id * 0x80)) |
27 | #define DRA7_ATL_ATLCR_REG(id) (0x208 + (id * 0x80)) |
28 | #define DRA7_ATL_SWEN_REG(id) (0x210 + (id * 0x80)) |
29 | #define DRA7_ATL_BWSMUX_REG(id) (0x214 + (id * 0x80)) |
30 | #define DRA7_ATL_AWSMUX_REG(id) (0x218 + (id * 0x80)) |
31 | #define DRA7_ATL_PCLKMUX_REG(id) (0x21c + (id * 0x80)) |
32 | |
33 | #define DRA7_ATL_SWEN BIT(0) |
34 | #define DRA7_ATL_DIVIDER_MASK (0x1f) |
35 | #define DRA7_ATL_PCLKMUX BIT(0) |
36 | struct dra7_atl_clock_info; |
37 | |
38 | struct dra7_atl_desc { |
39 | struct clk *clk; |
40 | struct clk_hw hw; |
41 | struct dra7_atl_clock_info *cinfo; |
42 | int id; |
43 | |
44 | bool probed; /* the driver for the IP has been loaded */ |
45 | bool valid; /* configured */ |
46 | bool enabled; |
47 | u32 bws; /* Baseband Word Select Mux */ |
48 | u32 aws; /* Audio Word Select Mux */ |
49 | u32 divider; /* Cached divider value */ |
50 | }; |
51 | |
52 | struct dra7_atl_clock_info { |
53 | struct device *dev; |
54 | void __iomem *iobase; |
55 | |
56 | struct dra7_atl_desc *cdesc; |
57 | }; |
58 | |
59 | #define to_atl_desc(_hw) container_of(_hw, struct dra7_atl_desc, hw) |
60 | |
61 | static inline void atl_write(struct dra7_atl_clock_info *cinfo, u32 reg, |
62 | u32 val) |
63 | { |
64 | __raw_writel(val, addr: cinfo->iobase + reg); |
65 | } |
66 | |
67 | static inline int atl_read(struct dra7_atl_clock_info *cinfo, u32 reg) |
68 | { |
69 | return __raw_readl(addr: cinfo->iobase + reg); |
70 | } |
71 | |
72 | static int atl_clk_enable(struct clk_hw *hw) |
73 | { |
74 | struct dra7_atl_desc *cdesc = to_atl_desc(hw); |
75 | |
76 | if (!cdesc->probed) |
77 | goto out; |
78 | |
79 | if (unlikely(!cdesc->valid)) |
80 | dev_warn(cdesc->cinfo->dev, "atl%d has not been configured\n" , |
81 | cdesc->id); |
82 | pm_runtime_get_sync(dev: cdesc->cinfo->dev); |
83 | |
84 | atl_write(cinfo: cdesc->cinfo, DRA7_ATL_ATLCR_REG(cdesc->id), |
85 | val: cdesc->divider - 1); |
86 | atl_write(cinfo: cdesc->cinfo, DRA7_ATL_SWEN_REG(cdesc->id), DRA7_ATL_SWEN); |
87 | |
88 | out: |
89 | cdesc->enabled = true; |
90 | |
91 | return 0; |
92 | } |
93 | |
94 | static void atl_clk_disable(struct clk_hw *hw) |
95 | { |
96 | struct dra7_atl_desc *cdesc = to_atl_desc(hw); |
97 | |
98 | if (!cdesc->probed) |
99 | goto out; |
100 | |
101 | atl_write(cinfo: cdesc->cinfo, DRA7_ATL_SWEN_REG(cdesc->id), val: 0); |
102 | pm_runtime_put_sync(dev: cdesc->cinfo->dev); |
103 | |
104 | out: |
105 | cdesc->enabled = false; |
106 | } |
107 | |
108 | static int atl_clk_is_enabled(struct clk_hw *hw) |
109 | { |
110 | struct dra7_atl_desc *cdesc = to_atl_desc(hw); |
111 | |
112 | return cdesc->enabled; |
113 | } |
114 | |
115 | static unsigned long atl_clk_recalc_rate(struct clk_hw *hw, |
116 | unsigned long parent_rate) |
117 | { |
118 | struct dra7_atl_desc *cdesc = to_atl_desc(hw); |
119 | |
120 | return parent_rate / cdesc->divider; |
121 | } |
122 | |
123 | static long atl_clk_round_rate(struct clk_hw *hw, unsigned long rate, |
124 | unsigned long *parent_rate) |
125 | { |
126 | unsigned divider; |
127 | |
128 | divider = (*parent_rate + rate / 2) / rate; |
129 | if (divider > DRA7_ATL_DIVIDER_MASK + 1) |
130 | divider = DRA7_ATL_DIVIDER_MASK + 1; |
131 | |
132 | return *parent_rate / divider; |
133 | } |
134 | |
135 | static int atl_clk_set_rate(struct clk_hw *hw, unsigned long rate, |
136 | unsigned long parent_rate) |
137 | { |
138 | struct dra7_atl_desc *cdesc; |
139 | u32 divider; |
140 | |
141 | if (!hw || !rate) |
142 | return -EINVAL; |
143 | |
144 | cdesc = to_atl_desc(hw); |
145 | divider = ((parent_rate + rate / 2) / rate) - 1; |
146 | if (divider > DRA7_ATL_DIVIDER_MASK) |
147 | divider = DRA7_ATL_DIVIDER_MASK; |
148 | |
149 | cdesc->divider = divider + 1; |
150 | |
151 | return 0; |
152 | } |
153 | |
154 | static const struct clk_ops atl_clk_ops = { |
155 | .enable = atl_clk_enable, |
156 | .disable = atl_clk_disable, |
157 | .is_enabled = atl_clk_is_enabled, |
158 | .recalc_rate = atl_clk_recalc_rate, |
159 | .round_rate = atl_clk_round_rate, |
160 | .set_rate = atl_clk_set_rate, |
161 | }; |
162 | |
163 | static void __init of_dra7_atl_clock_setup(struct device_node *node) |
164 | { |
165 | struct dra7_atl_desc *clk_hw = NULL; |
166 | struct clk_parent_data pdata = { .index = 0 }; |
167 | struct clk_init_data init = { NULL }; |
168 | const char *name; |
169 | struct clk *clk; |
170 | |
171 | clk_hw = kzalloc(size: sizeof(*clk_hw), GFP_KERNEL); |
172 | if (!clk_hw) { |
173 | pr_err("%s: could not allocate dra7_atl_desc\n" , __func__); |
174 | return; |
175 | } |
176 | |
177 | clk_hw->hw.init = &init; |
178 | clk_hw->divider = 1; |
179 | name = ti_dt_clk_name(np: node); |
180 | init.name = name; |
181 | init.ops = &atl_clk_ops; |
182 | init.flags = CLK_IGNORE_UNUSED; |
183 | init.num_parents = of_clk_get_parent_count(np: node); |
184 | |
185 | if (init.num_parents != 1) { |
186 | pr_err("%s: atl clock %pOFn must have 1 parent\n" , __func__, |
187 | node); |
188 | goto cleanup; |
189 | } |
190 | |
191 | init.parent_data = &pdata; |
192 | clk = of_ti_clk_register(node, hw: &clk_hw->hw, con: name); |
193 | |
194 | if (!IS_ERR(ptr: clk)) { |
195 | of_clk_add_provider(np: node, clk_src_get: of_clk_src_simple_get, data: clk); |
196 | return; |
197 | } |
198 | cleanup: |
199 | kfree(objp: clk_hw); |
200 | } |
201 | CLK_OF_DECLARE(dra7_atl_clock, "ti,dra7-atl-clock" , of_dra7_atl_clock_setup); |
202 | |
203 | static int of_dra7_atl_clk_probe(struct platform_device *pdev) |
204 | { |
205 | struct device_node *node = pdev->dev.of_node; |
206 | struct dra7_atl_clock_info *cinfo; |
207 | int i; |
208 | int ret = 0; |
209 | |
210 | if (!node) |
211 | return -ENODEV; |
212 | |
213 | cinfo = devm_kzalloc(dev: &pdev->dev, size: sizeof(*cinfo), GFP_KERNEL); |
214 | if (!cinfo) |
215 | return -ENOMEM; |
216 | |
217 | cinfo->iobase = of_iomap(node, index: 0); |
218 | cinfo->dev = &pdev->dev; |
219 | pm_runtime_enable(dev: cinfo->dev); |
220 | |
221 | pm_runtime_get_sync(dev: cinfo->dev); |
222 | atl_write(cinfo, DRA7_ATL_PCLKMUX_REG(0), DRA7_ATL_PCLKMUX); |
223 | |
224 | for (i = 0; i < DRA7_ATL_INSTANCES; i++) { |
225 | struct device_node *cfg_node; |
226 | char prop[5]; |
227 | struct dra7_atl_desc *cdesc; |
228 | struct of_phandle_args clkspec; |
229 | struct clk *clk; |
230 | int rc; |
231 | |
232 | rc = of_parse_phandle_with_args(np: node, list_name: "ti,provided-clocks" , |
233 | NULL, index: i, out_args: &clkspec); |
234 | |
235 | if (rc) { |
236 | pr_err("%s: failed to lookup atl clock %d\n" , __func__, |
237 | i); |
238 | ret = -EINVAL; |
239 | goto pm_put; |
240 | } |
241 | |
242 | clk = of_clk_get_from_provider(clkspec: &clkspec); |
243 | if (IS_ERR(ptr: clk)) { |
244 | pr_err("%s: failed to get atl clock %d from provider\n" , |
245 | __func__, i); |
246 | ret = PTR_ERR(ptr: clk); |
247 | goto pm_put; |
248 | } |
249 | |
250 | cdesc = to_atl_desc(__clk_get_hw(clk)); |
251 | cdesc->cinfo = cinfo; |
252 | cdesc->id = i; |
253 | |
254 | /* Get configuration for the ATL instances */ |
255 | snprintf(buf: prop, size: sizeof(prop), fmt: "atl%u" , i); |
256 | cfg_node = of_get_child_by_name(node, name: prop); |
257 | if (cfg_node) { |
258 | ret = of_property_read_u32(np: cfg_node, propname: "bws" , |
259 | out_value: &cdesc->bws); |
260 | ret |= of_property_read_u32(np: cfg_node, propname: "aws" , |
261 | out_value: &cdesc->aws); |
262 | if (!ret) { |
263 | cdesc->valid = true; |
264 | atl_write(cinfo, DRA7_ATL_BWSMUX_REG(i), |
265 | val: cdesc->bws); |
266 | atl_write(cinfo, DRA7_ATL_AWSMUX_REG(i), |
267 | val: cdesc->aws); |
268 | } |
269 | of_node_put(node: cfg_node); |
270 | } |
271 | |
272 | cdesc->probed = true; |
273 | /* |
274 | * Enable the clock if it has been asked prior to loading the |
275 | * hw driver |
276 | */ |
277 | if (cdesc->enabled) |
278 | atl_clk_enable(hw: __clk_get_hw(clk)); |
279 | } |
280 | |
281 | pm_put: |
282 | pm_runtime_put_sync(dev: cinfo->dev); |
283 | return ret; |
284 | } |
285 | |
286 | static const struct of_device_id of_dra7_atl_clk_match_tbl[] = { |
287 | { .compatible = "ti,dra7-atl" , }, |
288 | {}, |
289 | }; |
290 | |
291 | static struct platform_driver dra7_atl_clk_driver = { |
292 | .driver = { |
293 | .name = "dra7-atl" , |
294 | .suppress_bind_attrs = true, |
295 | .of_match_table = of_dra7_atl_clk_match_tbl, |
296 | }, |
297 | .probe = of_dra7_atl_clk_probe, |
298 | }; |
299 | builtin_platform_driver(dra7_atl_clk_driver); |
300 | |