linux/drivers/clk/mxs/clk-ref.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 * Copyright 2012 Freescale Semiconductor, Inc.
   4 */
   5
   6#include <linux/clk-provider.h>
   7#include <linux/err.h>
   8#include <linux/io.h>
   9#include <linux/slab.h>
  10#include "clk.h"
  11
  12/**
  13 * struct clk_ref - mxs reference clock
  14 * @hw: clk_hw for the reference clock
  15 * @reg: register address
  16 * @idx: the index of the reference clock within the same register
  17 *
  18 * The mxs reference clock sources from pll.  Every 4 reference clocks share
  19 * one register space, and @idx is used to identify them.  Each reference
  20 * clock has a gate control and a fractional * divider.  The rate is calculated
  21 * as pll rate  * (18 / FRAC), where FRAC = 18 ~ 35.
  22 */
  23struct clk_ref {
  24        struct clk_hw hw;
  25        void __iomem *reg;
  26        u8 idx;
  27};
  28
  29#define to_clk_ref(_hw) container_of(_hw, struct clk_ref, hw)
  30
  31static int clk_ref_enable(struct clk_hw *hw)
  32{
  33        struct clk_ref *ref = to_clk_ref(hw);
  34
  35        writel_relaxed(1 << ((ref->idx + 1) * 8 - 1), ref->reg + CLR);
  36
  37        return 0;
  38}
  39
  40static void clk_ref_disable(struct clk_hw *hw)
  41{
  42        struct clk_ref *ref = to_clk_ref(hw);
  43
  44        writel_relaxed(1 << ((ref->idx + 1) * 8 - 1), ref->reg + SET);
  45}
  46
  47static unsigned long clk_ref_recalc_rate(struct clk_hw *hw,
  48                                         unsigned long parent_rate)
  49{
  50        struct clk_ref *ref = to_clk_ref(hw);
  51        u64 tmp = parent_rate;
  52        u8 frac = (readl_relaxed(ref->reg) >> (ref->idx * 8)) & 0x3f;
  53
  54        tmp *= 18;
  55        do_div(tmp, frac);
  56
  57        return tmp;
  58}
  59
  60static long clk_ref_round_rate(struct clk_hw *hw, unsigned long rate,
  61                               unsigned long *prate)
  62{
  63        unsigned long parent_rate = *prate;
  64        u64 tmp = parent_rate;
  65        u8 frac;
  66
  67        tmp = tmp * 18 + rate / 2;
  68        do_div(tmp, rate);
  69        frac = tmp;
  70
  71        if (frac < 18)
  72                frac = 18;
  73        else if (frac > 35)
  74                frac = 35;
  75
  76        tmp = parent_rate;
  77        tmp *= 18;
  78        do_div(tmp, frac);
  79
  80        return tmp;
  81}
  82
  83static int clk_ref_set_rate(struct clk_hw *hw, unsigned long rate,
  84                            unsigned long parent_rate)
  85{
  86        struct clk_ref *ref = to_clk_ref(hw);
  87        unsigned long flags;
  88        u64 tmp = parent_rate;
  89        u32 val;
  90        u8 frac, shift = ref->idx * 8;
  91
  92        tmp = tmp * 18 + rate / 2;
  93        do_div(tmp, rate);
  94        frac = tmp;
  95
  96        if (frac < 18)
  97                frac = 18;
  98        else if (frac > 35)
  99                frac = 35;
 100
 101        spin_lock_irqsave(&mxs_lock, flags);
 102
 103        val = readl_relaxed(ref->reg);
 104        val &= ~(0x3f << shift);
 105        val |= frac << shift;
 106        writel_relaxed(val, ref->reg);
 107
 108        spin_unlock_irqrestore(&mxs_lock, flags);
 109
 110        return 0;
 111}
 112
 113static const struct clk_ops clk_ref_ops = {
 114        .enable         = clk_ref_enable,
 115        .disable        = clk_ref_disable,
 116        .recalc_rate    = clk_ref_recalc_rate,
 117        .round_rate     = clk_ref_round_rate,
 118        .set_rate       = clk_ref_set_rate,
 119};
 120
 121struct clk *mxs_clk_ref(const char *name, const char *parent_name,
 122                        void __iomem *reg, u8 idx)
 123{
 124        struct clk_ref *ref;
 125        struct clk *clk;
 126        struct clk_init_data init;
 127
 128        ref = kzalloc(sizeof(*ref), GFP_KERNEL);
 129        if (!ref)
 130                return ERR_PTR(-ENOMEM);
 131
 132        init.name = name;
 133        init.ops = &clk_ref_ops;
 134        init.flags = 0;
 135        init.parent_names = (parent_name ? &parent_name: NULL);
 136        init.num_parents = (parent_name ? 1 : 0);
 137
 138        ref->reg = reg;
 139        ref->idx = idx;
 140        ref->hw.init = &init;
 141
 142        clk = clk_register(NULL, &ref->hw);
 143        if (IS_ERR(clk))
 144                kfree(ref);
 145
 146        return clk;
 147}
 148