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 <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"
  22#define UBOOT_DM_CLK_AT91_SAMA7G5_MASTER        "at91-sama7g5-master-clk"
  23
  24#define MASTER_PRES_MASK        0x7
  25#define MASTER_PRES_MAX         MASTER_PRES_MASK
  26#define MASTER_DIV_SHIFT        8
  27#define MASTER_DIV_MASK         0x7
  28
  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
  41struct 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
  54static inline bool clk_master_ready(struct clk_master *master)
  55{
  56        unsigned int bit = master->id ? AT91_PMC_MCKXRDY : AT91_PMC_MCKRDY;
  57        unsigned int status;
  58
  59        pmc_read(master->base, AT91_PMC_SR, &status);
  60
  61        return !!(status & bit);
  62}
  63
  64static 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
  76static 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
 110static const struct clk_ops master_ops = {
 111        .enable = clk_master_enable,
 112        .get_rate = clk_master_get_rate,
 113};
 114
 115struct 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
 153U_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
 160static 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
 183static 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
 195static 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
 207static 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
 236static 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
 259static 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
 267struct 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
 315U_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
 322const struct clk_master_layout at91rm9200_master_layout = {
 323        .mask = 0x31F,
 324        .pres_shift = 2,
 325        .offset = AT91_PMC_MCKR,
 326};
 327
 328const struct clk_master_layout at91sam9x5_master_layout = {
 329        .mask = 0x373,
 330        .pres_shift = 4,
 331        .offset = AT91_PMC_MCKR,
 332};
 333