1
2
3
4
5
6
7
8
9
10
11#include <linux/clk-provider.h>
12#include <linux/clkdev.h>
13#include <linux/clk/at91_pmc.h>
14#include <linux/of.h>
15#include <linux/mfd/syscon.h>
16#include <linux/regmap.h>
17
18#include "pmc.h"
19
20#define MASTER_PRES_MASK 0x7
21#define MASTER_PRES_MAX MASTER_PRES_MASK
22#define MASTER_DIV_SHIFT 8
23#define MASTER_DIV_MASK 0x3
24
25#define to_clk_master(hw) container_of(hw, struct clk_master, hw)
26
27struct clk_master {
28 struct clk_hw hw;
29 struct regmap *regmap;
30 const struct clk_master_layout *layout;
31 const struct clk_master_characteristics *characteristics;
32};
33
34static inline bool clk_master_ready(struct regmap *regmap)
35{
36 unsigned int status;
37
38 regmap_read(regmap, AT91_PMC_SR, &status);
39
40 return status & AT91_PMC_MCKRDY ? 1 : 0;
41}
42
43static int clk_master_prepare(struct clk_hw *hw)
44{
45 struct clk_master *master = to_clk_master(hw);
46
47 while (!clk_master_ready(master->regmap))
48 cpu_relax();
49
50 return 0;
51}
52
53static int clk_master_is_prepared(struct clk_hw *hw)
54{
55 struct clk_master *master = to_clk_master(hw);
56
57 return clk_master_ready(master->regmap);
58}
59
60static unsigned long clk_master_recalc_rate(struct clk_hw *hw,
61 unsigned long parent_rate)
62{
63 u8 pres;
64 u8 div;
65 unsigned long rate = parent_rate;
66 struct clk_master *master = to_clk_master(hw);
67 const struct clk_master_layout *layout = master->layout;
68 const struct clk_master_characteristics *characteristics =
69 master->characteristics;
70 unsigned int mckr;
71
72 regmap_read(master->regmap, AT91_PMC_MCKR, &mckr);
73 mckr &= layout->mask;
74
75 pres = (mckr >> layout->pres_shift) & MASTER_PRES_MASK;
76 div = (mckr >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK;
77
78 if (characteristics->have_div3_pres && pres == MASTER_PRES_MAX)
79 rate /= 3;
80 else
81 rate >>= pres;
82
83 rate /= characteristics->divisors[div];
84
85 if (rate < characteristics->output.min)
86 pr_warn("master clk is underclocked");
87 else if (rate > characteristics->output.max)
88 pr_warn("master clk is overclocked");
89
90 return rate;
91}
92
93static u8 clk_master_get_parent(struct clk_hw *hw)
94{
95 struct clk_master *master = to_clk_master(hw);
96 unsigned int mckr;
97
98 regmap_read(master->regmap, AT91_PMC_MCKR, &mckr);
99
100 return mckr & AT91_PMC_CSS;
101}
102
103static const struct clk_ops master_ops = {
104 .prepare = clk_master_prepare,
105 .is_prepared = clk_master_is_prepared,
106 .recalc_rate = clk_master_recalc_rate,
107 .get_parent = clk_master_get_parent,
108};
109
110struct clk_hw * __init
111at91_clk_register_master(struct regmap *regmap,
112 const char *name, int num_parents,
113 const char **parent_names,
114 const struct clk_master_layout *layout,
115 const struct clk_master_characteristics *characteristics)
116{
117 struct clk_master *master;
118 struct clk_init_data init;
119 struct clk_hw *hw;
120 int ret;
121
122 if (!name || !num_parents || !parent_names)
123 return ERR_PTR(-EINVAL);
124
125 master = kzalloc(sizeof(*master), GFP_KERNEL);
126 if (!master)
127 return ERR_PTR(-ENOMEM);
128
129 init.name = name;
130 init.ops = &master_ops;
131 init.parent_names = parent_names;
132 init.num_parents = num_parents;
133 init.flags = 0;
134
135 master->hw.init = &init;
136 master->layout = layout;
137 master->characteristics = characteristics;
138 master->regmap = regmap;
139
140 hw = &master->hw;
141 ret = clk_hw_register(NULL, &master->hw);
142 if (ret) {
143 kfree(master);
144 hw = ERR_PTR(ret);
145 }
146
147 return hw;
148}
149
150
151const struct clk_master_layout at91rm9200_master_layout = {
152 .mask = 0x31F,
153 .pres_shift = 2,
154};
155
156const struct clk_master_layout at91sam9x5_master_layout = {
157 .mask = 0x373,
158 .pres_shift = 4,
159};
160