linux/drivers/clk/tegra/clk-divider.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * Copyright (c) 2012, NVIDIA CORPORATION.  All rights reserved.
   4 */
   5
   6#include <linux/kernel.h>
   7#include <linux/io.h>
   8#include <linux/err.h>
   9#include <linux/slab.h>
  10#include <linux/clk-provider.h>
  11
  12#include "clk.h"
  13
  14#define pll_out_override(p) (BIT((p->shift - 6)))
  15#define div_mask(d) ((1 << (d->width)) - 1)
  16#define get_mul(d) (1 << d->frac_width)
  17#define get_max_div(d) div_mask(d)
  18
  19#define PERIPH_CLK_UART_DIV_ENB BIT(24)
  20
  21static int get_div(struct tegra_clk_frac_div *divider, unsigned long rate,
  22                   unsigned long parent_rate)
  23{
  24        int div;
  25
  26        div = div_frac_get(rate, parent_rate, divider->width,
  27                           divider->frac_width, divider->flags);
  28
  29        if (div < 0)
  30                return 0;
  31
  32        return div;
  33}
  34
  35static unsigned long clk_frac_div_recalc_rate(struct clk_hw *hw,
  36                                             unsigned long parent_rate)
  37{
  38        struct tegra_clk_frac_div *divider = to_clk_frac_div(hw);
  39        u32 reg;
  40        int div, mul;
  41        u64 rate = parent_rate;
  42
  43        reg = readl_relaxed(divider->reg);
  44
  45        if ((divider->flags & TEGRA_DIVIDER_UART) &&
  46            !(reg & PERIPH_CLK_UART_DIV_ENB))
  47                return rate;
  48
  49        div = (reg >> divider->shift) & div_mask(divider);
  50
  51        mul = get_mul(divider);
  52        div += mul;
  53
  54        rate *= mul;
  55        rate += div - 1;
  56        do_div(rate, div);
  57
  58        return rate;
  59}
  60
  61static long clk_frac_div_round_rate(struct clk_hw *hw, unsigned long rate,
  62                                   unsigned long *prate)
  63{
  64        struct tegra_clk_frac_div *divider = to_clk_frac_div(hw);
  65        int div, mul;
  66        unsigned long output_rate = *prate;
  67
  68        if (!rate)
  69                return output_rate;
  70
  71        div = get_div(divider, rate, output_rate);
  72        if (div < 0)
  73                return *prate;
  74
  75        mul = get_mul(divider);
  76
  77        return DIV_ROUND_UP(output_rate * mul, div + mul);
  78}
  79
  80static int clk_frac_div_set_rate(struct clk_hw *hw, unsigned long rate,
  81                                unsigned long parent_rate)
  82{
  83        struct tegra_clk_frac_div *divider = to_clk_frac_div(hw);
  84        int div;
  85        unsigned long flags = 0;
  86        u32 val;
  87
  88        div = get_div(divider, rate, parent_rate);
  89        if (div < 0)
  90                return div;
  91
  92        if (divider->lock)
  93                spin_lock_irqsave(divider->lock, flags);
  94
  95        val = readl_relaxed(divider->reg);
  96        val &= ~(div_mask(divider) << divider->shift);
  97        val |= div << divider->shift;
  98
  99        if (divider->flags & TEGRA_DIVIDER_UART) {
 100                if (div)
 101                        val |= PERIPH_CLK_UART_DIV_ENB;
 102                else
 103                        val &= ~PERIPH_CLK_UART_DIV_ENB;
 104        }
 105
 106        if (divider->flags & TEGRA_DIVIDER_FIXED)
 107                val |= pll_out_override(divider);
 108
 109        writel_relaxed(val, divider->reg);
 110
 111        if (divider->lock)
 112                spin_unlock_irqrestore(divider->lock, flags);
 113
 114        return 0;
 115}
 116
 117static void clk_divider_restore_context(struct clk_hw *hw)
 118{
 119        struct clk_hw *parent = clk_hw_get_parent(hw);
 120        unsigned long parent_rate = clk_hw_get_rate(parent);
 121        unsigned long rate = clk_hw_get_rate(hw);
 122
 123        if (clk_frac_div_set_rate(hw, rate, parent_rate) < 0)
 124                WARN_ON(1);
 125}
 126
 127const struct clk_ops tegra_clk_frac_div_ops = {
 128        .recalc_rate = clk_frac_div_recalc_rate,
 129        .set_rate = clk_frac_div_set_rate,
 130        .round_rate = clk_frac_div_round_rate,
 131        .restore_context = clk_divider_restore_context,
 132};
 133
 134struct clk *tegra_clk_register_divider(const char *name,
 135                const char *parent_name, void __iomem *reg,
 136                unsigned long flags, u8 clk_divider_flags, u8 shift, u8 width,
 137                u8 frac_width, spinlock_t *lock)
 138{
 139        struct tegra_clk_frac_div *divider;
 140        struct clk *clk;
 141        struct clk_init_data init;
 142
 143        divider = kzalloc(sizeof(*divider), GFP_KERNEL);
 144        if (!divider) {
 145                pr_err("%s: could not allocate fractional divider clk\n",
 146                       __func__);
 147                return ERR_PTR(-ENOMEM);
 148        }
 149
 150        init.name = name;
 151        init.ops = &tegra_clk_frac_div_ops;
 152        init.flags = flags;
 153        init.parent_names = parent_name ? &parent_name : NULL;
 154        init.num_parents = parent_name ? 1 : 0;
 155
 156        divider->reg = reg;
 157        divider->shift = shift;
 158        divider->width = width;
 159        divider->frac_width = frac_width;
 160        divider->lock = lock;
 161        divider->flags = clk_divider_flags;
 162
 163        /* Data in .init is copied by clk_register(), so stack variable OK */
 164        divider->hw.init = &init;
 165
 166        clk = clk_register(NULL, &divider->hw);
 167        if (IS_ERR(clk))
 168                kfree(divider);
 169
 170        return clk;
 171}
 172
 173static const struct clk_div_table mc_div_table[] = {
 174        { .val = 0, .div = 2 },
 175        { .val = 1, .div = 1 },
 176        { .val = 0, .div = 0 },
 177};
 178
 179struct clk *tegra_clk_register_mc(const char *name, const char *parent_name,
 180                                  void __iomem *reg, spinlock_t *lock)
 181{
 182        return clk_register_divider_table(NULL, name, parent_name,
 183                                          CLK_IS_CRITICAL,
 184                                          reg, 16, 1, CLK_DIVIDER_READ_ONLY,
 185                                          mc_div_table, lock);
 186}
 187