File Index
Files and directories
Sign in
FILE

ccu_phase.c

File preview with syntax highlighting.

linux-6.19.14/drivers/clk/sunxi-ng/ccu_phase.c C
  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");
~/ Read-only file preview.