linux/drivers/clk/tegra/clk-divider.c
<<
>>
Prefs
   1/*
   2 * Copyright (c) 2012, NVIDIA CORPORATION.  All rights reserved.
   3 *
   4 * This program is free software; you can redistribute it and/or modify it
   5 * under the terms and conditions of the GNU General Public License,
   6 * version 2, as published by the Free Software Foundation.
   7 *
   8 * This program is distributed in the hope it will be useful, but WITHOUT
   9 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  10 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
  11 * more details.
  12 *
  13 * You should have received a copy of the GNU General Public License
  14 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  15 */
  16
  17#include <linux/kernel.h>
  18#include <linux/io.h>
  19#include <linux/err.h>
  20#include <linux/slab.h>
  21#include <linux/clk-provider.h>
  22
  23#include "clk.h"
  24
  25#define pll_out_override(p) (BIT((p->shift - 6)))
  26#define div_mask(d) ((1 << (d->width)) - 1)
  27#define get_mul(d) (1 << d->frac_width)
  28#define get_max_div(d) div_mask(d)
  29
  30#define PERIPH_CLK_UART_DIV_ENB BIT(24)
  31
  32static int get_div(struct tegra_clk_frac_div *divider, unsigned long rate,
  33                   unsigned long parent_rate)
  34{
  35        u64 divider_ux1 = parent_rate;
  36        u8 flags = divider->flags;
  37        int mul;
  38
  39        if (!rate)
  40                return 0;
  41
  42        mul = get_mul(divider);
  43
  44        if (!(flags & TEGRA_DIVIDER_INT))
  45                divider_ux1 *= mul;
  46
  47        if (flags & TEGRA_DIVIDER_ROUND_UP)
  48                divider_ux1 += rate - 1;
  49
  50        do_div(divider_ux1, rate);
  51
  52        if (flags & TEGRA_DIVIDER_INT)
  53                divider_ux1 *= mul;
  54
  55        divider_ux1 -= mul;
  56
  57        if ((s64)divider_ux1 < 0)
  58                return 0;
  59
  60        if (divider_ux1 > get_max_div(divider))
  61                return get_max_div(divider);
  62
  63        return divider_ux1;
  64}
  65
  66static unsigned long clk_frac_div_recalc_rate(struct clk_hw *hw,
  67                                             unsigned long parent_rate)
  68{
  69        struct tegra_clk_frac_div *divider = to_clk_frac_div(hw);
  70        u32 reg;
  71        int div, mul;
  72        u64 rate = parent_rate;
  73
  74        reg = readl_relaxed(divider->reg) >> divider->shift;
  75        div = reg & div_mask(divider);
  76
  77        mul = get_mul(divider);
  78        div += mul;
  79
  80        rate *= mul;
  81        rate += div - 1;
  82        do_div(rate, div);
  83
  84        return rate;
  85}
  86
  87static long clk_frac_div_round_rate(struct clk_hw *hw, unsigned long rate,
  88                                   unsigned long *prate)
  89{
  90        struct tegra_clk_frac_div *divider = to_clk_frac_div(hw);
  91        int div, mul;
  92        unsigned long output_rate = *prate;
  93
  94        if (!rate)
  95                return output_rate;
  96
  97        div = get_div(divider, rate, output_rate);
  98        if (div < 0)
  99                return *prate;
 100
 101        mul = get_mul(divider);
 102
 103        return DIV_ROUND_UP(output_rate * mul, div + mul);
 104}
 105
 106static int clk_frac_div_set_rate(struct clk_hw *hw, unsigned long rate,
 107                                unsigned long parent_rate)
 108{
 109        struct tegra_clk_frac_div *divider = to_clk_frac_div(hw);
 110        int div;
 111        unsigned long flags = 0;
 112        u32 val;
 113
 114        div = get_div(divider, rate, parent_rate);
 115        if (div < 0)
 116                return div;
 117
 118        if (divider->lock)
 119                spin_lock_irqsave(divider->lock, flags);
 120
 121        val = readl_relaxed(divider->reg);
 122        val &= ~(div_mask(divider) << divider->shift);
 123        val |= div << divider->shift;
 124
 125        if (divider->flags & TEGRA_DIVIDER_UART) {
 126                if (div)
 127                        val |= PERIPH_CLK_UART_DIV_ENB;
 128                else
 129                        val &= ~PERIPH_CLK_UART_DIV_ENB;
 130        }
 131
 132        if (divider->flags & TEGRA_DIVIDER_FIXED)
 133                val |= pll_out_override(divider);
 134
 135        writel_relaxed(val, divider->reg);
 136
 137        if (divider->lock)
 138                spin_unlock_irqrestore(divider->lock, flags);
 139
 140        return 0;
 141}
 142
 143const struct clk_ops tegra_clk_frac_div_ops = {
 144        .recalc_rate = clk_frac_div_recalc_rate,
 145        .set_rate = clk_frac_div_set_rate,
 146        .round_rate = clk_frac_div_round_rate,
 147};
 148
 149struct clk *tegra_clk_register_divider(const char *name,
 150                const char *parent_name, void __iomem *reg,
 151                unsigned long flags, u8 clk_divider_flags, u8 shift, u8 width,
 152                u8 frac_width, spinlock_t *lock)
 153{
 154        struct tegra_clk_frac_div *divider;
 155        struct clk *clk;
 156        struct clk_init_data init;
 157
 158        divider = kzalloc(sizeof(*divider), GFP_KERNEL);
 159        if (!divider) {
 160                pr_err("%s: could not allocate fractional divider clk\n",
 161                       __func__);
 162                return ERR_PTR(-ENOMEM);
 163        }
 164
 165        init.name = name;
 166        init.ops = &tegra_clk_frac_div_ops;
 167        init.flags = flags;
 168        init.parent_names = parent_name ? &parent_name : NULL;
 169        init.num_parents = parent_name ? 1 : 0;
 170
 171        divider->reg = reg;
 172        divider->shift = shift;
 173        divider->width = width;
 174        divider->frac_width = frac_width;
 175        divider->lock = lock;
 176        divider->flags = clk_divider_flags;
 177
 178        /* Data in .init is copied by clk_register(), so stack variable OK */
 179        divider->hw.init = &init;
 180
 181        clk = clk_register(NULL, &divider->hw);
 182        if (IS_ERR(clk))
 183                kfree(divider);
 184
 185        return clk;
 186}
 187
 188static const struct clk_div_table mc_div_table[] = {
 189        { .val = 0, .div = 2 },
 190        { .val = 1, .div = 1 },
 191        { .val = 0, .div = 0 },
 192};
 193
 194struct clk *tegra_clk_register_mc(const char *name, const char *parent_name,
 195                                  void __iomem *reg, spinlock_t *lock)
 196{
 197        return clk_register_divider_table(NULL, name, parent_name, 0, reg,
 198                                          16, 1, 0, mc_div_table, lock);
 199}
 200