Claudiu Beznea | 1f9023a | 2020-09-07 17:46:43 +0300 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0+ |
| 2 | /* |
| 3 | * Master 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-master.c from Linux. |
| 10 | */ |
| 11 | |
| 12 | #include <asm/processor.h> |
| 13 | #include <clk-uclass.h> |
| 14 | #include <common.h> |
Claudiu Beznea | 8b5bbad | 2021-07-16 08:43:48 +0300 | [diff] [blame^] | 15 | #include <div64.h> |
Claudiu Beznea | 1f9023a | 2020-09-07 17:46:43 +0300 | [diff] [blame] | 16 | #include <dm.h> |
| 17 | #include <linux/clk-provider.h> |
| 18 | #include <linux/clk/at91_pmc.h> |
| 19 | |
| 20 | #include "pmc.h" |
| 21 | |
Claudiu Beznea | 8b5bbad | 2021-07-16 08:43:48 +0300 | [diff] [blame^] | 22 | #define UBOOT_DM_CLK_AT91_MASTER_PRES "at91-master-clk-pres" |
| 23 | #define UBOOT_DM_CLK_AT91_MASTER_DIV "at91-master-clk-div" |
Claudiu Beznea | e812b02 | 2020-09-07 17:46:44 +0300 | [diff] [blame] | 24 | #define UBOOT_DM_CLK_AT91_SAMA7G5_MASTER "at91-sama7g5-master-clk" |
Claudiu Beznea | 1f9023a | 2020-09-07 17:46:43 +0300 | [diff] [blame] | 25 | |
| 26 | #define MASTER_PRES_MASK 0x7 |
| 27 | #define MASTER_PRES_MAX MASTER_PRES_MASK |
| 28 | #define MASTER_DIV_SHIFT 8 |
Eugen Hristev | 227fa12 | 2020-07-01 10:42:58 +0300 | [diff] [blame] | 29 | #define MASTER_DIV_MASK 0x7 |
Claudiu Beznea | 1f9023a | 2020-09-07 17:46:43 +0300 | [diff] [blame] | 30 | |
Claudiu Beznea | e812b02 | 2020-09-07 17:46:44 +0300 | [diff] [blame] | 31 | #define PMC_MCR 0x30 |
| 32 | #define PMC_MCR_ID_MSK GENMASK(3, 0) |
| 33 | #define PMC_MCR_CMD BIT(7) |
| 34 | #define PMC_MCR_DIV GENMASK(10, 8) |
| 35 | #define PMC_MCR_CSS GENMASK(20, 16) |
| 36 | #define PMC_MCR_CSS_SHIFT (16) |
| 37 | #define PMC_MCR_EN BIT(28) |
| 38 | |
| 39 | #define PMC_MCR_ID(x) ((x) & PMC_MCR_ID_MSK) |
| 40 | |
| 41 | #define MASTER_MAX_ID 4 |
| 42 | |
Claudiu Beznea | 1f9023a | 2020-09-07 17:46:43 +0300 | [diff] [blame] | 43 | struct clk_master { |
| 44 | void __iomem *base; |
| 45 | const struct clk_master_layout *layout; |
| 46 | const struct clk_master_characteristics *characteristics; |
| 47 | const u32 *mux_table; |
| 48 | const u32 *clk_mux_table; |
| 49 | u32 num_parents; |
| 50 | struct clk clk; |
| 51 | u8 id; |
| 52 | }; |
| 53 | |
| 54 | #define to_clk_master(_clk) container_of(_clk, struct clk_master, clk) |
| 55 | |
| 56 | static inline bool clk_master_ready(struct clk_master *master) |
| 57 | { |
Claudiu Beznea | e812b02 | 2020-09-07 17:46:44 +0300 | [diff] [blame] | 58 | unsigned int bit = master->id ? AT91_PMC_MCKXRDY : AT91_PMC_MCKRDY; |
Claudiu Beznea | 1f9023a | 2020-09-07 17:46:43 +0300 | [diff] [blame] | 59 | unsigned int status; |
| 60 | |
| 61 | pmc_read(master->base, AT91_PMC_SR, &status); |
| 62 | |
Claudiu Beznea | e812b02 | 2020-09-07 17:46:44 +0300 | [diff] [blame] | 63 | return !!(status & bit); |
Claudiu Beznea | 1f9023a | 2020-09-07 17:46:43 +0300 | [diff] [blame] | 64 | } |
| 65 | |
| 66 | static int clk_master_enable(struct clk *clk) |
| 67 | { |
| 68 | struct clk_master *master = to_clk_master(clk); |
| 69 | |
| 70 | while (!clk_master_ready(master)) { |
| 71 | debug("waiting for mck %d\n", master->id); |
| 72 | cpu_relax(); |
| 73 | } |
| 74 | |
| 75 | return 0; |
| 76 | } |
| 77 | |
Claudiu Beznea | 8b5bbad | 2021-07-16 08:43:48 +0300 | [diff] [blame^] | 78 | static ulong clk_master_pres_get_rate(struct clk *clk) |
Claudiu Beznea | 1f9023a | 2020-09-07 17:46:43 +0300 | [diff] [blame] | 79 | { |
| 80 | struct clk_master *master = to_clk_master(clk); |
| 81 | const struct clk_master_layout *layout = master->layout; |
| 82 | const struct clk_master_characteristics *characteristics = |
| 83 | master->characteristics; |
| 84 | ulong rate = clk_get_parent_rate(clk); |
| 85 | unsigned int mckr; |
Claudiu Beznea | 8b5bbad | 2021-07-16 08:43:48 +0300 | [diff] [blame^] | 86 | u8 pres; |
Claudiu Beznea | 1f9023a | 2020-09-07 17:46:43 +0300 | [diff] [blame] | 87 | |
| 88 | if (!rate) |
| 89 | return 0; |
| 90 | |
| 91 | pmc_read(master->base, master->layout->offset, &mckr); |
| 92 | mckr &= layout->mask; |
| 93 | |
| 94 | pres = (mckr >> layout->pres_shift) & MASTER_PRES_MASK; |
Claudiu Beznea | 1f9023a | 2020-09-07 17:46:43 +0300 | [diff] [blame] | 95 | |
| 96 | if (characteristics->have_div3_pres && pres == MASTER_PRES_MAX) |
Claudiu Beznea | 8b5bbad | 2021-07-16 08:43:48 +0300 | [diff] [blame^] | 97 | pres = 3; |
Claudiu Beznea | 1f9023a | 2020-09-07 17:46:43 +0300 | [diff] [blame] | 98 | else |
Claudiu Beznea | 8b5bbad | 2021-07-16 08:43:48 +0300 | [diff] [blame^] | 99 | pres = (1 << pres); |
Claudiu Beznea | 1f9023a | 2020-09-07 17:46:43 +0300 | [diff] [blame] | 100 | |
Claudiu Beznea | 8b5bbad | 2021-07-16 08:43:48 +0300 | [diff] [blame^] | 101 | return DIV_ROUND_CLOSEST_ULL(rate, pres); |
Claudiu Beznea | 1f9023a | 2020-09-07 17:46:43 +0300 | [diff] [blame] | 102 | } |
| 103 | |
Claudiu Beznea | 8b5bbad | 2021-07-16 08:43:48 +0300 | [diff] [blame^] | 104 | static const struct clk_ops master_pres_ops = { |
Claudiu Beznea | 1f9023a | 2020-09-07 17:46:43 +0300 | [diff] [blame] | 105 | .enable = clk_master_enable, |
Claudiu Beznea | 8b5bbad | 2021-07-16 08:43:48 +0300 | [diff] [blame^] | 106 | .get_rate = clk_master_pres_get_rate, |
Claudiu Beznea | 1f9023a | 2020-09-07 17:46:43 +0300 | [diff] [blame] | 107 | }; |
| 108 | |
Claudiu Beznea | 8b5bbad | 2021-07-16 08:43:48 +0300 | [diff] [blame^] | 109 | struct clk *at91_clk_register_master_pres(void __iomem *base, |
Claudiu Beznea | 1f9023a | 2020-09-07 17:46:43 +0300 | [diff] [blame] | 110 | const char *name, const char * const *parent_names, |
| 111 | int num_parents, const struct clk_master_layout *layout, |
| 112 | const struct clk_master_characteristics *characteristics, |
| 113 | const u32 *mux_table) |
| 114 | { |
| 115 | struct clk_master *master; |
| 116 | struct clk *clk; |
| 117 | unsigned int val; |
| 118 | int ret; |
| 119 | |
| 120 | if (!base || !name || !num_parents || !parent_names || |
| 121 | !layout || !characteristics || !mux_table) |
| 122 | return ERR_PTR(-EINVAL); |
| 123 | |
| 124 | master = kzalloc(sizeof(*master), GFP_KERNEL); |
| 125 | if (!master) |
| 126 | return ERR_PTR(-ENOMEM); |
| 127 | |
| 128 | master->layout = layout; |
| 129 | master->characteristics = characteristics; |
| 130 | master->base = base; |
| 131 | master->num_parents = num_parents; |
| 132 | master->mux_table = mux_table; |
| 133 | |
| 134 | pmc_read(master->base, master->layout->offset, &val); |
| 135 | clk = &master->clk; |
| 136 | clk->flags = CLK_GET_RATE_NOCACHE | CLK_IS_CRITICAL; |
Claudiu Beznea | 8b5bbad | 2021-07-16 08:43:48 +0300 | [diff] [blame^] | 137 | ret = clk_register(clk, UBOOT_DM_CLK_AT91_MASTER_PRES, name, |
Claudiu Beznea | 1f9023a | 2020-09-07 17:46:43 +0300 | [diff] [blame] | 138 | parent_names[val & AT91_PMC_CSS]); |
| 139 | if (ret) { |
| 140 | kfree(master); |
| 141 | clk = ERR_PTR(ret); |
| 142 | } |
| 143 | |
| 144 | return clk; |
| 145 | } |
| 146 | |
Claudiu Beznea | 8b5bbad | 2021-07-16 08:43:48 +0300 | [diff] [blame^] | 147 | U_BOOT_DRIVER(at91_master_pres_clk) = { |
| 148 | .name = UBOOT_DM_CLK_AT91_MASTER_PRES, |
| 149 | .id = UCLASS_CLK, |
| 150 | .ops = &master_pres_ops, |
| 151 | .flags = DM_FLAG_PRE_RELOC, |
| 152 | }; |
| 153 | |
| 154 | static ulong clk_master_div_get_rate(struct clk *clk) |
| 155 | { |
| 156 | struct clk_master *master = to_clk_master(clk); |
| 157 | const struct clk_master_layout *layout = master->layout; |
| 158 | const struct clk_master_characteristics *characteristics = |
| 159 | master->characteristics; |
| 160 | ulong rate = clk_get_parent_rate(clk); |
| 161 | unsigned int mckr; |
| 162 | u8 div; |
| 163 | |
| 164 | if (!rate) |
| 165 | return 0; |
| 166 | |
| 167 | pmc_read(master->base, master->layout->offset, &mckr); |
| 168 | mckr &= layout->mask; |
| 169 | div = (mckr >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK; |
| 170 | |
| 171 | rate = DIV_ROUND_CLOSEST_ULL(rate, characteristics->divisors[div]); |
| 172 | if (rate < characteristics->output.min) |
| 173 | pr_warn("master clk is underclocked"); |
| 174 | else if (rate > characteristics->output.max) |
| 175 | pr_warn("master clk is overclocked"); |
| 176 | |
| 177 | return rate; |
| 178 | } |
| 179 | |
| 180 | static const struct clk_ops master_div_ops = { |
| 181 | .enable = clk_master_enable, |
| 182 | .get_rate = clk_master_div_get_rate, |
| 183 | }; |
| 184 | |
| 185 | struct clk *at91_clk_register_master_div(void __iomem *base, |
| 186 | const char *name, const char *parent_name, |
| 187 | const struct clk_master_layout *layout, |
| 188 | const struct clk_master_characteristics *characteristics) |
| 189 | { |
| 190 | struct clk_master *master; |
| 191 | struct clk *clk; |
| 192 | int ret; |
| 193 | |
| 194 | if (!base || !name || !parent_name || !layout || !characteristics) |
| 195 | return ERR_PTR(-EINVAL); |
| 196 | |
| 197 | master = kzalloc(sizeof(*master), GFP_KERNEL); |
| 198 | if (!master) |
| 199 | return ERR_PTR(-ENOMEM); |
| 200 | |
| 201 | master->layout = layout; |
| 202 | master->characteristics = characteristics; |
| 203 | master->base = base; |
| 204 | master->num_parents = 1; |
| 205 | |
| 206 | clk = &master->clk; |
| 207 | clk->flags = CLK_GET_RATE_NOCACHE | CLK_IS_CRITICAL; |
| 208 | ret = clk_register(clk, UBOOT_DM_CLK_AT91_MASTER_DIV, name, |
| 209 | parent_name); |
| 210 | if (ret) { |
| 211 | kfree(master); |
| 212 | clk = ERR_PTR(ret); |
| 213 | } |
| 214 | |
| 215 | return clk; |
| 216 | } |
| 217 | |
| 218 | U_BOOT_DRIVER(at91_master_div_clk) = { |
| 219 | .name = UBOOT_DM_CLK_AT91_MASTER_DIV, |
Claudiu Beznea | 1f9023a | 2020-09-07 17:46:43 +0300 | [diff] [blame] | 220 | .id = UCLASS_CLK, |
Claudiu Beznea | 8b5bbad | 2021-07-16 08:43:48 +0300 | [diff] [blame^] | 221 | .ops = &master_div_ops, |
Claudiu Beznea | 1f9023a | 2020-09-07 17:46:43 +0300 | [diff] [blame] | 222 | .flags = DM_FLAG_PRE_RELOC, |
| 223 | }; |
| 224 | |
Claudiu Beznea | e812b02 | 2020-09-07 17:46:44 +0300 | [diff] [blame] | 225 | static int clk_sama7g5_master_set_parent(struct clk *clk, struct clk *parent) |
| 226 | { |
| 227 | struct clk_master *master = to_clk_master(clk); |
| 228 | int index; |
| 229 | |
| 230 | index = at91_clk_mux_val_to_index(master->clk_mux_table, |
| 231 | master->num_parents, parent->id); |
| 232 | if (index < 0) |
| 233 | return index; |
| 234 | |
| 235 | index = at91_clk_mux_index_to_val(master->mux_table, |
| 236 | master->num_parents, index); |
| 237 | if (index < 0) |
| 238 | return index; |
| 239 | |
| 240 | pmc_write(master->base, PMC_MCR, PMC_MCR_ID(master->id)); |
| 241 | pmc_update_bits(master->base, PMC_MCR, |
| 242 | PMC_MCR_CSS | PMC_MCR_CMD | PMC_MCR_ID_MSK, |
| 243 | (index << PMC_MCR_CSS_SHIFT) | PMC_MCR_CMD | |
| 244 | PMC_MCR_ID(master->id)); |
| 245 | return 0; |
| 246 | } |
| 247 | |
| 248 | static int clk_sama7g5_master_enable(struct clk *clk) |
| 249 | { |
| 250 | struct clk_master *master = to_clk_master(clk); |
| 251 | |
| 252 | pmc_write(master->base, PMC_MCR, PMC_MCR_ID(master->id)); |
| 253 | pmc_update_bits(master->base, PMC_MCR, |
| 254 | PMC_MCR_EN | PMC_MCR_CMD | PMC_MCR_ID_MSK, |
| 255 | PMC_MCR_EN | PMC_MCR_CMD | PMC_MCR_ID(master->id)); |
| 256 | |
| 257 | return 0; |
| 258 | } |
| 259 | |
| 260 | static int clk_sama7g5_master_disable(struct clk *clk) |
| 261 | { |
| 262 | struct clk_master *master = to_clk_master(clk); |
| 263 | |
| 264 | pmc_write(master->base, PMC_MCR, master->id); |
| 265 | pmc_update_bits(master->base, PMC_MCR, |
| 266 | PMC_MCR_EN | PMC_MCR_CMD | PMC_MCR_ID_MSK, |
| 267 | PMC_MCR_CMD | PMC_MCR_ID(master->id)); |
| 268 | |
| 269 | return 0; |
| 270 | } |
| 271 | |
| 272 | static ulong clk_sama7g5_master_set_rate(struct clk *clk, ulong rate) |
| 273 | { |
| 274 | struct clk_master *master = to_clk_master(clk); |
| 275 | ulong parent_rate = clk_get_parent_rate(clk); |
| 276 | ulong div, rrate; |
| 277 | |
| 278 | if (!parent_rate) |
| 279 | return 0; |
| 280 | |
| 281 | div = DIV_ROUND_CLOSEST(parent_rate, rate); |
| 282 | if ((div > (1 << (MASTER_PRES_MAX - 1))) || (div & (div - 1))) { |
| 283 | return 0; |
| 284 | } else if (div == 3) { |
| 285 | rrate = DIV_ROUND_CLOSEST(parent_rate, MASTER_PRES_MAX); |
| 286 | div = MASTER_PRES_MAX; |
| 287 | } else { |
| 288 | rrate = DIV_ROUND_CLOSEST(parent_rate, div); |
| 289 | div = ffs(div) - 1; |
| 290 | } |
| 291 | |
| 292 | pmc_write(master->base, PMC_MCR, master->id); |
| 293 | pmc_update_bits(master->base, PMC_MCR, |
| 294 | PMC_MCR_DIV | PMC_MCR_CMD | PMC_MCR_ID_MSK, |
| 295 | (div << MASTER_DIV_SHIFT) | PMC_MCR_CMD | |
| 296 | PMC_MCR_ID(master->id)); |
| 297 | |
| 298 | return rrate; |
| 299 | } |
| 300 | |
| 301 | static ulong clk_sama7g5_master_get_rate(struct clk *clk) |
| 302 | { |
| 303 | struct clk_master *master = to_clk_master(clk); |
| 304 | ulong parent_rate = clk_get_parent_rate(clk); |
| 305 | unsigned int val; |
| 306 | ulong div; |
| 307 | |
| 308 | if (!parent_rate) |
| 309 | return 0; |
| 310 | |
| 311 | pmc_write(master->base, PMC_MCR, master->id); |
| 312 | pmc_read(master->base, PMC_MCR, &val); |
| 313 | |
| 314 | div = (val >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK; |
| 315 | |
| 316 | if (div == MASTER_PRES_MAX) |
| 317 | div = 3; |
| 318 | else |
| 319 | div = 1 << div; |
| 320 | |
| 321 | return DIV_ROUND_CLOSEST(parent_rate, div); |
| 322 | } |
| 323 | |
| 324 | static const struct clk_ops sama7g5_master_ops = { |
| 325 | .enable = clk_sama7g5_master_enable, |
| 326 | .disable = clk_sama7g5_master_disable, |
| 327 | .set_rate = clk_sama7g5_master_set_rate, |
| 328 | .get_rate = clk_sama7g5_master_get_rate, |
| 329 | .set_parent = clk_sama7g5_master_set_parent, |
| 330 | }; |
| 331 | |
| 332 | struct clk *at91_clk_sama7g5_register_master(void __iomem *base, |
| 333 | const char *name, const char * const *parent_names, |
| 334 | int num_parents, const u32 *mux_table, const u32 *clk_mux_table, |
| 335 | bool critical, u8 id) |
| 336 | { |
| 337 | struct clk_master *master; |
| 338 | struct clk *clk; |
| 339 | u32 val, index; |
| 340 | int ret; |
| 341 | |
| 342 | if (!base || !name || !num_parents || !parent_names || |
| 343 | !mux_table || !clk_mux_table || id > MASTER_MAX_ID) |
| 344 | return ERR_PTR(-EINVAL); |
| 345 | |
| 346 | master = kzalloc(sizeof(*master), GFP_KERNEL); |
| 347 | if (!master) |
| 348 | return ERR_PTR(-ENOMEM); |
| 349 | |
| 350 | master->base = base; |
| 351 | master->id = id; |
| 352 | master->mux_table = mux_table; |
| 353 | master->clk_mux_table = clk_mux_table; |
| 354 | master->num_parents = num_parents; |
| 355 | |
| 356 | pmc_write(master->base, PMC_MCR, master->id); |
| 357 | pmc_read(master->base, PMC_MCR, &val); |
| 358 | |
| 359 | index = at91_clk_mux_val_to_index(master->mux_table, |
| 360 | master->num_parents, |
| 361 | (val & PMC_MCR_CSS) >> PMC_MCR_CSS_SHIFT); |
| 362 | if (index < 0) { |
| 363 | kfree(master); |
| 364 | return ERR_PTR(index); |
| 365 | } |
| 366 | |
| 367 | clk = &master->clk; |
| 368 | clk->flags = CLK_GET_RATE_NOCACHE | (critical ? CLK_IS_CRITICAL : 0); |
| 369 | |
| 370 | ret = clk_register(clk, UBOOT_DM_CLK_AT91_SAMA7G5_MASTER, name, |
| 371 | parent_names[index]); |
| 372 | if (ret) { |
| 373 | kfree(master); |
| 374 | clk = ERR_PTR(ret); |
| 375 | } |
| 376 | |
| 377 | return clk; |
| 378 | } |
| 379 | |
| 380 | U_BOOT_DRIVER(at91_sama7g5_master_clk) = { |
| 381 | .name = UBOOT_DM_CLK_AT91_SAMA7G5_MASTER, |
| 382 | .id = UCLASS_CLK, |
| 383 | .ops = &sama7g5_master_ops, |
| 384 | .flags = DM_FLAG_PRE_RELOC, |
| 385 | }; |
| 386 | |
Claudiu Beznea | 1f9023a | 2020-09-07 17:46:43 +0300 | [diff] [blame] | 387 | const struct clk_master_layout at91rm9200_master_layout = { |
| 388 | .mask = 0x31F, |
| 389 | .pres_shift = 2, |
| 390 | .offset = AT91_PMC_MCKR, |
| 391 | }; |
| 392 | |
| 393 | const struct clk_master_layout at91sam9x5_master_layout = { |
| 394 | .mask = 0x373, |
| 395 | .pres_shift = 4, |
| 396 | .offset = AT91_PMC_MCKR, |
| 397 | }; |