xref: /openbmc/linux/drivers/clk/sunxi-ng/ccu_sdm.c (revision 217188d9)
1 /*
2  * Copyright (C) 2017 Chen-Yu Tsai <wens@csie.org>
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 2 of
7  * the License, or (at your option) any later version.
8  */
9 
10 #include <linux/clk-provider.h>
11 #include <linux/spinlock.h>
12 
13 #include "ccu_sdm.h"
14 
15 bool ccu_sdm_helper_is_enabled(struct ccu_common *common,
16 			       struct ccu_sdm_internal *sdm)
17 {
18 	if (!(common->features & CCU_FEATURE_SIGMA_DELTA_MOD))
19 		return false;
20 
21 	if (sdm->enable && !(readl(common->base + common->reg) & sdm->enable))
22 		return false;
23 
24 	return !!(readl(common->base + sdm->tuning_reg) & sdm->tuning_enable);
25 }
26 
27 void ccu_sdm_helper_enable(struct ccu_common *common,
28 			   struct ccu_sdm_internal *sdm,
29 			   unsigned long rate)
30 {
31 	unsigned long flags;
32 	unsigned int i;
33 	u32 reg;
34 
35 	if (!(common->features & CCU_FEATURE_SIGMA_DELTA_MOD))
36 		return;
37 
38 	/* Set the pattern */
39 	for (i = 0; i < sdm->table_size; i++)
40 		if (sdm->table[i].rate == rate)
41 			writel(sdm->table[i].pattern,
42 			       common->base + sdm->tuning_reg);
43 
44 	/* Make sure SDM is enabled */
45 	spin_lock_irqsave(common->lock, flags);
46 	reg = readl(common->base + sdm->tuning_reg);
47 	writel(reg | sdm->tuning_enable, common->base + sdm->tuning_reg);
48 	spin_unlock_irqrestore(common->lock, flags);
49 
50 	spin_lock_irqsave(common->lock, flags);
51 	reg = readl(common->base + common->reg);
52 	writel(reg | sdm->enable, common->base + common->reg);
53 	spin_unlock_irqrestore(common->lock, flags);
54 }
55 
56 void ccu_sdm_helper_disable(struct ccu_common *common,
57 			    struct ccu_sdm_internal *sdm)
58 {
59 	unsigned long flags;
60 	u32 reg;
61 
62 	if (!(common->features & CCU_FEATURE_SIGMA_DELTA_MOD))
63 		return;
64 
65 	spin_lock_irqsave(common->lock, flags);
66 	reg = readl(common->base + common->reg);
67 	writel(reg & ~sdm->enable, common->base + common->reg);
68 	spin_unlock_irqrestore(common->lock, flags);
69 
70 	spin_lock_irqsave(common->lock, flags);
71 	reg = readl(common->base + sdm->tuning_reg);
72 	writel(reg & ~sdm->tuning_enable, common->base + sdm->tuning_reg);
73 	spin_unlock_irqrestore(common->lock, flags);
74 }
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 
109 unsigned long ccu_sdm_helper_read_rate(struct ccu_common *common,
110 				       struct ccu_sdm_internal *sdm,
111 				       u32 m, u32 n)
112 {
113 	unsigned int i;
114 	u32 reg;
115 
116 	pr_debug("%s: Read sigma-delta modulation setting\n",
117 		 clk_hw_get_name(&common->hw));
118 
119 	if (!(common->features & CCU_FEATURE_SIGMA_DELTA_MOD))
120 		return 0;
121 
122 	pr_debug("%s: clock is sigma-delta modulated\n",
123 		 clk_hw_get_name(&common->hw));
124 
125 	reg = readl(common->base + sdm->tuning_reg);
126 
127 	pr_debug("%s: pattern reg is 0x%x",
128 		 clk_hw_get_name(&common->hw), reg);
129 
130 	for (i = 0; i < sdm->table_size; i++)
131 		if (sdm->table[i].pattern == reg &&
132 		    sdm->table[i].m == m && sdm->table[i].n == n)
133 			return sdm->table[i].rate;
134 
135 	/* We can't calculate the effective clock rate, so just fail. */
136 	return 0;
137 }
138 
139 int ccu_sdm_helper_get_factors(struct ccu_common *common,
140 			       struct ccu_sdm_internal *sdm,
141 			       unsigned long rate,
142 			       unsigned long *m, unsigned long *n)
143 {
144 	unsigned int i;
145 
146 	if (!(common->features & CCU_FEATURE_SIGMA_DELTA_MOD))
147 		return -EINVAL;
148 
149 	for (i = 0; i < sdm->table_size; i++)
150 		if (sdm->table[i].rate == rate) {
151 			*m = sdm->table[i].m;
152 			*n = sdm->table[i].n;
153 			return 0;
154 		}
155 
156 	/* nothing found */
157 	return -EINVAL;
158 }
159