cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

ccu_phase.c (3148B)


      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#include <linux/spinlock.h>
     10
     11#include "ccu_phase.h"
     12
     13static int ccu_phase_get_phase(struct clk_hw *hw)
     14{
     15	struct ccu_phase *phase = hw_to_ccu_phase(hw);
     16	struct clk_hw *parent, *grandparent;
     17	unsigned int parent_rate, grandparent_rate;
     18	u16 step, parent_div;
     19	u32 reg;
     20	u8 delay;
     21
     22	reg = readl(phase->common.base + phase->common.reg);
     23	delay = (reg >> phase->shift);
     24	delay &= (1 << phase->width) - 1;
     25
     26	if (!delay)
     27		return 180;
     28
     29	/* Get our parent clock, it's the one that can adjust its rate */
     30	parent = clk_hw_get_parent(hw);
     31	if (!parent)
     32		return -EINVAL;
     33
     34	/* And its rate */
     35	parent_rate = clk_hw_get_rate(parent);
     36	if (!parent_rate)
     37		return -EINVAL;
     38
     39	/* Now, get our parent's parent (most likely some PLL) */
     40	grandparent = clk_hw_get_parent(parent);
     41	if (!grandparent)
     42		return -EINVAL;
     43
     44	/* And its rate */
     45	grandparent_rate = clk_hw_get_rate(grandparent);
     46	if (!grandparent_rate)
     47		return -EINVAL;
     48
     49	/* Get our parent clock divider */
     50	parent_div = grandparent_rate / parent_rate;
     51
     52	step = DIV_ROUND_CLOSEST(360, parent_div);
     53	return delay * step;
     54}
     55
     56static int ccu_phase_set_phase(struct clk_hw *hw, int degrees)
     57{
     58	struct ccu_phase *phase = hw_to_ccu_phase(hw);
     59	struct clk_hw *parent, *grandparent;
     60	unsigned int parent_rate, grandparent_rate;
     61	unsigned long flags;
     62	u32 reg;
     63	u8 delay;
     64
     65	/* Get our parent clock, it's the one that can adjust its rate */
     66	parent = clk_hw_get_parent(hw);
     67	if (!parent)
     68		return -EINVAL;
     69
     70	/* And its rate */
     71	parent_rate = clk_hw_get_rate(parent);
     72	if (!parent_rate)
     73		return -EINVAL;
     74
     75	/* Now, get our parent's parent (most likely some PLL) */
     76	grandparent = clk_hw_get_parent(parent);
     77	if (!grandparent)
     78		return -EINVAL;
     79
     80	/* And its rate */
     81	grandparent_rate = clk_hw_get_rate(grandparent);
     82	if (!grandparent_rate)
     83		return -EINVAL;
     84
     85	if (degrees != 180) {
     86		u16 step, parent_div;
     87
     88		/* Get our parent divider */
     89		parent_div = grandparent_rate / parent_rate;
     90
     91		/*
     92		 * We can only outphase the clocks by multiple of the
     93		 * PLL's period.
     94		 *
     95		 * Since our parent clock is only a divider, and the
     96		 * formula to get the outphasing in degrees is deg =
     97		 * 360 * delta / period
     98		 *
     99		 * If we simplify this formula, we can see that the
    100		 * only thing that we're concerned about is the number
    101		 * of period we want to outphase our clock from, and
    102		 * the divider set by our parent clock.
    103		 */
    104		step = DIV_ROUND_CLOSEST(360, parent_div);
    105		delay = DIV_ROUND_CLOSEST(degrees, step);
    106	} else {
    107		delay = 0;
    108	}
    109
    110	spin_lock_irqsave(phase->common.lock, flags);
    111	reg = readl(phase->common.base + phase->common.reg);
    112	reg &= ~GENMASK(phase->width + phase->shift - 1, phase->shift);
    113	writel(reg | (delay << phase->shift),
    114	       phase->common.base + phase->common.reg);
    115	spin_unlock_irqrestore(phase->common.lock, flags);
    116
    117	return 0;
    118}
    119
    120const struct clk_ops ccu_phase_ops = {
    121	.get_phase	= ccu_phase_get_phase,
    122	.set_phase	= ccu_phase_set_phase,
    123};
    124EXPORT_SYMBOL_NS_GPL(ccu_phase_ops, SUNXI_CCU);