blob: 08d7e7dddc9e5f769bc1200fd41214bc6daec130 [file] [log] [blame]
Claudiu Beznead0c738b2020-09-07 17:46:49 +03001// SPDX-License-Identifier: GPL-2.0+
2/*
3 * Peripheral clock support for AT91 architectures.
4 *
5 * Copyright (C) 2020 Microchip Technology Inc. and its subsidiaries
6 *
7 * Author: Claudiu Beznea <claudiu.beznea@microchip.com>
8 *
9 * Based on drivers/clk/at91/clk-peripheral.c from Linux.
10 */
Claudiu Beznead0c738b2020-09-07 17:46:49 +030011#include <clk-uclass.h>
12#include <dm.h>
13#include <linux/io.h>
14#include <linux/clk-provider.h>
15#include <linux/clk/at91_pmc.h>
16
17#include "pmc.h"
18
19#define UBOOT_DM_CLK_AT91_PERIPH "at91-periph-clk"
20#define UBOOT_DM_CLK_AT91_SAM9X5_PERIPH "at91-sam9x5-periph-clk"
21
22#define PERIPHERAL_ID_MIN 2
23#define PERIPHERAL_ID_MAX 31
24#define PERIPHERAL_MASK(id) (1 << ((id) & PERIPHERAL_ID_MAX))
25
26#define PERIPHERAL_MAX_SHIFT 3
27
28struct clk_peripheral {
29 void __iomem *base;
30 struct clk clk;
31 u32 id;
32};
33
34#define to_clk_peripheral(_c) container_of(_c, struct clk_peripheral, clk)
35
36struct clk_sam9x5_peripheral {
37 const struct clk_pcr_layout *layout;
38 void __iomem *base;
39 struct clk clk;
40 struct clk_range range;
41 u32 id;
42 u32 div;
43 bool auto_div;
44};
45
46#define to_clk_sam9x5_peripheral(_c) \
47 container_of(_c, struct clk_sam9x5_peripheral, clk)
48
49static int clk_peripheral_enable(struct clk *clk)
50{
51 struct clk_peripheral *periph = to_clk_peripheral(clk);
52 int offset = AT91_PMC_PCER;
53 u32 id = periph->id;
54
55 if (id < PERIPHERAL_ID_MIN)
56 return 0;
57 if (id > PERIPHERAL_ID_MAX)
58 offset = AT91_PMC_PCER1;
59 pmc_write(periph->base, offset, PERIPHERAL_MASK(id));
60
61 return 0;
62}
63
64static int clk_peripheral_disable(struct clk *clk)
65{
66 struct clk_peripheral *periph = to_clk_peripheral(clk);
67 int offset = AT91_PMC_PCDR;
68 u32 id = periph->id;
69
70 if (id < PERIPHERAL_ID_MIN)
71 return -EINVAL;
72
73 if (id > PERIPHERAL_ID_MAX)
74 offset = AT91_PMC_PCDR1;
75 pmc_write(periph->base, offset, PERIPHERAL_MASK(id));
76
77 return 0;
78}
79
80static const struct clk_ops peripheral_ops = {
81 .enable = clk_peripheral_enable,
82 .disable = clk_peripheral_disable,
83 .get_rate = clk_generic_get_rate,
84};
85
86struct clk *
87at91_clk_register_peripheral(void __iomem *base, const char *name,
88 const char *parent_name, u32 id)
89{
90 struct clk_peripheral *periph;
91 struct clk *clk;
92 int ret;
93
94 if (!base || !name || !parent_name || id > PERIPHERAL_ID_MAX)
95 return ERR_PTR(-EINVAL);
96
97 periph = kzalloc(sizeof(*periph), GFP_KERNEL);
98 if (!periph)
99 return ERR_PTR(-ENOMEM);
100
101 periph->id = id;
102 periph->base = base;
103
104 clk = &periph->clk;
105 clk->flags = CLK_GET_RATE_NOCACHE;
106 ret = clk_register(clk, UBOOT_DM_CLK_AT91_PERIPH, name, parent_name);
107 if (ret) {
108 kfree(periph);
109 clk = ERR_PTR(ret);
110 }
111
112 return clk;
113}
114
115U_BOOT_DRIVER(at91_periph_clk) = {
116 .name = UBOOT_DM_CLK_AT91_PERIPH,
117 .id = UCLASS_CLK,
118 .ops = &peripheral_ops,
119 .flags = DM_FLAG_PRE_RELOC,
120};
121
122static int clk_sam9x5_peripheral_enable(struct clk *clk)
123{
124 struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(clk);
125
126 if (periph->id < PERIPHERAL_ID_MIN)
127 return 0;
128
129 pmc_write(periph->base, periph->layout->offset,
130 (periph->id & periph->layout->pid_mask));
131 pmc_update_bits(periph->base, periph->layout->offset,
132 periph->layout->cmd | AT91_PMC_PCR_EN,
133 periph->layout->cmd | AT91_PMC_PCR_EN);
134
135 return 0;
136}
137
138static int clk_sam9x5_peripheral_disable(struct clk *clk)
139{
140 struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(clk);
141
142 if (periph->id < PERIPHERAL_ID_MIN)
143 return -EINVAL;
144
145 pmc_write(periph->base, periph->layout->offset,
146 (periph->id & periph->layout->pid_mask));
147 pmc_update_bits(periph->base, periph->layout->offset,
148 AT91_PMC_PCR_EN | periph->layout->cmd,
149 periph->layout->cmd);
150
151 return 0;
152}
153
154static ulong clk_sam9x5_peripheral_get_rate(struct clk *clk)
155{
156 struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(clk);
157 ulong parent_rate = clk_get_parent_rate(clk);
158 u32 val, shift = ffs(periph->layout->div_mask) - 1;
159
160 if (!parent_rate)
161 return 0;
162
163 pmc_write(periph->base, periph->layout->offset,
164 (periph->id & periph->layout->pid_mask));
165 pmc_read(periph->base, periph->layout->offset, &val);
166 shift = (val & periph->layout->div_mask) >> shift;
167
168 return parent_rate >> shift;
169}
170
171static ulong clk_sam9x5_peripheral_set_rate(struct clk *clk, ulong rate)
172{
173 struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(clk);
174 ulong parent_rate = clk_get_parent_rate(clk);
175 int shift;
176
177 if (!parent_rate)
178 return 0;
179
180 if (periph->id < PERIPHERAL_ID_MIN || !periph->range.max) {
181 if (parent_rate == rate)
182 return rate;
183 else
184 return 0;
185 }
186
187 if (periph->range.max && rate > periph->range.max)
188 return 0;
189
190 for (shift = 0; shift <= PERIPHERAL_MAX_SHIFT; shift++) {
191 if (parent_rate >> shift <= rate)
192 break;
193 }
194 if (shift == PERIPHERAL_MAX_SHIFT + 1)
195 return 0;
196
197 pmc_write(periph->base, periph->layout->offset,
198 (periph->id & periph->layout->pid_mask));
199 pmc_update_bits(periph->base, periph->layout->offset,
200 periph->layout->div_mask | periph->layout->cmd,
201 (shift << (ffs(periph->layout->div_mask) - 1)) |
202 periph->layout->cmd);
203
204 return parent_rate >> shift;
205}
206
207static const struct clk_ops sam9x5_peripheral_ops = {
208 .enable = clk_sam9x5_peripheral_enable,
209 .disable = clk_sam9x5_peripheral_disable,
210 .get_rate = clk_sam9x5_peripheral_get_rate,
211 .set_rate = clk_sam9x5_peripheral_set_rate,
212};
213
214struct clk *
215at91_clk_register_sam9x5_peripheral(void __iomem *base,
216 const struct clk_pcr_layout *layout,
217 const char *name, const char *parent_name,
218 u32 id, const struct clk_range *range)
219{
220 struct clk_sam9x5_peripheral *periph;
221 struct clk *clk;
222 int ret;
223
224 if (!base || !layout || !name || !parent_name || !range)
225 return ERR_PTR(-EINVAL);
226
227 periph = kzalloc(sizeof(*periph), GFP_KERNEL);
228 if (!periph)
229 return ERR_PTR(-ENOMEM);
230
231 periph->id = id;
232 periph->base = base;
233 periph->layout = layout;
234 periph->range = *range;
235
236 clk = &periph->clk;
237 clk->flags = CLK_GET_RATE_NOCACHE;
238 ret = clk_register(clk, UBOOT_DM_CLK_AT91_SAM9X5_PERIPH, name,
239 parent_name);
240 if (ret) {
241 kfree(periph);
242 clk = ERR_PTR(ret);
243 }
244
245 return clk;
246}
247
248U_BOOT_DRIVER(at91_sam9x5_periph_clk) = {
249 .name = UBOOT_DM_CLK_AT91_SAM9X5_PERIPH,
250 .id = UCLASS_CLK,
251 .ops = &sam9x5_peripheral_ops,
252 .flags = DM_FLAG_PRE_RELOC,
253};