uboot/drivers/clk/at91/clk-master.c
<<
>>
Prefs
   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 <div64.h>
  16#include <dm.h>
  17#include <linux/clk-provider.h>
  18#include <linux/clk/at91_pmc.h>
  19
  20#include "pmc.h"
  21
  22#define UBOOT_DM_CLK_AT91_MASTER_PRES           "at91-master-clk-pres"
  23#define UBOOT_DM_CLK_AT91_MASTER_DIV            "at91-master-clk-div"
  24#define UBOOT_DM_CLK_AT91_SAMA7G5_MASTER        "at91-sama7g5-master-clk"
  25
  26#define MASTER_PRES_MASK        0x7
  27#define MASTER_PRES_MAX         MASTER_PRES_MASK
  28#define MASTER_DIV_SHIFT        8
  29#define MASTER_DIV_MASK         0x7
  30
  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
  43struct 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
  56static inline bool clk_master_ready(struct clk_master *master)
  57{
  58        unsigned int bit = master->id ? AT91_PMC_MCKXRDY : AT91_PMC_MCKRDY;
  59        unsigned int status;
  60
  61        pmc_read(master->base, AT91_PMC_SR, &status);
  62
  63        return !!(status & bit);
  64}
  65
  66static 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
  78static ulong clk_master_pres_get_rate(struct clk *clk)
  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;
  86        u8 pres;
  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;
  95
  96        if (characteristics->have_div3_pres && pres == MASTER_PRES_MAX)
  97                pres = 3;
  98        else
  99                pres = (1 << pres);
 100
 101        return DIV_ROUND_CLOSEST_ULL(rate, pres);
 102}
 103
 104static const struct clk_ops master_pres_ops = {
 105        .enable = clk_master_enable,
 106        .get_rate = clk_master_pres_get_rate,
 107};
 108
 109struct clk *at91_clk_register_master_pres(void __iomem *base,
 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;
 137        ret = clk_register(clk, UBOOT_DM_CLK_AT91_MASTER_PRES, name,
 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
 147U_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
 154static 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
 180static const struct clk_ops master_div_ops = {
 181        .enable = clk_master_enable,
 182        .get_rate = clk_master_div_get_rate,
 183};
 184
 185struct 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
 218U_BOOT_DRIVER(at91_master_div_clk) = {
 219        .name = UBOOT_DM_CLK_AT91_MASTER_DIV,
 220        .id = UCLASS_CLK,
 221        .ops = &master_div_ops,
 222        .flags = DM_FLAG_PRE_RELOC,
 223};
 224
 225static 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
 248static 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
 260static 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
 272static 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
 301static 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
 324static 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
 332struct 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
 380U_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
 387const struct clk_master_layout at91rm9200_master_layout = {
 388        .mask = 0x31F,
 389        .pres_shift = 2,
 390        .offset = AT91_PMC_MCKR,
 391};
 392
 393const struct clk_master_layout at91sam9x5_master_layout = {
 394        .mask = 0x373,
 395        .pres_shift = 4,
 396        .offset = AT91_PMC_MCKR,
 397};
 398