linux/drivers/clk/mxs/clk-frac.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_frac - mxs fractional divider clock
  14 * @hw: clk_hw for the fractional divider clock
  15 * @reg: register address
  16 * @shift: the divider bit shift
  17 * @width: the divider bit width
  18 * @busy: busy bit shift
  19 *
  20 * The clock is an adjustable fractional divider with a busy bit to wait
  21 * when the divider is adjusted.
  22 */
  23struct clk_frac {
  24        struct clk_hw hw;
  25        void __iomem *reg;
  26        u8 shift;
  27        u8 width;
  28        u8 busy;
  29};
  30
  31#define to_clk_frac(_hw) container_of(_hw, struct clk_frac, hw)
  32
  33static unsigned long clk_frac_recalc_rate(struct clk_hw *hw,
  34                                          unsigned long parent_rate)
  35{
  36        struct clk_frac *frac = to_clk_frac(hw);
  37        u32 div;
  38        u64 tmp_rate;
  39
  40        div = readl_relaxed(frac->reg) >> frac->shift;
  41        div &= (1 << frac->width) - 1;
  42
  43        tmp_rate = (u64)parent_rate * div;
  44        return tmp_rate >> frac->width;
  45}
  46
  47static long clk_frac_round_rate(struct clk_hw *hw, unsigned long rate,
  48                                unsigned long *prate)
  49{
  50        struct clk_frac *frac = to_clk_frac(hw);
  51        unsigned long parent_rate = *prate;
  52        u32 div;
  53        u64 tmp, tmp_rate, result;
  54
  55        if (rate > parent_rate)
  56                return -EINVAL;
  57
  58        tmp = rate;
  59        tmp <<= frac->width;
  60        do_div(tmp, parent_rate);
  61        div = tmp;
  62
  63        if (!div)
  64                return -EINVAL;
  65
  66        tmp_rate = (u64)parent_rate * div;
  67        result = tmp_rate >> frac->width;
  68        if ((result << frac->width) < tmp_rate)
  69                result += 1;
  70        return result;
  71}
  72
  73static int clk_frac_set_rate(struct clk_hw *hw, unsigned long rate,
  74                             unsigned long parent_rate)
  75{
  76        struct clk_frac *frac = to_clk_frac(hw);
  77        unsigned long flags;
  78        u32 div, val;
  79        u64 tmp;
  80
  81        if (rate > parent_rate)
  82                return -EINVAL;
  83
  84        tmp = rate;
  85        tmp <<= frac->width;
  86        do_div(tmp, parent_rate);
  87        div = tmp;
  88
  89        if (!div)
  90                return -EINVAL;
  91
  92        spin_lock_irqsave(&mxs_lock, flags);
  93
  94        val = readl_relaxed(frac->reg);
  95        val &= ~(((1 << frac->width) - 1) << frac->shift);
  96        val |= div << frac->shift;
  97        writel_relaxed(val, frac->reg);
  98
  99        spin_unlock_irqrestore(&mxs_lock, flags);
 100
 101        return mxs_clk_wait(frac->reg, frac->busy);
 102}
 103
 104static const struct clk_ops clk_frac_ops = {
 105        .recalc_rate = clk_frac_recalc_rate,
 106        .round_rate = clk_frac_round_rate,
 107        .set_rate = clk_frac_set_rate,
 108};
 109
 110struct clk *mxs_clk_frac(const char *name, const char *parent_name,
 111                         void __iomem *reg, u8 shift, u8 width, u8 busy)
 112{
 113        struct clk_frac *frac;
 114        struct clk *clk;
 115        struct clk_init_data init;
 116
 117        frac = kzalloc(sizeof(*frac), GFP_KERNEL);
 118        if (!frac)
 119                return ERR_PTR(-ENOMEM);
 120
 121        init.name = name;
 122        init.ops = &clk_frac_ops;
 123        init.flags = CLK_SET_RATE_PARENT;
 124        init.parent_names = (parent_name ? &parent_name: NULL);
 125        init.num_parents = (parent_name ? 1 : 0);
 126
 127        frac->reg = reg;
 128        frac->shift = shift;
 129        frac->width = width;
 130        frac->busy = busy;
 131        frac->hw.init = &init;
 132
 133        clk = clk_register(NULL, &frac->hw);
 134        if (IS_ERR(clk))
 135                kfree(frac);
 136
 137        return clk;
 138}
 139