1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Copyright (C) 2017 Chen-Yu Tsai <wens@csie.org>
4 */
5
6#include <linux/clk-provider.h>
7#include <linux/io.h>
8#include <linux/spinlock.h>
9
10#include "ccu_sdm.h"
11
12bool ccu_sdm_helper_is_enabled(struct ccu_common *common,
13 struct ccu_sdm_internal *sdm)
14{
15 if (!(common->features & CCU_FEATURE_SIGMA_DELTA_MOD))
16 return false;
17
18 if (sdm->enable && !(readl(addr: common->base + common->reg) & sdm->enable))
19 return false;
20
21 return !!(readl(addr: common->base + sdm->tuning_reg) & sdm->tuning_enable);
22}
23EXPORT_SYMBOL_NS_GPL(ccu_sdm_helper_is_enabled, SUNXI_CCU);
24
25void ccu_sdm_helper_enable(struct ccu_common *common,
26 struct ccu_sdm_internal *sdm,
27 unsigned long rate)
28{
29 unsigned long flags;
30 unsigned int i;
31 u32 reg;
32
33 if (!(common->features & CCU_FEATURE_SIGMA_DELTA_MOD))
34 return;
35
36 /* Set the pattern */
37 for (i = 0; i < sdm->table_size; i++)
38 if (sdm->table[i].rate == rate)
39 writel(val: sdm->table[i].pattern,
40 addr: common->base + sdm->tuning_reg);
41
42 /* Make sure SDM is enabled */
43 spin_lock_irqsave(common->lock, flags);
44 reg = readl(addr: common->base + sdm->tuning_reg);
45 writel(val: reg | sdm->tuning_enable, addr: common->base + sdm->tuning_reg);
46 spin_unlock_irqrestore(lock: common->lock, flags);
47
48 spin_lock_irqsave(common->lock, flags);
49 reg = readl(addr: common->base + common->reg);
50 writel(val: reg | sdm->enable, addr: common->base + common->reg);
51 spin_unlock_irqrestore(lock: common->lock, flags);
52}
53EXPORT_SYMBOL_NS_GPL(ccu_sdm_helper_enable, SUNXI_CCU);
54
55void ccu_sdm_helper_disable(struct ccu_common *common,
56 struct ccu_sdm_internal *sdm)
57{
58 unsigned long flags;
59 u32 reg;
60
61 if (!(common->features & CCU_FEATURE_SIGMA_DELTA_MOD))
62 return;
63
64 spin_lock_irqsave(common->lock, flags);
65 reg = readl(addr: common->base + common->reg);
66 writel(val: reg & ~sdm->enable, addr: common->base + common->reg);
67 spin_unlock_irqrestore(lock: common->lock, flags);
68
69 spin_lock_irqsave(common->lock, flags);
70 reg = readl(addr: common->base + sdm->tuning_reg);
71 writel(val: reg & ~sdm->tuning_enable, addr: common->base + sdm->tuning_reg);
72 spin_unlock_irqrestore(lock: common->lock, flags);
73}
74EXPORT_SYMBOL_NS_GPL(ccu_sdm_helper_disable, SUNXI_CCU);
75
76/*
77 * Sigma delta modulation provides a way to do fractional-N frequency
78 * synthesis, in essence allowing the PLL to output any frequency
79 * within its operational range. On earlier SoCs such as the A10/A20,
80 * some PLLs support this. On later SoCs, all PLLs support this.
81 *
82 * The datasheets do not explain what the "wave top" and "wave bottom"
83 * parameters mean or do, nor how to calculate the effective output
84 * frequency. The only examples (and real world usage) are for the audio
85 * PLL to generate 24.576 and 22.5792 MHz clock rates used by the audio
86 * peripherals. The author lacks the underlying domain knowledge to
87 * pursue this.
88 *
89 * The goal and function of the following code is to support the two
90 * clock rates used by the audio subsystem, allowing for proper audio
91 * playback and capture without any pitch or speed changes.
92 */
93bool ccu_sdm_helper_has_rate(struct ccu_common *common,
94 struct ccu_sdm_internal *sdm,
95 unsigned long rate)
96{
97 unsigned int i;
98
99 if (!(common->features & CCU_FEATURE_SIGMA_DELTA_MOD))
100 return false;
101
102 for (i = 0; i < sdm->table_size; i++)
103 if (sdm->table[i].rate == rate)
104 return true;
105
106 return false;
107}
108EXPORT_SYMBOL_NS_GPL(ccu_sdm_helper_has_rate, SUNXI_CCU);
109
110unsigned long ccu_sdm_helper_read_rate(struct ccu_common *common,
111 struct ccu_sdm_internal *sdm,
112 u32 m, u32 n)
113{
114 unsigned int i;
115 u32 reg;
116
117 pr_debug("%s: Read sigma-delta modulation setting\n",
118 clk_hw_get_name(&common->hw));
119
120 if (!(common->features & CCU_FEATURE_SIGMA_DELTA_MOD))
121 return 0;
122
123 pr_debug("%s: clock is sigma-delta modulated\n",
124 clk_hw_get_name(&common->hw));
125
126 reg = readl(addr: common->base + sdm->tuning_reg);
127
128 pr_debug("%s: pattern reg is 0x%x",
129 clk_hw_get_name(&common->hw), reg);
130
131 for (i = 0; i < sdm->table_size; i++)
132 if (sdm->table[i].pattern == reg &&
133 sdm->table[i].m == m && sdm->table[i].n == n)
134 return sdm->table[i].rate;
135
136 /* We can't calculate the effective clock rate, so just fail. */
137 return 0;
138}
139EXPORT_SYMBOL_NS_GPL(ccu_sdm_helper_read_rate, SUNXI_CCU);
140
141int ccu_sdm_helper_get_factors(struct ccu_common *common,
142 struct ccu_sdm_internal *sdm,
143 unsigned long rate,
144 unsigned long *m, unsigned long *n)
145{
146 unsigned int i;
147
148 if (!(common->features & CCU_FEATURE_SIGMA_DELTA_MOD))
149 return -EINVAL;
150
151 for (i = 0; i < sdm->table_size; i++)
152 if (sdm->table[i].rate == rate) {
153 *m = sdm->table[i].m;
154 *n = sdm->table[i].n;
155 return 0;
156 }
157
158 /* nothing found */
159 return -EINVAL;
160}
161EXPORT_SYMBOL_NS_GPL(ccu_sdm_helper_get_factors, SUNXI_CCU);
162

source code of linux/drivers/clk/sunxi-ng/ccu_sdm.c