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);
125