File Index
Files and directories
Sign in
FILE

clk-flexgen.c

File preview with syntax highlighting.

linux-6.19.14/drivers/clk/st/clk-flexgen.c C
  1// SPDX-License-Identifier: GPL-2.0-only
  2/*
  3 * clk-flexgen.c
  4 *
  5 * Copyright (C) ST-Microelectronics SA 2013
  6 * Author:  Maxime Coquelin <maxime.coquelin@st.com> for ST-Microelectronics.
  7 */
  8
  9#include <linux/clk.h>
 10#include <linux/clk-provider.h>
 11#include <linux/module.h>
 12#include <linux/slab.h>
 13#include <linux/io.h>
 14#include <linux/err.h>
 15#include <linux/string.h>
 16#include <linux/of.h>
 17#include <linux/of_address.h>
 18
 19struct clkgen_clk_out {
 20	const char *name;
 21	unsigned long flags;
 22};
 23
 24struct clkgen_data {
 25	unsigned long flags;
 26	bool mode;
 27	const struct clkgen_clk_out *outputs;
 28	const unsigned int outputs_nb;
 29};
 30
 31struct flexgen {
 32	struct clk_hw hw;
 33
 34	/* Crossbar */
 35	struct clk_mux mux;
 36	/* Pre-divisor's gate */
 37	struct clk_gate pgate;
 38	/* Pre-divisor */
 39	struct clk_divider pdiv;
 40	/* Final divisor's gate */
 41	struct clk_gate fgate;
 42	/* Final divisor */
 43	struct clk_divider fdiv;
 44	/* Asynchronous mode control */
 45	struct clk_gate sync;
 46	/* hw control flags */
 47	bool control_mode;
 48};
 49
 50#define to_flexgen(_hw) container_of(_hw, struct flexgen, hw)
 51#define to_clk_gate(_hw) container_of(_hw, struct clk_gate, hw)
 52
 53static int flexgen_enable(struct clk_hw *hw)
 54{
 55	struct flexgen *flexgen = to_flexgen(hw);
 56	struct clk_hw *pgate_hw = &flexgen->pgate.hw;
 57	struct clk_hw *fgate_hw = &flexgen->fgate.hw;
 58
 59	__clk_hw_set_clk(pgate_hw, hw);
 60	__clk_hw_set_clk(fgate_hw, hw);
 61
 62	clk_gate_ops.enable(pgate_hw);
 63
 64	clk_gate_ops.enable(fgate_hw);
 65
 66	pr_debug("%s: flexgen output enabled\n", clk_hw_get_name(hw));
 67	return 0;
 68}
 69
 70static void flexgen_disable(struct clk_hw *hw)
 71{
 72	struct flexgen *flexgen = to_flexgen(hw);
 73	struct clk_hw *fgate_hw = &flexgen->fgate.hw;
 74
 75	/* disable only the final gate */
 76	__clk_hw_set_clk(fgate_hw, hw);
 77
 78	clk_gate_ops.disable(fgate_hw);
 79
 80	pr_debug("%s: flexgen output disabled\n", clk_hw_get_name(hw));
 81}
 82
 83static int flexgen_is_enabled(struct clk_hw *hw)
 84{
 85	struct flexgen *flexgen = to_flexgen(hw);
 86	struct clk_hw *fgate_hw = &flexgen->fgate.hw;
 87
 88	__clk_hw_set_clk(fgate_hw, hw);
 89
 90	if (!clk_gate_ops.is_enabled(fgate_hw))
 91		return 0;
 92
 93	return 1;
 94}
 95
 96static u8 flexgen_get_parent(struct clk_hw *hw)
 97{
 98	struct flexgen *flexgen = to_flexgen(hw);
 99	struct clk_hw *mux_hw = &flexgen->mux.hw;
100
101	__clk_hw_set_clk(mux_hw, hw);
102
103	return clk_mux_ops.get_parent(mux_hw);
104}
105
106static int flexgen_set_parent(struct clk_hw *hw, u8 index)
107{
108	struct flexgen *flexgen = to_flexgen(hw);
109	struct clk_hw *mux_hw = &flexgen->mux.hw;
110
111	__clk_hw_set_clk(mux_hw, hw);
112
113	return clk_mux_ops.set_parent(mux_hw, index);
114}
115
116static inline unsigned long
117clk_best_div(unsigned long parent_rate, unsigned long rate)
118{
119	return parent_rate / rate + ((rate > (2*(parent_rate % rate))) ? 0 : 1);
120}
121
122static int flexgen_determine_rate(struct clk_hw *hw,
123				  struct clk_rate_request *req)
124{
125	unsigned long div;
126
127	/* Round div according to exact prate and wished rate */
128	div = clk_best_div(req->best_parent_rate, req->rate);
129
130	if (clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT) {
131		req->best_parent_rate = req->rate * div;
132		return 0;
133	}
134
135	req->rate = req->best_parent_rate / div;
136	return 0;
137}
138
139static unsigned long flexgen_recalc_rate(struct clk_hw *hw,
140		unsigned long parent_rate)
141{
142	struct flexgen *flexgen = to_flexgen(hw);
143	struct clk_hw *pdiv_hw = &flexgen->pdiv.hw;
144	struct clk_hw *fdiv_hw = &flexgen->fdiv.hw;
145	unsigned long mid_rate;
146
147	__clk_hw_set_clk(pdiv_hw, hw);
148	__clk_hw_set_clk(fdiv_hw, hw);
149
150	mid_rate = clk_divider_ops.recalc_rate(pdiv_hw, parent_rate);
151
152	return clk_divider_ops.recalc_rate(fdiv_hw, mid_rate);
153}
154
155static int flexgen_set_rate(struct clk_hw *hw, unsigned long rate,
156				unsigned long parent_rate)
157{
158	struct flexgen *flexgen = to_flexgen(hw);
159	struct clk_hw *pdiv_hw = &flexgen->pdiv.hw;
160	struct clk_hw *fdiv_hw = &flexgen->fdiv.hw;
161	struct clk_hw *sync_hw = &flexgen->sync.hw;
162	struct clk_gate *config = to_clk_gate(sync_hw);
163	unsigned long div = 0;
164	int ret = 0;
165	u32 reg;
166
167	__clk_hw_set_clk(pdiv_hw, hw);
168	__clk_hw_set_clk(fdiv_hw, hw);
169
170	if (flexgen->control_mode) {
171		reg = readl(config->reg);
172		reg &= ~BIT(config->bit_idx);
173		writel(reg, config->reg);
174	}
175
176	div = clk_best_div(parent_rate, rate);
177
178	/*
179	* pdiv is mainly targeted for low freq results, while fdiv
180	* should be used for div <= 64. The other way round can
181	* lead to 'duty cycle' issues.
182	*/
183
184	if (div <= 64) {
185		clk_divider_ops.set_rate(pdiv_hw, parent_rate, parent_rate);
186		ret = clk_divider_ops.set_rate(fdiv_hw, rate, rate * div);
187	} else {
188		clk_divider_ops.set_rate(fdiv_hw, parent_rate, parent_rate);
189		ret = clk_divider_ops.set_rate(pdiv_hw, rate, rate * div);
190	}
191
192	return ret;
193}
194
195static const struct clk_ops flexgen_ops = {
196	.enable = flexgen_enable,
197	.disable = flexgen_disable,
198	.is_enabled = flexgen_is_enabled,
199	.get_parent = flexgen_get_parent,
200	.set_parent = flexgen_set_parent,
201	.determine_rate = flexgen_determine_rate,
202	.recalc_rate = flexgen_recalc_rate,
203	.set_rate = flexgen_set_rate,
204};
205
206static struct clk *clk_register_flexgen(const char *name,
207				const char **parent_names, u8 num_parents,
208				void __iomem *reg, spinlock_t *lock, u32 idx,
209				unsigned long flexgen_flags, bool mode) {
210	struct flexgen *fgxbar;
211	struct clk *clk;
212	struct clk_init_data init;
213	u32  xbar_shift;
214	void __iomem *xbar_reg, *fdiv_reg;
215
216	fgxbar = kzalloc(sizeof(struct flexgen), GFP_KERNEL);
217	if (!fgxbar)
218		return ERR_PTR(-ENOMEM);
219
220	init.name = name;
221	init.ops = &flexgen_ops;
222	init.flags = CLK_GET_RATE_NOCACHE | flexgen_flags;
223	init.parent_names = parent_names;
224	init.num_parents = num_parents;
225
226	xbar_reg = reg + 0x18 + (idx & ~0x3);
227	xbar_shift = (idx % 4) * 0x8;
228	fdiv_reg = reg + 0x164 + idx * 4;
229
230	/* Crossbar element config */
231	fgxbar->mux.lock = lock;
232	fgxbar->mux.mask = BIT(6) - 1;
233	fgxbar->mux.reg = xbar_reg;
234	fgxbar->mux.shift = xbar_shift;
235	fgxbar->mux.table = NULL;
236
237
238	/* Pre-divider's gate config (in xbar register)*/
239	fgxbar->pgate.lock = lock;
240	fgxbar->pgate.reg = xbar_reg;
241	fgxbar->pgate.bit_idx = xbar_shift + 6;
242
243	/* Pre-divider config */
244	fgxbar->pdiv.lock = lock;
245	fgxbar->pdiv.reg = reg + 0x58 + idx * 4;
246	fgxbar->pdiv.width = 10;
247
248	/* Final divider's gate config */
249	fgxbar->fgate.lock = lock;
250	fgxbar->fgate.reg = fdiv_reg;
251	fgxbar->fgate.bit_idx = 6;
252
253	/* Final divider config */
254	fgxbar->fdiv.lock = lock;
255	fgxbar->fdiv.reg = fdiv_reg;
256	fgxbar->fdiv.width = 6;
257
258	/* Final divider sync config */
259	fgxbar->sync.lock = lock;
260	fgxbar->sync.reg = fdiv_reg;
261	fgxbar->sync.bit_idx = 7;
262
263	fgxbar->control_mode = mode;
264
265	fgxbar->hw.init = &init;
266
267	clk = clk_register(NULL, &fgxbar->hw);
268	if (IS_ERR(clk))
269		kfree(fgxbar);
270	else
271		pr_debug("%s: parent %s rate %u\n",
272			__clk_get_name(clk),
273			__clk_get_name(clk_get_parent(clk)),
274			(unsigned int)clk_get_rate(clk));
275	return clk;
276}
277
278static const char ** __init flexgen_get_parents(struct device_node *np,
279						       int *num_parents)
280{
281	const char **parents;
282	unsigned int nparents;
283
284	nparents = of_clk_get_parent_count(np);
285	if (WARN_ON(!nparents))
286		return NULL;
287
288	parents = kcalloc(nparents, sizeof(const char *), GFP_KERNEL);
289	if (!parents)
290		return NULL;
291
292	*num_parents = of_clk_parent_fill(np, parents, nparents);
293
294	return parents;
295}
296
297static const struct clkgen_data clkgen_audio = {
298	.flags = CLK_SET_RATE_PARENT,
299};
300
301static const struct clkgen_data clkgen_video = {
302	.flags = CLK_SET_RATE_PARENT,
303	.mode = 1,
304};
305
306static const struct clkgen_clk_out clkgen_stih410_a0_clk_out[] = {
307	/* Those clks need to be on so that memory interface is accessible */
308	{ .name = "clk-ic-lmi0", .flags = CLK_IS_CRITICAL },
309	{ .name = "clk-ic-lmi1", .flags = CLK_IS_CRITICAL },
310};
311
312static const struct clkgen_data clkgen_stih410_a0 = {
313	.outputs = clkgen_stih410_a0_clk_out,
314	.outputs_nb = ARRAY_SIZE(clkgen_stih410_a0_clk_out),
315};
316
317static const struct clkgen_clk_out clkgen_stih410_c0_clk_out[] = {
318	{ .name = "clk-icn-gpu", },
319	{ .name = "clk-fdma", },
320	{ .name = "clk-nand", },
321	{ .name = "clk-hva", },
322	{ .name = "clk-proc-stfe", },
323	{ .name = "clk-proc-tp", },
324	{ .name = "clk-rx-icn-dmu", },
325	{ .name = "clk-rx-icn-hva", },
326	/* This clk needs to be on to keep bus interconnect alive */
327	{ .name = "clk-icn-cpu", .flags = CLK_IS_CRITICAL },
328	/* This clk needs to be on to keep bus interconnect alive */
329	{ .name = "clk-tx-icn-dmu", .flags = CLK_IS_CRITICAL },
330	{ .name = "clk-mmc-0", },
331	{ .name = "clk-mmc-1", },
332	{ .name = "clk-jpegdec", },
333	/* This clk needs to be on to keep A9 running */
334	{ .name = "clk-ext2fa9", .flags = CLK_IS_CRITICAL },
335	{ .name = "clk-ic-bdisp-0", },
336	{ .name = "clk-ic-bdisp-1", },
337	{ .name = "clk-pp-dmu", },
338	{ .name = "clk-vid-dmu", },
339	{ .name = "clk-dss-lpc", },
340	{ .name = "clk-st231-aud-0", },
341	{ .name = "clk-st231-gp-1", },
342	{ .name = "clk-st231-dmu", },
343	/* This clk needs to be on to keep bus interconnect alive */
344	{ .name = "clk-icn-lmi", .flags = CLK_IS_CRITICAL },
345	{ .name = "clk-tx-icn-disp-1", },
346	/* This clk needs to be on to keep bus interconnect alive */
347	{ .name = "clk-icn-sbc", .flags = CLK_IS_CRITICAL },
348	{ .name = "clk-stfe-frc2", },
349	{ .name = "clk-eth-phy", },
350	{ .name = "clk-eth-ref-phyclk", },
351	{ .name = "clk-flash-promip", },
352	{ .name = "clk-main-disp", },
353	{ .name = "clk-aux-disp", },
354	{ .name = "clk-compo-dvp", },
355	{ .name = "clk-tx-icn-hades", },
356	{ .name = "clk-rx-icn-hades", },
357	/* This clk needs to be on to keep bus interconnect alive */
358	{ .name = "clk-icn-reg-16", .flags = CLK_IS_CRITICAL },
359	{ .name = "clk-pp-hades", },
360	{ .name = "clk-clust-hades", },
361	{ .name = "clk-hwpe-hades", },
362	{ .name = "clk-fc-hades", },
363};
364
365static const struct clkgen_data clkgen_stih410_c0 = {
366	.outputs = clkgen_stih410_c0_clk_out,
367	.outputs_nb = ARRAY_SIZE(clkgen_stih410_c0_clk_out),
368};
369
370static const struct clkgen_clk_out clkgen_stih418_c0_clk_out[] = {
371	{ .name = "clk-icn-gpu", },
372	{ .name = "clk-fdma", },
373	{ .name = "clk-nand", },
374	{ .name = "clk-hva", },
375	{ .name = "clk-proc-stfe", },
376	{ .name = "clk-tp", },
377	/* This clk needs to be on to keep bus interconnect alive */
378	{ .name = "clk-rx-icn-dmu", .flags = CLK_IS_CRITICAL },
379	/* This clk needs to be on to keep bus interconnect alive */
380	{ .name = "clk-rx-icn-hva", .flags = CLK_IS_CRITICAL },
381	{ .name = "clk-icn-cpu", .flags = CLK_IS_CRITICAL },
382	/* This clk needs to be on to keep bus interconnect alive */
383	{ .name = "clk-tx-icn-dmu", .flags = CLK_IS_CRITICAL },
384	{ .name = "clk-mmc-0", },
385	{ .name = "clk-mmc-1", },
386	{ .name = "clk-jpegdec", },
387	/* This clk needs to be on to keep bus interconnect alive */
388	{ .name = "clk-icn-reg", .flags = CLK_IS_CRITICAL },
389	{ .name = "clk-proc-bdisp-0", },
390	{ .name = "clk-proc-bdisp-1", },
391	{ .name = "clk-pp-dmu", },
392	{ .name = "clk-vid-dmu", },
393	{ .name = "clk-dss-lpc", },
394	{ .name = "clk-st231-aud-0", },
395	{ .name = "clk-st231-gp-1", },
396	{ .name = "clk-st231-dmu", },
397	/* This clk needs to be on to keep bus interconnect alive */
398	{ .name = "clk-icn-lmi", .flags = CLK_IS_CRITICAL },
399	/* This clk needs to be on to keep bus interconnect alive */
400	{ .name = "clk-tx-icn-1", .flags = CLK_IS_CRITICAL },
401	/* This clk needs to be on to keep bus interconnect alive */
402	{ .name = "clk-icn-sbc", .flags = CLK_IS_CRITICAL },
403	{ .name = "clk-stfe-frc2", },
404	{ .name = "clk-eth-phyref", },
405	{ .name = "clk-eth-ref-phyclk", },
406	{ .name = "clk-flash-promip", },
407	{ .name = "clk-main-disp", },
408	{ .name = "clk-aux-disp", },
409	{ .name = "clk-compo-dvp", },
410	/* This clk needs to be on to keep bus interconnect alive */
411	{ .name = "clk-tx-icn-hades", .flags = CLK_IS_CRITICAL },
412	/* This clk needs to be on to keep bus interconnect alive */
413	{ .name = "clk-rx-icn-hades", .flags = CLK_IS_CRITICAL },
414	/* This clk needs to be on to keep bus interconnect alive */
415	{ .name = "clk-icn-reg-16", .flags = CLK_IS_CRITICAL },
416	{ .name = "clk-pp-hevc", },
417	{ .name = "clk-clust-hevc", },
418	{ .name = "clk-hwpe-hevc", },
419	{ .name = "clk-fc-hevc", },
420	{ .name = "clk-proc-mixer", },
421	{ .name = "clk-proc-sc", },
422	{ .name = "clk-avsp-hevc", },
423};
424
425static const struct clkgen_data clkgen_stih418_c0 = {
426	.outputs = clkgen_stih418_c0_clk_out,
427	.outputs_nb = ARRAY_SIZE(clkgen_stih418_c0_clk_out),
428};
429
430static const struct clkgen_clk_out clkgen_stih410_d0_clk_out[] = {
431	{ .name = "clk-pcm-0", },
432	{ .name = "clk-pcm-1", },
433	{ .name = "clk-pcm-2", },
434	{ .name = "clk-spdiff", },
435	{ .name = "clk-pcmr10-master", },
436	{ .name = "clk-usb2-phy", },
437};
438
439static const struct clkgen_data clkgen_stih410_d0 = {
440	.flags = CLK_SET_RATE_PARENT,
441	.outputs = clkgen_stih410_d0_clk_out,
442	.outputs_nb = ARRAY_SIZE(clkgen_stih410_d0_clk_out),
443};
444
445static const struct clkgen_clk_out clkgen_stih407_d2_clk_out[] = {
446	{ .name = "clk-pix-main-disp", },
447	{ .name = "clk-pix-pip", },
448	{ .name = "clk-pix-gdp1", },
449	{ .name = "clk-pix-gdp2", },
450	{ .name = "clk-pix-gdp3", },
451	{ .name = "clk-pix-gdp4", },
452	{ .name = "clk-pix-aux-disp", },
453	{ .name = "clk-denc", },
454	{ .name = "clk-pix-hddac", },
455	{ .name = "clk-hddac", },
456	{ .name = "clk-sddac", },
457	{ .name = "clk-pix-dvo", },
458	{ .name = "clk-dvo", },
459	{ .name = "clk-pix-hdmi", },
460	{ .name = "clk-tmds-hdmi", },
461	{ .name = "clk-ref-hdmiphy", },
462};
463
464static const struct clkgen_data clkgen_stih407_d2 = {
465	.outputs = clkgen_stih407_d2_clk_out,
466	.outputs_nb = ARRAY_SIZE(clkgen_stih407_d2_clk_out),
467	.flags = CLK_SET_RATE_PARENT,
468	.mode = 1,
469};
470
471static const struct clkgen_clk_out clkgen_stih418_d2_clk_out[] = {
472	{ .name = "clk-pix-main-disp", },
473	{ .name = "", },
474	{ .name = "", },
475	{ .name = "", },
476	{ .name = "", },
477	{ .name = "clk-tmds-hdmi-div2", },
478	{ .name = "clk-pix-aux-disp", },
479	{ .name = "clk-denc", },
480	{ .name = "clk-pix-hddac", },
481	{ .name = "clk-hddac", },
482	{ .name = "clk-sddac", },
483	{ .name = "clk-pix-dvo", },
484	{ .name = "clk-dvo", },
485	{ .name = "clk-pix-hdmi", },
486	{ .name = "clk-tmds-hdmi", },
487	{ .name = "clk-ref-hdmiphy", },
488	{ .name = "", }, { .name = "", }, { .name = "", }, { .name = "", },
489	{ .name = "", }, { .name = "", }, { .name = "", }, { .name = "", },
490	{ .name = "", }, { .name = "", }, { .name = "", }, { .name = "", },
491	{ .name = "", }, { .name = "", }, { .name = "", }, { .name = "", },
492	{ .name = "", }, { .name = "", }, { .name = "", }, { .name = "", },
493	{ .name = "", }, { .name = "", }, { .name = "", }, { .name = "", },
494	{ .name = "", }, { .name = "", }, { .name = "", }, { .name = "", },
495	{ .name = "", }, { .name = "", }, { .name = "", },
496	{ .name = "clk-vp9", },
497};
498
499static const struct clkgen_data clkgen_stih418_d2 = {
500	.outputs = clkgen_stih418_d2_clk_out,
501	.outputs_nb = ARRAY_SIZE(clkgen_stih418_d2_clk_out),
502	.flags = CLK_SET_RATE_PARENT,
503	.mode = 1,
504};
505
506static const struct clkgen_clk_out clkgen_stih407_d3_clk_out[] = {
507	{ .name = "clk-stfe-frc1", },
508	{ .name = "clk-tsout-0", },
509	{ .name = "clk-tsout-1", },
510	{ .name = "clk-mchi", },
511	{ .name = "clk-vsens-compo", },
512	{ .name = "clk-frc1-remote", },
513	{ .name = "clk-lpc-0", },
514	{ .name = "clk-lpc-1", },
515};
516
517static const struct clkgen_data clkgen_stih407_d3 = {
518	.outputs = clkgen_stih407_d3_clk_out,
519	.outputs_nb = ARRAY_SIZE(clkgen_stih407_d3_clk_out),
520};
521
522static const struct of_device_id flexgen_of_match[] = {
523	{
524		.compatible = "st,flexgen-audio",
525		.data = &clkgen_audio,
526	},
527	{
528		.compatible = "st,flexgen-video",
529		.data = &clkgen_video,
530	},
531	{
532		.compatible = "st,flexgen-stih410-a0",
533		.data = &clkgen_stih410_a0,
534	},
535	{
536		.compatible = "st,flexgen-stih410-c0",
537		.data = &clkgen_stih410_c0,
538	},
539	{
540		.compatible = "st,flexgen-stih418-c0",
541		.data = &clkgen_stih418_c0,
542	},
543	{
544		.compatible = "st,flexgen-stih410-d0",
545		.data = &clkgen_stih410_d0,
546	},
547	{
548		.compatible = "st,flexgen-stih407-d2",
549		.data = &clkgen_stih407_d2,
550	},
551	{
552		.compatible = "st,flexgen-stih418-d2",
553		.data = &clkgen_stih418_d2,
554	},
555	{
556		.compatible = "st,flexgen-stih407-d3",
557		.data = &clkgen_stih407_d3,
558	},
559	{}
560};
561
562static void __init st_of_flexgen_setup(struct device_node *np)
563{
564	struct device_node *pnode;
565	void __iomem *reg;
566	struct clk_onecell_data *clk_data;
567	const char **parents;
568	int num_parents, i;
569	spinlock_t *rlock = NULL;
570	const struct of_device_id *match;
571	struct clkgen_data *data = NULL;
572	unsigned long flex_flags = 0;
573	int ret;
574	bool clk_mode = 0;
575	const char *clk_name;
576
577	pnode = of_get_parent(np);
578	if (!pnode)
579		return;
580
581	reg = of_iomap(pnode, 0);
582	of_node_put(pnode);
583	if (!reg)
584		return;
585
586	parents = flexgen_get_parents(np, &num_parents);
587	if (!parents) {
588		iounmap(reg);
589		return;
590	}
591
592	match = of_match_node(flexgen_of_match, np);
593	if (match) {
594		data = (struct clkgen_data *)match->data;
595		flex_flags = data->flags;
596		clk_mode = data->mode;
597	}
598
599	clk_data = kzalloc(sizeof(*clk_data), GFP_KERNEL);
600	if (!clk_data)
601		goto err;
602
603	/* First try to get output information from the compatible data */
604	if (!data || !data->outputs_nb || !data->outputs) {
605		ret = of_property_count_strings(np, "clock-output-names");
606		if (ret <= 0) {
607			pr_err("%s: Failed to get number of output clocks (%d)",
608					__func__, clk_data->clk_num);
609			goto err;
610		}
611		clk_data->clk_num = ret;
612	} else
613		clk_data->clk_num = data->outputs_nb;
614
615	clk_data->clks = kcalloc(clk_data->clk_num, sizeof(struct clk *),
616			GFP_KERNEL);
617	if (!clk_data->clks)
618		goto err;
619
620	rlock = kzalloc(sizeof(spinlock_t), GFP_KERNEL);
621	if (!rlock)
622		goto err;
623
624	spin_lock_init(rlock);
625
626	for (i = 0; i < clk_data->clk_num; i++) {
627		struct clk *clk;
628
629		if (!data || !data->outputs_nb || !data->outputs) {
630			if (of_property_read_string_index(np,
631							  "clock-output-names",
632							  i, &clk_name))
633				break;
634			flex_flags &= ~CLK_IS_CRITICAL;
635			of_clk_detect_critical(np, i, &flex_flags);
636		} else {
637			clk_name = data->outputs[i].name;
638			flex_flags = data->flags | data->outputs[i].flags;
639		}
640
641		/*
642		 * If we read an empty clock name then the output is unused
643		 */
644		if (*clk_name == '\0')
645			continue;
646
647		clk = clk_register_flexgen(clk_name, parents, num_parents,
648					   reg, rlock, i, flex_flags, clk_mode);
649
650		if (IS_ERR(clk))
651			goto err;
652
653		clk_data->clks[i] = clk;
654	}
655
656	kfree(parents);
657	of_clk_add_provider(np, of_clk_src_onecell_get, clk_data);
658
659	return;
660
661err:
662	iounmap(reg);
663	if (clk_data)
664		kfree(clk_data->clks);
665	kfree(clk_data);
666	kfree(parents);
667	kfree(rlock);
668}
669CLK_OF_DECLARE(flexgen, "st,flexgen", st_of_flexgen_setup);
~/ Read-only file preview.