xref: /openbmc/linux/drivers/clk/sunxi-ng/ccu_sdm.c (revision 94d964e5)
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 
12 bool 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(common->base + common->reg) & sdm->enable))
19 		return false;
20 
21 	return !!(readl(common->base + sdm->tuning_reg) & sdm->tuning_enable);
22 }
23 EXPORT_SYMBOL_NS_GPL(ccu_sdm_helper_is_enabled, SUNXI_CCU);
24 
25 void 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(sdm->table[i].pattern,
40 			       common->base + sdm->tuning_reg);
41 
42 	/* Make sure SDM is enabled */
43 	spin_lock_irqsave(common->lock, flags);
44 	reg = readl(common->base + sdm->tuning_reg);
45 	writel(reg | sdm->tuning_enable, common->base + sdm->tuning_reg);
46 	spin_unlock_irqrestore(common->lock, flags);
47 
48 	spin_lock_irqsave(common->lock, flags);
49 	reg = readl(common->base + common->reg);
50 	writel(reg | sdm->enable, common->base + common->reg);
51 	spin_unlock_irqrestore(common->lock, flags);
52 }
53 EXPORT_SYMBOL_NS_GPL(ccu_sdm_helper_enable, SUNXI_CCU);
54 
55 void 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(common->base + common->reg);
66 	writel(reg & ~sdm->enable, common->base + common->reg);
67 	spin_unlock_irqrestore(common->lock, flags);
68 
69 	spin_lock_irqsave(common->lock, flags);
70 	reg = readl(common->base + sdm->tuning_reg);
71 	writel(reg & ~sdm->tuning_enable, common->base + sdm->tuning_reg);
72 	spin_unlock_irqrestore(common->lock, flags);
73 }
74 EXPORT_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  */
93 bool 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 }
108 EXPORT_SYMBOL_NS_GPL(ccu_sdm_helper_has_rate, SUNXI_CCU);
109 
110 unsigned 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(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 }
139 EXPORT_SYMBOL_NS_GPL(ccu_sdm_helper_read_rate, SUNXI_CCU);
140 
141 int 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 }
161 EXPORT_SYMBOL_NS_GPL(ccu_sdm_helper_get_factors, SUNXI_CCU);
162