Merge branch 'pm-cpufreq'
[firefly-linux-kernel-4.4.55.git] / drivers / clk / imx / clk-pfd.c
1 /*
2  * Copyright 2012 Freescale Semiconductor, Inc.
3  * Copyright 2012 Linaro Ltd.
4  *
5  * The code contained herein is licensed under the GNU General Public
6  * License. You may obtain a copy of the GNU General Public License
7  * Version 2 or later at the following locations:
8  *
9  * http://www.opensource.org/licenses/gpl-license.html
10  * http://www.gnu.org/copyleft/gpl.html
11  */
12
13 #include <linux/clk-provider.h>
14 #include <linux/io.h>
15 #include <linux/slab.h>
16 #include <linux/err.h>
17 #include "clk.h"
18
19 /**
20  * struct clk_pfd - IMX PFD clock
21  * @clk_hw:     clock source
22  * @reg:        PFD register address
23  * @idx:        the index of PFD encoded in the register
24  *
25  * PFD clock found on i.MX6 series.  Each register for PFD has 4 clk_pfd
26  * data encoded, and member idx is used to specify the one.  And each
27  * register has SET, CLR and TOG registers at offset 0x4 0x8 and 0xc.
28  */
29 struct clk_pfd {
30         struct clk_hw   hw;
31         void __iomem    *reg;
32         u8              idx;
33 };
34
35 #define to_clk_pfd(_hw) container_of(_hw, struct clk_pfd, hw)
36
37 #define SET     0x4
38 #define CLR     0x8
39 #define OTG     0xc
40
41 static int clk_pfd_enable(struct clk_hw *hw)
42 {
43         struct clk_pfd *pfd = to_clk_pfd(hw);
44
45         writel_relaxed(1 << ((pfd->idx + 1) * 8 - 1), pfd->reg + CLR);
46
47         return 0;
48 }
49
50 static void clk_pfd_disable(struct clk_hw *hw)
51 {
52         struct clk_pfd *pfd = to_clk_pfd(hw);
53
54         writel_relaxed(1 << ((pfd->idx + 1) * 8 - 1), pfd->reg + SET);
55 }
56
57 static unsigned long clk_pfd_recalc_rate(struct clk_hw *hw,
58                                          unsigned long parent_rate)
59 {
60         struct clk_pfd *pfd = to_clk_pfd(hw);
61         u64 tmp = parent_rate;
62         u8 frac = (readl_relaxed(pfd->reg) >> (pfd->idx * 8)) & 0x3f;
63
64         tmp *= 18;
65         do_div(tmp, frac);
66
67         return tmp;
68 }
69
70 static long clk_pfd_round_rate(struct clk_hw *hw, unsigned long rate,
71                                unsigned long *prate)
72 {
73         u64 tmp = *prate;
74         u8 frac;
75
76         tmp = tmp * 18 + rate / 2;
77         do_div(tmp, rate);
78         frac = tmp;
79         if (frac < 12)
80                 frac = 12;
81         else if (frac > 35)
82                 frac = 35;
83         tmp = *prate;
84         tmp *= 18;
85         do_div(tmp, frac);
86
87         return tmp;
88 }
89
90 static int clk_pfd_set_rate(struct clk_hw *hw, unsigned long rate,
91                 unsigned long parent_rate)
92 {
93         struct clk_pfd *pfd = to_clk_pfd(hw);
94         u64 tmp = parent_rate;
95         u8 frac;
96
97         tmp = tmp * 18 + rate / 2;
98         do_div(tmp, rate);
99         frac = tmp;
100         if (frac < 12)
101                 frac = 12;
102         else if (frac > 35)
103                 frac = 35;
104
105         writel_relaxed(0x3f << (pfd->idx * 8), pfd->reg + CLR);
106         writel_relaxed(frac << (pfd->idx * 8), pfd->reg + SET);
107
108         return 0;
109 }
110
111 static int clk_pfd_is_enabled(struct clk_hw *hw)
112 {
113         struct clk_pfd *pfd = to_clk_pfd(hw);
114
115         if (readl_relaxed(pfd->reg) & (1 << ((pfd->idx + 1) * 8 - 1)))
116                 return 0;
117
118         return 1;
119 }
120
121 static const struct clk_ops clk_pfd_ops = {
122         .enable         = clk_pfd_enable,
123         .disable        = clk_pfd_disable,
124         .recalc_rate    = clk_pfd_recalc_rate,
125         .round_rate     = clk_pfd_round_rate,
126         .set_rate       = clk_pfd_set_rate,
127         .is_enabled     = clk_pfd_is_enabled,
128 };
129
130 struct clk *imx_clk_pfd(const char *name, const char *parent_name,
131                         void __iomem *reg, u8 idx)
132 {
133         struct clk_pfd *pfd;
134         struct clk *clk;
135         struct clk_init_data init;
136
137         pfd = kzalloc(sizeof(*pfd), GFP_KERNEL);
138         if (!pfd)
139                 return ERR_PTR(-ENOMEM);
140
141         pfd->reg = reg;
142         pfd->idx = idx;
143
144         init.name = name;
145         init.ops = &clk_pfd_ops;
146         init.flags = 0;
147         init.parent_names = &parent_name;
148         init.num_parents = 1;
149
150         pfd->hw.init = &init;
151
152         clk = clk_register(NULL, &pfd->hw);
153         if (IS_ERR(clk))
154                 kfree(pfd);
155
156         return clk;
157 }