Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-or-later
0002 /*
0003  * Copyright (C) 2016 Maxime Ripard
0004  * Maxime Ripard <maxime.ripard@free-electrons.com>
0005  */
0006 
0007 #include <linux/clk-provider.h>
0008 #include <linux/io.h>
0009 #include <linux/spinlock.h>
0010 
0011 #include "ccu_phase.h"
0012 
0013 static int ccu_phase_get_phase(struct clk_hw *hw)
0014 {
0015     struct ccu_phase *phase = hw_to_ccu_phase(hw);
0016     struct clk_hw *parent, *grandparent;
0017     unsigned int parent_rate, grandparent_rate;
0018     u16 step, parent_div;
0019     u32 reg;
0020     u8 delay;
0021 
0022     reg = readl(phase->common.base + phase->common.reg);
0023     delay = (reg >> phase->shift);
0024     delay &= (1 << phase->width) - 1;
0025 
0026     if (!delay)
0027         return 180;
0028 
0029     /* Get our parent clock, it's the one that can adjust its rate */
0030     parent = clk_hw_get_parent(hw);
0031     if (!parent)
0032         return -EINVAL;
0033 
0034     /* And its rate */
0035     parent_rate = clk_hw_get_rate(parent);
0036     if (!parent_rate)
0037         return -EINVAL;
0038 
0039     /* Now, get our parent's parent (most likely some PLL) */
0040     grandparent = clk_hw_get_parent(parent);
0041     if (!grandparent)
0042         return -EINVAL;
0043 
0044     /* And its rate */
0045     grandparent_rate = clk_hw_get_rate(grandparent);
0046     if (!grandparent_rate)
0047         return -EINVAL;
0048 
0049     /* Get our parent clock divider */
0050     parent_div = grandparent_rate / parent_rate;
0051 
0052     step = DIV_ROUND_CLOSEST(360, parent_div);
0053     return delay * step;
0054 }
0055 
0056 static int ccu_phase_set_phase(struct clk_hw *hw, int degrees)
0057 {
0058     struct ccu_phase *phase = hw_to_ccu_phase(hw);
0059     struct clk_hw *parent, *grandparent;
0060     unsigned int parent_rate, grandparent_rate;
0061     unsigned long flags;
0062     u32 reg;
0063     u8 delay;
0064 
0065     /* Get our parent clock, it's the one that can adjust its rate */
0066     parent = clk_hw_get_parent(hw);
0067     if (!parent)
0068         return -EINVAL;
0069 
0070     /* And its rate */
0071     parent_rate = clk_hw_get_rate(parent);
0072     if (!parent_rate)
0073         return -EINVAL;
0074 
0075     /* Now, get our parent's parent (most likely some PLL) */
0076     grandparent = clk_hw_get_parent(parent);
0077     if (!grandparent)
0078         return -EINVAL;
0079 
0080     /* And its rate */
0081     grandparent_rate = clk_hw_get_rate(grandparent);
0082     if (!grandparent_rate)
0083         return -EINVAL;
0084 
0085     if (degrees != 180) {
0086         u16 step, parent_div;
0087 
0088         /* Get our parent divider */
0089         parent_div = grandparent_rate / parent_rate;
0090 
0091         /*
0092          * We can only outphase the clocks by multiple of the
0093          * PLL's period.
0094          *
0095          * Since our parent clock is only a divider, and the
0096          * formula to get the outphasing in degrees is deg =
0097          * 360 * delta / period
0098          *
0099          * If we simplify this formula, we can see that the
0100          * only thing that we're concerned about is the number
0101          * of period we want to outphase our clock from, and
0102          * the divider set by our parent clock.
0103          */
0104         step = DIV_ROUND_CLOSEST(360, parent_div);
0105         delay = DIV_ROUND_CLOSEST(degrees, step);
0106     } else {
0107         delay = 0;
0108     }
0109 
0110     spin_lock_irqsave(phase->common.lock, flags);
0111     reg = readl(phase->common.base + phase->common.reg);
0112     reg &= ~GENMASK(phase->width + phase->shift - 1, phase->shift);
0113     writel(reg | (delay << phase->shift),
0114            phase->common.base + phase->common.reg);
0115     spin_unlock_irqrestore(phase->common.lock, flags);
0116 
0117     return 0;
0118 }
0119 
0120 const struct clk_ops ccu_phase_ops = {
0121     .get_phase  = ccu_phase_get_phase,
0122     .set_phase  = ccu_phase_set_phase,
0123 };
0124 EXPORT_SYMBOL_NS_GPL(ccu_phase_ops, SUNXI_CCU);