linux/drivers/clk/mxs/clk-frac.c
<<
>>
Prefs
   1/*
   2 * Copyright 2012 Freescale Semiconductor, Inc.
   3 *
   4 * The code contained herein is licensed under the GNU General Public
   5 * License. You may obtain a copy of the GNU General Public License
   6 * Version 2 or later at the following locations:
   7 *
   8 * http://www.opensource.org/licenses/gpl-license.html
   9 * http://www.gnu.org/copyleft/gpl.html
  10 */
  11
  12#include <linux/clk.h>
  13#include <linux/clk-provider.h>
  14#include <linux/err.h>
  15#include <linux/io.h>
  16#include <linux/slab.h>
  17#include "clk.h"
  18
  19/**
  20 * struct clk_frac - mxs fractional divider clock
  21 * @hw: clk_hw for the fractional divider clock
  22 * @reg: register address
  23 * @shift: the divider bit shift
  24 * @width: the divider bit width
  25 * @busy: busy bit shift
  26 *
  27 * The clock is an adjustable fractional divider with a busy bit to wait
  28 * when the divider is adjusted.
  29 */
  30struct clk_frac {
  31        struct clk_hw hw;
  32        void __iomem *reg;
  33        u8 shift;
  34        u8 width;
  35        u8 busy;
  36};
  37
  38#define to_clk_frac(_hw) container_of(_hw, struct clk_frac, hw)
  39
  40static unsigned long clk_frac_recalc_rate(struct clk_hw *hw,
  41                                          unsigned long parent_rate)
  42{
  43        struct clk_frac *frac = to_clk_frac(hw);
  44        u32 div;
  45
  46        div = readl_relaxed(frac->reg) >> frac->shift;
  47        div &= (1 << frac->width) - 1;
  48
  49        return (parent_rate >> frac->width) * div;
  50}
  51
  52static long clk_frac_round_rate(struct clk_hw *hw, unsigned long rate,
  53                                unsigned long *prate)
  54{
  55        struct clk_frac *frac = to_clk_frac(hw);
  56        unsigned long parent_rate = *prate;
  57        u32 div;
  58        u64 tmp;
  59
  60        if (rate > parent_rate)
  61                return -EINVAL;
  62
  63        tmp = rate;
  64        tmp <<= frac->width;
  65        do_div(tmp, parent_rate);
  66        div = tmp;
  67
  68        if (!div)
  69                return -EINVAL;
  70
  71        return (parent_rate >> frac->width) * div;
  72}
  73
  74static int clk_frac_set_rate(struct clk_hw *hw, unsigned long rate,
  75                             unsigned long parent_rate)
  76{
  77        struct clk_frac *frac = to_clk_frac(hw);
  78        unsigned long flags;
  79        u32 div, val;
  80        u64 tmp;
  81
  82        if (rate > parent_rate)
  83                return -EINVAL;
  84
  85        tmp = rate;
  86        tmp <<= frac->width;
  87        do_div(tmp, parent_rate);
  88        div = tmp;
  89
  90        if (!div)
  91                return -EINVAL;
  92
  93        spin_lock_irqsave(&mxs_lock, flags);
  94
  95        val = readl_relaxed(frac->reg);
  96        val &= ~(((1 << frac->width) - 1) << frac->shift);
  97        val |= div << frac->shift;
  98        writel_relaxed(val, frac->reg);
  99
 100        spin_unlock_irqrestore(&mxs_lock, flags);
 101
 102        return mxs_clk_wait(frac->reg, frac->busy);
 103}
 104
 105static struct clk_ops clk_frac_ops = {
 106        .recalc_rate = clk_frac_recalc_rate,
 107        .round_rate = clk_frac_round_rate,
 108        .set_rate = clk_frac_set_rate,
 109};
 110
 111struct clk *mxs_clk_frac(const char *name, const char *parent_name,
 112                         void __iomem *reg, u8 shift, u8 width, u8 busy)
 113{
 114        struct clk_frac *frac;
 115        struct clk *clk;
 116        struct clk_init_data init;
 117
 118        frac = kzalloc(sizeof(*frac), GFP_KERNEL);
 119        if (!frac)
 120                return ERR_PTR(-ENOMEM);
 121
 122        init.name = name;
 123        init.ops = &clk_frac_ops;
 124        init.flags = CLK_SET_RATE_PARENT;
 125        init.parent_names = (parent_name ? &parent_name: NULL);
 126        init.num_parents = (parent_name ? 1 : 0);
 127
 128        frac->reg = reg;
 129        frac->shift = shift;
 130        frac->width = width;
 131        frac->busy = busy;
 132        frac->hw.init = &init;
 133
 134        clk = clk_register(NULL, &frac->hw);
 135        if (IS_ERR(clk))
 136                kfree(frac);
 137
 138        return clk;
 139}
 140