linux/drivers/clk/sunxi-ng/ccu_mult.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 * Copyright (C) 2016 Maxime Ripard
   4 * Maxime Ripard <maxime.ripard@free-electrons.com>
   5 */
   6
   7#include <linux/clk-provider.h>
   8#include <linux/io.h>
   9
  10#include "ccu_gate.h"
  11#include "ccu_mult.h"
  12
  13struct _ccu_mult {
  14        unsigned long   mult, min, max;
  15};
  16
  17static void ccu_mult_find_best(unsigned long parent, unsigned long rate,
  18                               struct _ccu_mult *mult)
  19{
  20        int _mult;
  21
  22        _mult = rate / parent;
  23        if (_mult < mult->min)
  24                _mult = mult->min;
  25
  26        if (_mult > mult->max)
  27                _mult = mult->max;
  28
  29        mult->mult = _mult;
  30}
  31
  32static unsigned long ccu_mult_round_rate(struct ccu_mux_internal *mux,
  33                                         struct clk_hw *parent,
  34                                         unsigned long *parent_rate,
  35                                         unsigned long rate,
  36                                         void *data)
  37{
  38        struct ccu_mult *cm = data;
  39        struct _ccu_mult _cm;
  40
  41        _cm.min = cm->mult.min;
  42
  43        if (cm->mult.max)
  44                _cm.max = cm->mult.max;
  45        else
  46                _cm.max = (1 << cm->mult.width) + cm->mult.offset - 1;
  47
  48        ccu_mult_find_best(*parent_rate, rate, &_cm);
  49
  50        return *parent_rate * _cm.mult;
  51}
  52
  53static void ccu_mult_disable(struct clk_hw *hw)
  54{
  55        struct ccu_mult *cm = hw_to_ccu_mult(hw);
  56
  57        return ccu_gate_helper_disable(&cm->common, cm->enable);
  58}
  59
  60static int ccu_mult_enable(struct clk_hw *hw)
  61{
  62        struct ccu_mult *cm = hw_to_ccu_mult(hw);
  63
  64        return ccu_gate_helper_enable(&cm->common, cm->enable);
  65}
  66
  67static int ccu_mult_is_enabled(struct clk_hw *hw)
  68{
  69        struct ccu_mult *cm = hw_to_ccu_mult(hw);
  70
  71        return ccu_gate_helper_is_enabled(&cm->common, cm->enable);
  72}
  73
  74static unsigned long ccu_mult_recalc_rate(struct clk_hw *hw,
  75                                        unsigned long parent_rate)
  76{
  77        struct ccu_mult *cm = hw_to_ccu_mult(hw);
  78        unsigned long val;
  79        u32 reg;
  80
  81        if (ccu_frac_helper_is_enabled(&cm->common, &cm->frac))
  82                return ccu_frac_helper_read_rate(&cm->common, &cm->frac);
  83
  84        reg = readl(cm->common.base + cm->common.reg);
  85        val = reg >> cm->mult.shift;
  86        val &= (1 << cm->mult.width) - 1;
  87
  88        parent_rate = ccu_mux_helper_apply_prediv(&cm->common, &cm->mux, -1,
  89                                                  parent_rate);
  90
  91        return parent_rate * (val + cm->mult.offset);
  92}
  93
  94static int ccu_mult_determine_rate(struct clk_hw *hw,
  95                                struct clk_rate_request *req)
  96{
  97        struct ccu_mult *cm = hw_to_ccu_mult(hw);
  98
  99        return ccu_mux_helper_determine_rate(&cm->common, &cm->mux,
 100                                             req, ccu_mult_round_rate, cm);
 101}
 102
 103static int ccu_mult_set_rate(struct clk_hw *hw, unsigned long rate,
 104                           unsigned long parent_rate)
 105{
 106        struct ccu_mult *cm = hw_to_ccu_mult(hw);
 107        struct _ccu_mult _cm;
 108        unsigned long flags;
 109        u32 reg;
 110
 111        if (ccu_frac_helper_has_rate(&cm->common, &cm->frac, rate)) {
 112                ccu_frac_helper_enable(&cm->common, &cm->frac);
 113
 114                return ccu_frac_helper_set_rate(&cm->common, &cm->frac,
 115                                                rate, cm->lock);
 116        } else {
 117                ccu_frac_helper_disable(&cm->common, &cm->frac);
 118        }
 119
 120        parent_rate = ccu_mux_helper_apply_prediv(&cm->common, &cm->mux, -1,
 121                                                  parent_rate);
 122
 123        _cm.min = cm->mult.min;
 124
 125        if (cm->mult.max)
 126                _cm.max = cm->mult.max;
 127        else
 128                _cm.max = (1 << cm->mult.width) + cm->mult.offset - 1;
 129
 130        ccu_mult_find_best(parent_rate, rate, &_cm);
 131
 132        spin_lock_irqsave(cm->common.lock, flags);
 133
 134        reg = readl(cm->common.base + cm->common.reg);
 135        reg &= ~GENMASK(cm->mult.width + cm->mult.shift - 1, cm->mult.shift);
 136        reg |= ((_cm.mult - cm->mult.offset) << cm->mult.shift);
 137
 138        writel(reg, cm->common.base + cm->common.reg);
 139
 140        spin_unlock_irqrestore(cm->common.lock, flags);
 141
 142        ccu_helper_wait_for_lock(&cm->common, cm->lock);
 143
 144        return 0;
 145}
 146
 147static u8 ccu_mult_get_parent(struct clk_hw *hw)
 148{
 149        struct ccu_mult *cm = hw_to_ccu_mult(hw);
 150
 151        return ccu_mux_helper_get_parent(&cm->common, &cm->mux);
 152}
 153
 154static int ccu_mult_set_parent(struct clk_hw *hw, u8 index)
 155{
 156        struct ccu_mult *cm = hw_to_ccu_mult(hw);
 157
 158        return ccu_mux_helper_set_parent(&cm->common, &cm->mux, index);
 159}
 160
 161const struct clk_ops ccu_mult_ops = {
 162        .disable        = ccu_mult_disable,
 163        .enable         = ccu_mult_enable,
 164        .is_enabled     = ccu_mult_is_enabled,
 165
 166        .get_parent     = ccu_mult_get_parent,
 167        .set_parent     = ccu_mult_set_parent,
 168
 169        .determine_rate = ccu_mult_determine_rate,
 170        .recalc_rate    = ccu_mult_recalc_rate,
 171        .set_rate       = ccu_mult_set_rate,
 172};
 173