1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * clk-h32mx.c |
4 | * |
5 | * Copyright (C) 2014 Atmel |
6 | * |
7 | * Alexandre Belloni <alexandre.belloni@free-electrons.com> |
8 | */ |
9 | |
10 | #include <linux/clk-provider.h> |
11 | #include <linux/clkdev.h> |
12 | #include <linux/clk/at91_pmc.h> |
13 | #include <linux/of.h> |
14 | #include <linux/regmap.h> |
15 | #include <linux/mfd/syscon.h> |
16 | |
17 | #include "pmc.h" |
18 | |
19 | #define H32MX_MAX_FREQ 90000000 |
20 | |
21 | struct clk_sama5d4_h32mx { |
22 | struct clk_hw hw; |
23 | struct regmap *regmap; |
24 | }; |
25 | |
26 | #define to_clk_sama5d4_h32mx(hw) container_of(hw, struct clk_sama5d4_h32mx, hw) |
27 | |
28 | static unsigned long clk_sama5d4_h32mx_recalc_rate(struct clk_hw *hw, |
29 | unsigned long parent_rate) |
30 | { |
31 | struct clk_sama5d4_h32mx *h32mxclk = to_clk_sama5d4_h32mx(hw); |
32 | unsigned int mckr; |
33 | |
34 | regmap_read(map: h32mxclk->regmap, AT91_PMC_MCKR, val: &mckr); |
35 | if (mckr & AT91_PMC_H32MXDIV) |
36 | return parent_rate / 2; |
37 | |
38 | if (parent_rate > H32MX_MAX_FREQ) |
39 | pr_warn("H32MX clock is too fast\n" ); |
40 | return parent_rate; |
41 | } |
42 | |
43 | static long clk_sama5d4_h32mx_round_rate(struct clk_hw *hw, unsigned long rate, |
44 | unsigned long *parent_rate) |
45 | { |
46 | unsigned long div; |
47 | |
48 | if (rate > *parent_rate) |
49 | return *parent_rate; |
50 | div = *parent_rate / 2; |
51 | if (rate < div) |
52 | return div; |
53 | |
54 | if (rate - div < *parent_rate - rate) |
55 | return div; |
56 | |
57 | return *parent_rate; |
58 | } |
59 | |
60 | static int clk_sama5d4_h32mx_set_rate(struct clk_hw *hw, unsigned long rate, |
61 | unsigned long parent_rate) |
62 | { |
63 | struct clk_sama5d4_h32mx *h32mxclk = to_clk_sama5d4_h32mx(hw); |
64 | u32 mckr = 0; |
65 | |
66 | if (parent_rate != rate && (parent_rate / 2) != rate) |
67 | return -EINVAL; |
68 | |
69 | if ((parent_rate / 2) == rate) |
70 | mckr = AT91_PMC_H32MXDIV; |
71 | |
72 | regmap_update_bits(map: h32mxclk->regmap, AT91_PMC_MCKR, |
73 | AT91_PMC_H32MXDIV, val: mckr); |
74 | |
75 | return 0; |
76 | } |
77 | |
78 | static const struct clk_ops h32mx_ops = { |
79 | .recalc_rate = clk_sama5d4_h32mx_recalc_rate, |
80 | .round_rate = clk_sama5d4_h32mx_round_rate, |
81 | .set_rate = clk_sama5d4_h32mx_set_rate, |
82 | }; |
83 | |
84 | struct clk_hw * __init |
85 | at91_clk_register_h32mx(struct regmap *regmap, const char *name, |
86 | const char *parent_name) |
87 | { |
88 | struct clk_sama5d4_h32mx *h32mxclk; |
89 | struct clk_init_data init; |
90 | int ret; |
91 | |
92 | h32mxclk = kzalloc(size: sizeof(*h32mxclk), GFP_KERNEL); |
93 | if (!h32mxclk) |
94 | return ERR_PTR(error: -ENOMEM); |
95 | |
96 | init.name = name; |
97 | init.ops = &h32mx_ops; |
98 | init.parent_names = parent_name ? &parent_name : NULL; |
99 | init.num_parents = parent_name ? 1 : 0; |
100 | init.flags = CLK_SET_RATE_GATE; |
101 | |
102 | h32mxclk->hw.init = &init; |
103 | h32mxclk->regmap = regmap; |
104 | |
105 | ret = clk_hw_register(NULL, hw: &h32mxclk->hw); |
106 | if (ret) { |
107 | kfree(objp: h32mxclk); |
108 | return ERR_PTR(error: ret); |
109 | } |
110 | |
111 | return &h32mxclk->hw; |
112 | } |
113 | |