uboot/drivers/clk/at91/clk-peripheral.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * Peripheral 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-peripheral.c from Linux.
  10 */
  11#include <common.h>
  12#include <clk-uclass.h>
  13#include <dm.h>
  14#include <linux/io.h>
  15#include <linux/clk-provider.h>
  16#include <linux/clk/at91_pmc.h>
  17
  18#include "pmc.h"
  19
  20#define UBOOT_DM_CLK_AT91_PERIPH                "at91-periph-clk"
  21#define UBOOT_DM_CLK_AT91_SAM9X5_PERIPH         "at91-sam9x5-periph-clk"
  22
  23#define PERIPHERAL_ID_MIN       2
  24#define PERIPHERAL_ID_MAX       31
  25#define PERIPHERAL_MASK(id)     (1 << ((id) & PERIPHERAL_ID_MAX))
  26
  27#define PERIPHERAL_MAX_SHIFT    3
  28
  29struct clk_peripheral {
  30        void __iomem *base;
  31        struct clk clk;
  32        u32 id;
  33};
  34
  35#define to_clk_peripheral(_c) container_of(_c, struct clk_peripheral, clk)
  36
  37struct clk_sam9x5_peripheral {
  38        const struct clk_pcr_layout *layout;
  39        void __iomem *base;
  40        struct clk clk;
  41        struct clk_range range;
  42        u32 id;
  43        u32 div;
  44        bool auto_div;
  45};
  46
  47#define to_clk_sam9x5_peripheral(_c) \
  48        container_of(_c, struct clk_sam9x5_peripheral, clk)
  49
  50static int clk_peripheral_enable(struct clk *clk)
  51{
  52        struct clk_peripheral *periph = to_clk_peripheral(clk);
  53        int offset = AT91_PMC_PCER;
  54        u32 id = periph->id;
  55
  56        if (id < PERIPHERAL_ID_MIN)
  57                return 0;
  58        if (id > PERIPHERAL_ID_MAX)
  59                offset = AT91_PMC_PCER1;
  60        pmc_write(periph->base, offset, PERIPHERAL_MASK(id));
  61
  62        return 0;
  63}
  64
  65static int clk_peripheral_disable(struct clk *clk)
  66{
  67        struct clk_peripheral *periph = to_clk_peripheral(clk);
  68        int offset = AT91_PMC_PCDR;
  69        u32 id = periph->id;
  70
  71        if (id < PERIPHERAL_ID_MIN)
  72                return -EINVAL;
  73
  74        if (id > PERIPHERAL_ID_MAX)
  75                offset = AT91_PMC_PCDR1;
  76        pmc_write(periph->base, offset, PERIPHERAL_MASK(id));
  77
  78        return 0;
  79}
  80
  81static const struct clk_ops peripheral_ops = {
  82        .enable = clk_peripheral_enable,
  83        .disable = clk_peripheral_disable,
  84        .get_rate = clk_generic_get_rate,
  85};
  86
  87struct clk *
  88at91_clk_register_peripheral(void __iomem *base, const char *name,
  89                             const char *parent_name, u32 id)
  90{
  91        struct clk_peripheral *periph;
  92        struct clk *clk;
  93        int ret;
  94
  95        if (!base || !name || !parent_name || id > PERIPHERAL_ID_MAX)
  96                return ERR_PTR(-EINVAL);
  97
  98        periph = kzalloc(sizeof(*periph), GFP_KERNEL);
  99        if (!periph)
 100                return ERR_PTR(-ENOMEM);
 101
 102        periph->id = id;
 103        periph->base = base;
 104
 105        clk = &periph->clk;
 106        clk->flags = CLK_GET_RATE_NOCACHE;
 107        ret = clk_register(clk, UBOOT_DM_CLK_AT91_PERIPH, name, parent_name);
 108        if (ret) {
 109                kfree(periph);
 110                clk = ERR_PTR(ret);
 111        }
 112
 113        return clk;
 114}
 115
 116U_BOOT_DRIVER(at91_periph_clk) = {
 117        .name = UBOOT_DM_CLK_AT91_PERIPH,
 118        .id = UCLASS_CLK,
 119        .ops = &peripheral_ops,
 120        .flags = DM_FLAG_PRE_RELOC,
 121};
 122
 123static int clk_sam9x5_peripheral_enable(struct clk *clk)
 124{
 125        struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(clk);
 126
 127        if (periph->id < PERIPHERAL_ID_MIN)
 128                return 0;
 129
 130        pmc_write(periph->base, periph->layout->offset,
 131                  (periph->id & periph->layout->pid_mask));
 132        pmc_update_bits(periph->base, periph->layout->offset,
 133                        periph->layout->cmd | AT91_PMC_PCR_EN,
 134                        periph->layout->cmd | AT91_PMC_PCR_EN);
 135
 136        return 0;
 137}
 138
 139static int clk_sam9x5_peripheral_disable(struct clk *clk)
 140{
 141        struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(clk);
 142
 143        if (periph->id < PERIPHERAL_ID_MIN)
 144                return -EINVAL;
 145
 146        pmc_write(periph->base, periph->layout->offset,
 147                  (periph->id & periph->layout->pid_mask));
 148        pmc_update_bits(periph->base, periph->layout->offset,
 149                        AT91_PMC_PCR_EN | periph->layout->cmd,
 150                        periph->layout->cmd);
 151
 152        return 0;
 153}
 154
 155static ulong clk_sam9x5_peripheral_get_rate(struct clk *clk)
 156{
 157        struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(clk);
 158        ulong parent_rate = clk_get_parent_rate(clk);
 159        u32 val, shift = ffs(periph->layout->div_mask) - 1;
 160
 161        if (!parent_rate)
 162                return 0;
 163
 164        pmc_write(periph->base, periph->layout->offset,
 165                  (periph->id & periph->layout->pid_mask));
 166        pmc_read(periph->base, periph->layout->offset, &val);
 167        shift = (val & periph->layout->div_mask) >> shift;
 168
 169        return parent_rate >> shift;
 170}
 171
 172static ulong clk_sam9x5_peripheral_set_rate(struct clk *clk, ulong rate)
 173{
 174        struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(clk);
 175        ulong parent_rate = clk_get_parent_rate(clk);
 176        int shift;
 177
 178        if (!parent_rate)
 179                return 0;
 180
 181        if (periph->id < PERIPHERAL_ID_MIN || !periph->range.max) {
 182                if (parent_rate == rate)
 183                        return rate;
 184                else
 185                        return 0;
 186        }
 187
 188        if (periph->range.max && rate > periph->range.max)
 189                return 0;
 190
 191        for (shift = 0; shift <= PERIPHERAL_MAX_SHIFT; shift++) {
 192                if (parent_rate >> shift <= rate)
 193                        break;
 194        }
 195        if (shift == PERIPHERAL_MAX_SHIFT + 1)
 196                return 0;
 197
 198        pmc_write(periph->base, periph->layout->offset,
 199                  (periph->id & periph->layout->pid_mask));
 200        pmc_update_bits(periph->base, periph->layout->offset,
 201                        periph->layout->div_mask | periph->layout->cmd,
 202                        (shift << (ffs(periph->layout->div_mask) - 1)) |
 203                        periph->layout->cmd);
 204
 205        return parent_rate >> shift;
 206}
 207
 208static const struct clk_ops sam9x5_peripheral_ops = {
 209        .enable = clk_sam9x5_peripheral_enable,
 210        .disable = clk_sam9x5_peripheral_disable,
 211        .get_rate = clk_sam9x5_peripheral_get_rate,
 212        .set_rate = clk_sam9x5_peripheral_set_rate,
 213};
 214
 215struct clk *
 216at91_clk_register_sam9x5_peripheral(void __iomem *base,
 217                                    const struct clk_pcr_layout *layout,
 218                                    const char *name, const char *parent_name,
 219                                    u32 id, const struct clk_range *range)
 220{
 221        struct clk_sam9x5_peripheral *periph;
 222        struct clk *clk;
 223        int ret;
 224
 225        if (!base || !layout || !name || !parent_name || !range)
 226                return ERR_PTR(-EINVAL);
 227
 228        periph = kzalloc(sizeof(*periph), GFP_KERNEL);
 229        if (!periph)
 230                return ERR_PTR(-ENOMEM);
 231
 232        periph->id = id;
 233        periph->base = base;
 234        periph->layout = layout;
 235        periph->range = *range;
 236
 237        clk = &periph->clk;
 238        clk->flags = CLK_GET_RATE_NOCACHE;
 239        ret = clk_register(clk, UBOOT_DM_CLK_AT91_SAM9X5_PERIPH, name,
 240                           parent_name);
 241        if (ret) {
 242                kfree(periph);
 243                clk = ERR_PTR(ret);
 244        }
 245
 246        return clk;
 247}
 248
 249U_BOOT_DRIVER(at91_sam9x5_periph_clk) = {
 250        .name = UBOOT_DM_CLK_AT91_SAM9X5_PERIPH,
 251        .id = UCLASS_CLK,
 252        .ops = &sam9x5_peripheral_ops,
 253        .flags = DM_FLAG_PRE_RELOC,
 254};
 255