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 * @clk_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 long clk_pfdv2_round_rate(struct clk_hw *hw, unsigned long rate,
 102                                 unsigned long *prate)
 103{
 104        u64 tmp = *prate;
 105        u8 frac;
 106
 107        tmp = tmp * 18 + rate / 2;
 108        do_div(tmp, rate);
 109        frac = tmp;
 110
 111        if (frac < 12)
 112                frac = 12;
 113        else if (frac > 35)
 114                frac = 35;
 115
 116        tmp = *prate;
 117        tmp *= 18;
 118        do_div(tmp, frac);
 119
 120        return tmp;
 121}
 122
 123static int clk_pfdv2_is_enabled(struct clk_hw *hw)
 124{
 125        struct clk_pfdv2 *pfd = to_clk_pfdv2(hw);
 126
 127        if (readl_relaxed(pfd->reg) & (1 << pfd->gate_bit))
 128                return 0;
 129
 130        return 1;
 131}
 132
 133static int clk_pfdv2_set_rate(struct clk_hw *hw, unsigned long rate,
 134                              unsigned long parent_rate)
 135{
 136        struct clk_pfdv2 *pfd = to_clk_pfdv2(hw);
 137        unsigned long flags;
 138        u64 tmp = parent_rate;
 139        u32 val;
 140        u8 frac;
 141
 142        tmp = tmp * 18 + rate / 2;
 143        do_div(tmp, rate);
 144        frac = tmp;
 145        if (frac < 12)
 146                frac = 12;
 147        else if (frac > 35)
 148                frac = 35;
 149
 150        spin_lock_irqsave(&pfd_lock, flags);
 151        val = readl_relaxed(pfd->reg);
 152        val &= ~(CLK_PFDV2_FRAC_MASK << pfd->frac_off);
 153        val |= frac << pfd->frac_off;
 154        writel_relaxed(val, pfd->reg);
 155        spin_unlock_irqrestore(&pfd_lock, flags);
 156
 157        return 0;
 158}
 159
 160static const struct clk_ops clk_pfdv2_ops = {
 161        .enable         = clk_pfdv2_enable,
 162        .disable        = clk_pfdv2_disable,
 163        .recalc_rate    = clk_pfdv2_recalc_rate,
 164        .round_rate     = clk_pfdv2_round_rate,
 165        .set_rate       = clk_pfdv2_set_rate,
 166        .is_enabled     = clk_pfdv2_is_enabled,
 167};
 168
 169struct clk_hw *imx_clk_pfdv2(const char *name, const char *parent_name,
 170                             void __iomem *reg, u8 idx)
 171{
 172        struct clk_init_data init;
 173        struct clk_pfdv2 *pfd;
 174        struct clk_hw *hw;
 175        int ret;
 176
 177        WARN_ON(idx > 3);
 178
 179        pfd = kzalloc(sizeof(*pfd), GFP_KERNEL);
 180        if (!pfd)
 181                return ERR_PTR(-ENOMEM);
 182
 183        pfd->reg = reg;
 184        pfd->gate_bit = (idx + 1) * 8 - 1;
 185        pfd->vld_bit = pfd->gate_bit - 1;
 186        pfd->frac_off = idx * 8;
 187
 188        init.name = name;
 189        init.ops = &clk_pfdv2_ops;
 190        init.parent_names = &parent_name;
 191        init.num_parents = 1;
 192        init.flags = CLK_SET_RATE_GATE;
 193
 194        pfd->hw.init = &init;
 195
 196        hw = &pfd->hw;
 197        ret = clk_hw_register(NULL, hw);
 198        if (ret) {
 199                kfree(pfd);
 200                hw = ERR_PTR(ret);
 201        }
 202
 203        return hw;
 204}
 205