1 // SPDX-License-Identifier: GPL-2.0
6 #include <linux/clk-provider.h>
7 #include <linux/errno.h>
8 #include <linux/export.h>
10 #include <linux/slab.h>
14 #define PCG_PREDIV_SHIFT 16
15 #define PCG_PREDIV_WIDTH 3
16 #define PCG_PREDIV_MAX 8
18 #define PCG_DIV_SHIFT 0
19 #define PCG_CORE_DIV_WIDTH 3
20 #define PCG_DIV_WIDTH 6
21 #define PCG_DIV_MAX 64
23 #define PCG_PCS_SHIFT 24
24 #define PCG_PCS_MASK 0x7
26 #define PCG_CGC_SHIFT 28
28 static unsigned long imx8m_clk_composite_divider_recalc_rate(struct clk_hw
*hw
,
29 unsigned long parent_rate
)
31 struct clk_divider
*divider
= to_clk_divider(hw
);
32 unsigned long prediv_rate
;
33 unsigned int prediv_value
;
34 unsigned int div_value
;
36 prediv_value
= readl(divider
->reg
) >> divider
->shift
;
37 prediv_value
&= clk_div_mask(divider
->width
);
39 prediv_rate
= divider_recalc_rate(hw
, parent_rate
, prediv_value
,
43 div_value
= readl(divider
->reg
) >> PCG_DIV_SHIFT
;
44 div_value
&= clk_div_mask(PCG_DIV_WIDTH
);
46 return divider_recalc_rate(hw
, prediv_rate
, div_value
, NULL
,
47 divider
->flags
, PCG_DIV_WIDTH
);
50 static int imx8m_clk_composite_compute_dividers(unsigned long rate
,
51 unsigned long parent_rate
,
52 int *prediv
, int *postdiv
)
61 for (div1
= 1; div1
<= PCG_PREDIV_MAX
; div1
++) {
62 for (div2
= 1; div2
<= PCG_DIV_MAX
; div2
++) {
63 int new_error
= ((parent_rate
/ div1
) / div2
) - rate
;
65 if (abs(new_error
) < abs(error
)) {
76 static long imx8m_clk_composite_divider_round_rate(struct clk_hw
*hw
,
83 imx8m_clk_composite_compute_dividers(rate
, *prate
,
84 &prediv_value
, &div_value
);
85 rate
= DIV_ROUND_UP(*prate
, prediv_value
);
87 return DIV_ROUND_UP(rate
, div_value
);
91 static int imx8m_clk_composite_divider_set_rate(struct clk_hw
*hw
,
93 unsigned long parent_rate
)
95 struct clk_divider
*divider
= to_clk_divider(hw
);
102 ret
= imx8m_clk_composite_compute_dividers(rate
, parent_rate
,
103 &prediv_value
, &div_value
);
107 spin_lock_irqsave(divider
->lock
, flags
);
109 val
= readl(divider
->reg
);
110 val
&= ~((clk_div_mask(divider
->width
) << divider
->shift
) |
111 (clk_div_mask(PCG_DIV_WIDTH
) << PCG_DIV_SHIFT
));
113 val
|= (u32
)(prediv_value
- 1) << divider
->shift
;
114 val
|= (u32
)(div_value
- 1) << PCG_DIV_SHIFT
;
115 writel(val
, divider
->reg
);
117 spin_unlock_irqrestore(divider
->lock
, flags
);
122 static const struct clk_ops imx8m_clk_composite_divider_ops
= {
123 .recalc_rate
= imx8m_clk_composite_divider_recalc_rate
,
124 .round_rate
= imx8m_clk_composite_divider_round_rate
,
125 .set_rate
= imx8m_clk_composite_divider_set_rate
,
128 static u8
imx8m_clk_composite_mux_get_parent(struct clk_hw
*hw
)
130 return clk_mux_ops
.get_parent(hw
);
133 static int imx8m_clk_composite_mux_set_parent(struct clk_hw
*hw
, u8 index
)
135 struct clk_mux
*mux
= to_clk_mux(hw
);
136 u32 val
= clk_mux_index_to_val(mux
->table
, mux
->flags
, index
);
137 unsigned long flags
= 0;
141 spin_lock_irqsave(mux
->lock
, flags
);
143 reg
= readl(mux
->reg
);
144 reg
&= ~(mux
->mask
<< mux
->shift
);
145 val
= val
<< mux
->shift
;
148 * write twice to make sure non-target interface
149 * SEL_A/B point the same clk input.
151 writel(reg
, mux
->reg
);
152 writel(reg
, mux
->reg
);
155 spin_unlock_irqrestore(mux
->lock
, flags
);
161 imx8m_clk_composite_mux_determine_rate(struct clk_hw
*hw
,
162 struct clk_rate_request
*req
)
164 return clk_mux_ops
.determine_rate(hw
, req
);
168 static const struct clk_ops imx8m_clk_composite_mux_ops
= {
169 .get_parent
= imx8m_clk_composite_mux_get_parent
,
170 .set_parent
= imx8m_clk_composite_mux_set_parent
,
171 .determine_rate
= imx8m_clk_composite_mux_determine_rate
,
174 struct clk_hw
*imx8m_clk_hw_composite_flags(const char *name
,
175 const char * const *parent_names
,
176 int num_parents
, void __iomem
*reg
,
180 struct clk_hw
*hw
= ERR_PTR(-ENOMEM
), *mux_hw
;
181 struct clk_hw
*div_hw
, *gate_hw
;
182 struct clk_divider
*div
= NULL
;
183 struct clk_gate
*gate
= NULL
;
184 struct clk_mux
*mux
= NULL
;
185 const struct clk_ops
*divider_ops
;
186 const struct clk_ops
*mux_ops
;
188 mux
= kzalloc(sizeof(*mux
), GFP_KERNEL
);
194 mux
->shift
= PCG_PCS_SHIFT
;
195 mux
->mask
= PCG_PCS_MASK
;
196 mux
->lock
= &imx_ccm_lock
;
198 div
= kzalloc(sizeof(*div
), GFP_KERNEL
);
204 if (composite_flags
& IMX_COMPOSITE_CORE
) {
205 div
->shift
= PCG_DIV_SHIFT
;
206 div
->width
= PCG_CORE_DIV_WIDTH
;
207 divider_ops
= &clk_divider_ops
;
208 mux_ops
= &imx8m_clk_composite_mux_ops
;
209 } else if (composite_flags
& IMX_COMPOSITE_BUS
) {
210 div
->shift
= PCG_PREDIV_SHIFT
;
211 div
->width
= PCG_PREDIV_WIDTH
;
212 divider_ops
= &imx8m_clk_composite_divider_ops
;
213 mux_ops
= &imx8m_clk_composite_mux_ops
;
215 div
->shift
= PCG_PREDIV_SHIFT
;
216 div
->width
= PCG_PREDIV_WIDTH
;
217 divider_ops
= &imx8m_clk_composite_divider_ops
;
218 mux_ops
= &clk_mux_ops
;
219 flags
|= CLK_SET_PARENT_GATE
;
222 div
->lock
= &imx_ccm_lock
;
223 div
->flags
= CLK_DIVIDER_ROUND_CLOSEST
;
225 gate
= kzalloc(sizeof(*gate
), GFP_KERNEL
);
231 gate
->bit_idx
= PCG_CGC_SHIFT
;
232 gate
->lock
= &imx_ccm_lock
;
234 hw
= clk_hw_register_composite(NULL
, name
, parent_names
, num_parents
,
235 mux_hw
, mux_ops
, div_hw
,
236 divider_ops
, gate_hw
, &clk_gate_ops
, flags
);
248 EXPORT_SYMBOL_GPL(imx8m_clk_hw_composite_flags
);