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