1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com>
6 #include <linux/clk-provider.h>
7 #include <linux/clkdev.h>
8 #include <linux/clk/at91_pmc.h>
10 #include <linux/mfd/syscon.h>
11 #include <linux/regmap.h>
15 #define SAM9X5_USB_DIV_SHIFT 8
16 #define SAM9X5_USB_MAX_DIV 0xf
18 #define RM9200_USB_DIV_SHIFT 28
19 #define RM9200_USB_DIV_TAB_SIZE 4
21 #define SAM9X5_USBS_MASK GENMASK(0, 0)
22 #define SAM9X60_USBS_MASK GENMASK(1, 0)
24 struct at91sam9x5_clk_usb
{
26 struct regmap
*regmap
;
31 #define to_at91sam9x5_clk_usb(hw) \
32 container_of(hw, struct at91sam9x5_clk_usb, hw)
34 struct at91rm9200_clk_usb
{
36 struct regmap
*regmap
;
40 #define to_at91rm9200_clk_usb(hw) \
41 container_of(hw, struct at91rm9200_clk_usb, hw)
43 static unsigned long at91sam9x5_clk_usb_recalc_rate(struct clk_hw
*hw
,
44 unsigned long parent_rate
)
46 struct at91sam9x5_clk_usb
*usb
= to_at91sam9x5_clk_usb(hw
);
50 regmap_read(usb
->regmap
, AT91_PMC_USB
, &usbr
);
51 usbdiv
= (usbr
& AT91_PMC_OHCIUSBDIV
) >> SAM9X5_USB_DIV_SHIFT
;
53 return DIV_ROUND_CLOSEST(parent_rate
, (usbdiv
+ 1));
56 static int at91sam9x5_clk_usb_determine_rate(struct clk_hw
*hw
,
57 struct clk_rate_request
*req
)
59 struct clk_hw
*parent
;
60 long best_rate
= -EINVAL
;
61 unsigned long tmp_rate
;
66 for (i
= 0; i
< clk_hw_get_num_parents(hw
); i
++) {
69 parent
= clk_hw_get_parent_by_index(hw
, i
);
73 for (div
= 1; div
< SAM9X5_USB_MAX_DIV
+ 2; div
++) {
74 unsigned long tmp_parent_rate
;
76 tmp_parent_rate
= req
->rate
* div
;
77 tmp_parent_rate
= clk_hw_round_rate(parent
,
82 tmp_rate
= DIV_ROUND_CLOSEST(tmp_parent_rate
, div
);
83 if (tmp_rate
< req
->rate
)
84 tmp_diff
= req
->rate
- tmp_rate
;
86 tmp_diff
= tmp_rate
- req
->rate
;
88 if (best_diff
< 0 || best_diff
> tmp_diff
) {
91 req
->best_parent_rate
= tmp_parent_rate
;
92 req
->best_parent_hw
= parent
;
95 if (!best_diff
|| tmp_rate
< req
->rate
)
106 req
->rate
= best_rate
;
110 static int at91sam9x5_clk_usb_set_parent(struct clk_hw
*hw
, u8 index
)
112 struct at91sam9x5_clk_usb
*usb
= to_at91sam9x5_clk_usb(hw
);
114 if (index
>= usb
->num_parents
)
117 regmap_update_bits(usb
->regmap
, AT91_PMC_USB
, usb
->usbs_mask
, index
);
122 static u8
at91sam9x5_clk_usb_get_parent(struct clk_hw
*hw
)
124 struct at91sam9x5_clk_usb
*usb
= to_at91sam9x5_clk_usb(hw
);
127 regmap_read(usb
->regmap
, AT91_PMC_USB
, &usbr
);
129 return usbr
& usb
->usbs_mask
;
132 static int at91sam9x5_clk_usb_set_rate(struct clk_hw
*hw
, unsigned long rate
,
133 unsigned long parent_rate
)
135 struct at91sam9x5_clk_usb
*usb
= to_at91sam9x5_clk_usb(hw
);
141 div
= DIV_ROUND_CLOSEST(parent_rate
, rate
);
142 if (div
> SAM9X5_USB_MAX_DIV
+ 1 || !div
)
145 regmap_update_bits(usb
->regmap
, AT91_PMC_USB
, AT91_PMC_OHCIUSBDIV
,
146 (div
- 1) << SAM9X5_USB_DIV_SHIFT
);
151 static const struct clk_ops at91sam9x5_usb_ops
= {
152 .recalc_rate
= at91sam9x5_clk_usb_recalc_rate
,
153 .determine_rate
= at91sam9x5_clk_usb_determine_rate
,
154 .get_parent
= at91sam9x5_clk_usb_get_parent
,
155 .set_parent
= at91sam9x5_clk_usb_set_parent
,
156 .set_rate
= at91sam9x5_clk_usb_set_rate
,
159 static int at91sam9n12_clk_usb_enable(struct clk_hw
*hw
)
161 struct at91sam9x5_clk_usb
*usb
= to_at91sam9x5_clk_usb(hw
);
163 regmap_update_bits(usb
->regmap
, AT91_PMC_USB
, AT91_PMC_USBS
,
169 static void at91sam9n12_clk_usb_disable(struct clk_hw
*hw
)
171 struct at91sam9x5_clk_usb
*usb
= to_at91sam9x5_clk_usb(hw
);
173 regmap_update_bits(usb
->regmap
, AT91_PMC_USB
, AT91_PMC_USBS
, 0);
176 static int at91sam9n12_clk_usb_is_enabled(struct clk_hw
*hw
)
178 struct at91sam9x5_clk_usb
*usb
= to_at91sam9x5_clk_usb(hw
);
181 regmap_read(usb
->regmap
, AT91_PMC_USB
, &usbr
);
183 return usbr
& AT91_PMC_USBS
;
186 static const struct clk_ops at91sam9n12_usb_ops
= {
187 .enable
= at91sam9n12_clk_usb_enable
,
188 .disable
= at91sam9n12_clk_usb_disable
,
189 .is_enabled
= at91sam9n12_clk_usb_is_enabled
,
190 .recalc_rate
= at91sam9x5_clk_usb_recalc_rate
,
191 .determine_rate
= at91sam9x5_clk_usb_determine_rate
,
192 .set_rate
= at91sam9x5_clk_usb_set_rate
,
195 static struct clk_hw
* __init
196 _at91sam9x5_clk_register_usb(struct regmap
*regmap
, const char *name
,
197 const char **parent_names
, u8 num_parents
,
200 struct at91sam9x5_clk_usb
*usb
;
202 struct clk_init_data init
;
205 usb
= kzalloc(sizeof(*usb
), GFP_KERNEL
);
207 return ERR_PTR(-ENOMEM
);
210 init
.ops
= &at91sam9x5_usb_ops
;
211 init
.parent_names
= parent_names
;
212 init
.num_parents
= num_parents
;
213 init
.flags
= CLK_SET_RATE_GATE
| CLK_SET_PARENT_GATE
|
216 usb
->hw
.init
= &init
;
217 usb
->regmap
= regmap
;
218 usb
->usbs_mask
= usbs_mask
;
219 usb
->num_parents
= num_parents
;
222 ret
= clk_hw_register(NULL
, &usb
->hw
);
231 struct clk_hw
* __init
232 at91sam9x5_clk_register_usb(struct regmap
*regmap
, const char *name
,
233 const char **parent_names
, u8 num_parents
)
235 return _at91sam9x5_clk_register_usb(regmap
, name
, parent_names
,
236 num_parents
, SAM9X5_USBS_MASK
);
239 struct clk_hw
* __init
240 sam9x60_clk_register_usb(struct regmap
*regmap
, const char *name
,
241 const char **parent_names
, u8 num_parents
)
243 return _at91sam9x5_clk_register_usb(regmap
, name
, parent_names
,
244 num_parents
, SAM9X60_USBS_MASK
);
247 struct clk_hw
* __init
248 at91sam9n12_clk_register_usb(struct regmap
*regmap
, const char *name
,
249 const char *parent_name
)
251 struct at91sam9x5_clk_usb
*usb
;
253 struct clk_init_data init
;
256 usb
= kzalloc(sizeof(*usb
), GFP_KERNEL
);
258 return ERR_PTR(-ENOMEM
);
261 init
.ops
= &at91sam9n12_usb_ops
;
262 init
.parent_names
= &parent_name
;
263 init
.num_parents
= 1;
264 init
.flags
= CLK_SET_RATE_GATE
| CLK_SET_RATE_PARENT
;
266 usb
->hw
.init
= &init
;
267 usb
->regmap
= regmap
;
270 ret
= clk_hw_register(NULL
, &usb
->hw
);
279 static unsigned long at91rm9200_clk_usb_recalc_rate(struct clk_hw
*hw
,
280 unsigned long parent_rate
)
282 struct at91rm9200_clk_usb
*usb
= to_at91rm9200_clk_usb(hw
);
286 regmap_read(usb
->regmap
, AT91_CKGR_PLLBR
, &pllbr
);
288 usbdiv
= (pllbr
& AT91_PMC_USBDIV
) >> RM9200_USB_DIV_SHIFT
;
289 if (usb
->divisors
[usbdiv
])
290 return parent_rate
/ usb
->divisors
[usbdiv
];
295 static long at91rm9200_clk_usb_round_rate(struct clk_hw
*hw
, unsigned long rate
,
296 unsigned long *parent_rate
)
298 struct at91rm9200_clk_usb
*usb
= to_at91rm9200_clk_usb(hw
);
299 struct clk_hw
*parent
= clk_hw_get_parent(hw
);
300 unsigned long bestrate
= 0;
302 unsigned long tmprate
;
306 for (i
= 0; i
< RM9200_USB_DIV_TAB_SIZE
; i
++) {
307 unsigned long tmp_parent_rate
;
309 if (!usb
->divisors
[i
])
312 tmp_parent_rate
= rate
* usb
->divisors
[i
];
313 tmp_parent_rate
= clk_hw_round_rate(parent
, tmp_parent_rate
);
314 tmprate
= DIV_ROUND_CLOSEST(tmp_parent_rate
, usb
->divisors
[i
]);
316 tmpdiff
= rate
- tmprate
;
318 tmpdiff
= tmprate
- rate
;
320 if (bestdiff
< 0 || bestdiff
> tmpdiff
) {
323 *parent_rate
= tmp_parent_rate
;
333 static int at91rm9200_clk_usb_set_rate(struct clk_hw
*hw
, unsigned long rate
,
334 unsigned long parent_rate
)
337 struct at91rm9200_clk_usb
*usb
= to_at91rm9200_clk_usb(hw
);
343 div
= DIV_ROUND_CLOSEST(parent_rate
, rate
);
345 for (i
= 0; i
< RM9200_USB_DIV_TAB_SIZE
; i
++) {
346 if (usb
->divisors
[i
] == div
) {
347 regmap_update_bits(usb
->regmap
, AT91_CKGR_PLLBR
,
349 i
<< RM9200_USB_DIV_SHIFT
);
358 static const struct clk_ops at91rm9200_usb_ops
= {
359 .recalc_rate
= at91rm9200_clk_usb_recalc_rate
,
360 .round_rate
= at91rm9200_clk_usb_round_rate
,
361 .set_rate
= at91rm9200_clk_usb_set_rate
,
364 struct clk_hw
* __init
365 at91rm9200_clk_register_usb(struct regmap
*regmap
, const char *name
,
366 const char *parent_name
, const u32
*divisors
)
368 struct at91rm9200_clk_usb
*usb
;
370 struct clk_init_data init
;
373 usb
= kzalloc(sizeof(*usb
), GFP_KERNEL
);
375 return ERR_PTR(-ENOMEM
);
378 init
.ops
= &at91rm9200_usb_ops
;
379 init
.parent_names
= &parent_name
;
380 init
.num_parents
= 1;
381 init
.flags
= CLK_SET_RATE_PARENT
;
383 usb
->hw
.init
= &init
;
384 usb
->regmap
= regmap
;
385 memcpy(usb
->divisors
, divisors
, sizeof(usb
->divisors
));
388 ret
= clk_hw_register(NULL
, &usb
->hw
);