linux/drivers/clk/spear/clk-aux-synth.c
<<
>>
Prefs
   1/*
   2 * Copyright (C) 2012 ST Microelectronics
   3 * Viresh Kumar <vireshk@kernel.org>
   4 *
   5 * This file is licensed under the terms of the GNU General Public
   6 * License version 2. This program is licensed "as is" without any
   7 * warranty of any kind, whether express or implied.
   8 *
   9 * Auxiliary Synthesizer clock implementation
  10 */
  11
  12#define pr_fmt(fmt) "clk-aux-synth: " fmt
  13
  14#include <linux/clk-provider.h>
  15#include <linux/slab.h>
  16#include <linux/io.h>
  17#include <linux/err.h>
  18#include "clk.h"
  19
  20/*
  21 * DOC: Auxiliary Synthesizer clock
  22 *
  23 * Aux synth gives rate for different values of eq, x and y
  24 *
  25 * Fout from synthesizer can be given from two equations:
  26 * Fout1 = (Fin * X/Y)/2                EQ1
  27 * Fout2 = Fin * X/Y                    EQ2
  28 */
  29
  30#define to_clk_aux(_hw) container_of(_hw, struct clk_aux, hw)
  31
  32static const  struct aux_clk_masks default_aux_masks = {
  33        .eq_sel_mask = AUX_EQ_SEL_MASK,
  34        .eq_sel_shift = AUX_EQ_SEL_SHIFT,
  35        .eq1_mask = AUX_EQ1_SEL,
  36        .eq2_mask = AUX_EQ2_SEL,
  37        .xscale_sel_mask = AUX_XSCALE_MASK,
  38        .xscale_sel_shift = AUX_XSCALE_SHIFT,
  39        .yscale_sel_mask = AUX_YSCALE_MASK,
  40        .yscale_sel_shift = AUX_YSCALE_SHIFT,
  41        .enable_bit = AUX_SYNT_ENB,
  42};
  43
  44static unsigned long aux_calc_rate(struct clk_hw *hw, unsigned long prate,
  45                int index)
  46{
  47        struct clk_aux *aux = to_clk_aux(hw);
  48        struct aux_rate_tbl *rtbl = aux->rtbl;
  49        u8 eq = rtbl[index].eq ? 1 : 2;
  50
  51        return (((prate / 10000) * rtbl[index].xscale) /
  52                        (rtbl[index].yscale * eq)) * 10000;
  53}
  54
  55static long clk_aux_round_rate(struct clk_hw *hw, unsigned long drate,
  56                unsigned long *prate)
  57{
  58        struct clk_aux *aux = to_clk_aux(hw);
  59        int unused;
  60
  61        return clk_round_rate_index(hw, drate, *prate, aux_calc_rate,
  62                        aux->rtbl_cnt, &unused);
  63}
  64
  65static unsigned long clk_aux_recalc_rate(struct clk_hw *hw,
  66                unsigned long parent_rate)
  67{
  68        struct clk_aux *aux = to_clk_aux(hw);
  69        unsigned int num = 1, den = 1, val, eqn;
  70        unsigned long flags = 0;
  71
  72        if (aux->lock)
  73                spin_lock_irqsave(aux->lock, flags);
  74
  75        val = readl_relaxed(aux->reg);
  76
  77        if (aux->lock)
  78                spin_unlock_irqrestore(aux->lock, flags);
  79
  80        eqn = (val >> aux->masks->eq_sel_shift) & aux->masks->eq_sel_mask;
  81        if (eqn == aux->masks->eq1_mask)
  82                den = 2;
  83
  84        /* calculate numerator */
  85        num = (val >> aux->masks->xscale_sel_shift) &
  86                aux->masks->xscale_sel_mask;
  87
  88        /* calculate denominator */
  89        den *= (val >> aux->masks->yscale_sel_shift) &
  90                aux->masks->yscale_sel_mask;
  91
  92        if (!den)
  93                return 0;
  94
  95        return (((parent_rate / 10000) * num) / den) * 10000;
  96}
  97
  98/* Configures new clock rate of aux */
  99static int clk_aux_set_rate(struct clk_hw *hw, unsigned long drate,
 100                                unsigned long prate)
 101{
 102        struct clk_aux *aux = to_clk_aux(hw);
 103        struct aux_rate_tbl *rtbl = aux->rtbl;
 104        unsigned long val, flags = 0;
 105        int i;
 106
 107        clk_round_rate_index(hw, drate, prate, aux_calc_rate, aux->rtbl_cnt,
 108                        &i);
 109
 110        if (aux->lock)
 111                spin_lock_irqsave(aux->lock, flags);
 112
 113        val = readl_relaxed(aux->reg) &
 114                ~(aux->masks->eq_sel_mask << aux->masks->eq_sel_shift);
 115        val |= (rtbl[i].eq & aux->masks->eq_sel_mask) <<
 116                aux->masks->eq_sel_shift;
 117        val &= ~(aux->masks->xscale_sel_mask << aux->masks->xscale_sel_shift);
 118        val |= (rtbl[i].xscale & aux->masks->xscale_sel_mask) <<
 119                aux->masks->xscale_sel_shift;
 120        val &= ~(aux->masks->yscale_sel_mask << aux->masks->yscale_sel_shift);
 121        val |= (rtbl[i].yscale & aux->masks->yscale_sel_mask) <<
 122                aux->masks->yscale_sel_shift;
 123        writel_relaxed(val, aux->reg);
 124
 125        if (aux->lock)
 126                spin_unlock_irqrestore(aux->lock, flags);
 127
 128        return 0;
 129}
 130
 131static const struct clk_ops clk_aux_ops = {
 132        .recalc_rate = clk_aux_recalc_rate,
 133        .round_rate = clk_aux_round_rate,
 134        .set_rate = clk_aux_set_rate,
 135};
 136
 137struct clk *clk_register_aux(const char *aux_name, const char *gate_name,
 138                const char *parent_name, unsigned long flags, void __iomem *reg,
 139                const struct aux_clk_masks *masks, struct aux_rate_tbl *rtbl,
 140                u8 rtbl_cnt, spinlock_t *lock, struct clk **gate_clk)
 141{
 142        struct clk_aux *aux;
 143        struct clk_init_data init;
 144        struct clk *clk;
 145
 146        if (!aux_name || !parent_name || !reg || !rtbl || !rtbl_cnt) {
 147                pr_err("Invalid arguments passed");
 148                return ERR_PTR(-EINVAL);
 149        }
 150
 151        aux = kzalloc(sizeof(*aux), GFP_KERNEL);
 152        if (!aux)
 153                return ERR_PTR(-ENOMEM);
 154
 155        /* struct clk_aux assignments */
 156        if (!masks)
 157                aux->masks = &default_aux_masks;
 158        else
 159                aux->masks = masks;
 160
 161        aux->reg = reg;
 162        aux->rtbl = rtbl;
 163        aux->rtbl_cnt = rtbl_cnt;
 164        aux->lock = lock;
 165        aux->hw.init = &init;
 166
 167        init.name = aux_name;
 168        init.ops = &clk_aux_ops;
 169        init.flags = flags;
 170        init.parent_names = &parent_name;
 171        init.num_parents = 1;
 172
 173        clk = clk_register(NULL, &aux->hw);
 174        if (IS_ERR_OR_NULL(clk))
 175                goto free_aux;
 176
 177        if (gate_name) {
 178                struct clk *tgate_clk;
 179
 180                tgate_clk = clk_register_gate(NULL, gate_name, aux_name,
 181                                CLK_SET_RATE_PARENT, reg,
 182                                aux->masks->enable_bit, 0, lock);
 183                if (IS_ERR_OR_NULL(tgate_clk))
 184                        goto free_aux;
 185
 186                if (gate_clk)
 187                        *gate_clk = tgate_clk;
 188        }
 189
 190        return clk;
 191
 192free_aux:
 193        kfree(aux);
 194        pr_err("clk register failed\n");
 195
 196        return NULL;
 197}
 198