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