linux/drivers/clk/imx/clk-pfdv2.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * Copyright (C) 2016 Freescale Semiconductor, Inc.
   4 * Copyright 2017~2018 NXP
   5 *
   6 * Author: Dong Aisheng <aisheng.dong@nxp.com>
   7 *
   8 */
   9
  10#include <linux/clk-provider.h>
  11#include <linux/err.h>
  12#include <linux/io.h>
  13#include <linux/iopoll.h>
  14#include <linux/slab.h>
  15
  16#include "clk.h"
  17
  18/**
  19 * struct clk_pfdv2 - IMX PFD clock
  20 * @hw:         clock source
  21 * @reg:        PFD register address
  22 * @gate_bit:   Gate bit offset
  23 * @vld_bit:    Valid bit offset
  24 * @frac_off:   PLL Fractional Divider offset
  25 */
  26
  27struct clk_pfdv2 {
  28        struct clk_hw   hw;
  29        void __iomem    *reg;
  30        u8              gate_bit;
  31        u8              vld_bit;
  32        u8              frac_off;
  33};
  34
  35#define to_clk_pfdv2(_hw) container_of(_hw, struct clk_pfdv2, hw)
  36
  37#define CLK_PFDV2_FRAC_MASK 0x3f
  38
  39#define LOCK_TIMEOUT_US         USEC_PER_MSEC
  40
  41static DEFINE_SPINLOCK(pfd_lock);
  42
  43static int clk_pfdv2_wait(struct clk_pfdv2 *pfd)
  44{
  45        u32 val;
  46
  47        return readl_poll_timeout(pfd->reg, val, val & (1 << pfd->vld_bit),
  48                                  0, LOCK_TIMEOUT_US);
  49}
  50
  51static int clk_pfdv2_enable(struct clk_hw *hw)
  52{
  53        struct clk_pfdv2 *pfd = to_clk_pfdv2(hw);
  54        unsigned long flags;
  55        u32 val;
  56
  57        spin_lock_irqsave(&pfd_lock, flags);
  58        val = readl_relaxed(pfd->reg);
  59        val &= ~(1 << pfd->gate_bit);
  60        writel_relaxed(val, pfd->reg);
  61        spin_unlock_irqrestore(&pfd_lock, flags);
  62
  63        return clk_pfdv2_wait(pfd);
  64}
  65
  66static void clk_pfdv2_disable(struct clk_hw *hw)
  67{
  68        struct clk_pfdv2 *pfd = to_clk_pfdv2(hw);
  69        unsigned long flags;
  70        u32 val;
  71
  72        spin_lock_irqsave(&pfd_lock, flags);
  73        val = readl_relaxed(pfd->reg);
  74        val |= (1 << pfd->gate_bit);
  75        writel_relaxed(val, pfd->reg);
  76        spin_unlock_irqrestore(&pfd_lock, flags);
  77}
  78
  79static unsigned long clk_pfdv2_recalc_rate(struct clk_hw *hw,
  80                                           unsigned long parent_rate)
  81{
  82        struct clk_pfdv2 *pfd = to_clk_pfdv2(hw);
  83        u64 tmp = parent_rate;
  84        u8 frac;
  85
  86        frac = (readl_relaxed(pfd->reg) >> pfd->frac_off)
  87                & CLK_PFDV2_FRAC_MASK;
  88
  89        if (!frac) {
  90                pr_debug("clk_pfdv2: %s invalid pfd frac value 0\n",
  91                         clk_hw_get_name(hw));
  92                return 0;
  93        }
  94
  95        tmp *= 18;
  96        do_div(tmp, frac);
  97
  98        return tmp;
  99}
 100
 101static int clk_pfdv2_determine_rate(struct clk_hw *hw,
 102                                    struct clk_rate_request *req)
 103{
 104        unsigned long parent_rates[] = {
 105                                        480000000,
 106                                        528000000,
 107                                        req->best_parent_rate
 108                                       };
 109        unsigned long best_rate = -1UL, rate = req->rate;
 110        unsigned long best_parent_rate = req->best_parent_rate;
 111        u64 tmp;
 112        u8 frac;
 113        int i;
 114
 115        for (i = 0; i < ARRAY_SIZE(parent_rates); i++) {
 116                tmp = parent_rates[i];
 117                tmp = tmp * 18 + rate / 2;
 118                do_div(tmp, rate);
 119                frac = tmp;
 120
 121                if (frac < 12)
 122                        frac = 12;
 123                else if (frac > 35)
 124                        frac = 35;
 125
 126                tmp = parent_rates[i];
 127                tmp *= 18;
 128                do_div(tmp, frac);
 129
 130                if (abs(tmp - req->rate) < abs(best_rate - req->rate)) {
 131                        best_rate = tmp;
 132                        best_parent_rate = parent_rates[i];
 133                }
 134        }
 135
 136        req->best_parent_rate = best_parent_rate;
 137        req->rate = best_rate;
 138
 139        return 0;
 140}
 141
 142static int clk_pfdv2_is_enabled(struct clk_hw *hw)
 143{
 144        struct clk_pfdv2 *pfd = to_clk_pfdv2(hw);
 145
 146        if (readl_relaxed(pfd->reg) & (1 << pfd->gate_bit))
 147                return 0;
 148
 149        return 1;
 150}
 151
 152static int clk_pfdv2_set_rate(struct clk_hw *hw, unsigned long rate,
 153                              unsigned long parent_rate)
 154{
 155        struct clk_pfdv2 *pfd = to_clk_pfdv2(hw);
 156        unsigned long flags;
 157        u64 tmp = parent_rate;
 158        u32 val;
 159        u8 frac;
 160
 161        if (!rate)
 162                return -EINVAL;
 163
 164        /* PFD can NOT change rate without gating */
 165        WARN_ON(clk_pfdv2_is_enabled(hw));
 166
 167        tmp = tmp * 18 + rate / 2;
 168        do_div(tmp, rate);
 169        frac = tmp;
 170        if (frac < 12)
 171                frac = 12;
 172        else if (frac > 35)
 173                frac = 35;
 174
 175        spin_lock_irqsave(&pfd_lock, flags);
 176        val = readl_relaxed(pfd->reg);
 177        val &= ~(CLK_PFDV2_FRAC_MASK << pfd->frac_off);
 178        val |= frac << pfd->frac_off;
 179        writel_relaxed(val, pfd->reg);
 180        spin_unlock_irqrestore(&pfd_lock, flags);
 181
 182        return 0;
 183}
 184
 185static const struct clk_ops clk_pfdv2_ops = {
 186        .enable         = clk_pfdv2_enable,
 187        .disable        = clk_pfdv2_disable,
 188        .recalc_rate    = clk_pfdv2_recalc_rate,
 189        .determine_rate = clk_pfdv2_determine_rate,
 190        .set_rate       = clk_pfdv2_set_rate,
 191        .is_enabled     = clk_pfdv2_is_enabled,
 192};
 193
 194struct clk_hw *imx_clk_hw_pfdv2(const char *name, const char *parent_name,
 195                             void __iomem *reg, u8 idx)
 196{
 197        struct clk_init_data init;
 198        struct clk_pfdv2 *pfd;
 199        struct clk_hw *hw;
 200        int ret;
 201
 202        WARN_ON(idx > 3);
 203
 204        pfd = kzalloc(sizeof(*pfd), GFP_KERNEL);
 205        if (!pfd)
 206                return ERR_PTR(-ENOMEM);
 207
 208        pfd->reg = reg;
 209        pfd->gate_bit = (idx + 1) * 8 - 1;
 210        pfd->vld_bit = pfd->gate_bit - 1;
 211        pfd->frac_off = idx * 8;
 212
 213        init.name = name;
 214        init.ops = &clk_pfdv2_ops;
 215        init.parent_names = &parent_name;
 216        init.num_parents = 1;
 217        init.flags = CLK_SET_RATE_GATE | CLK_SET_RATE_PARENT;
 218
 219        pfd->hw.init = &init;
 220
 221        hw = &pfd->hw;
 222        ret = clk_hw_register(NULL, hw);
 223        if (ret) {
 224                kfree(pfd);
 225                hw = ERR_PTR(ret);
 226        }
 227
 228        return hw;
 229}
 230