xref: /openbmc/linux/drivers/clk/sunxi-ng/ccu_sdm.c (revision 4984dd069f2995f239f075199ee8c0d9f020bcd9)
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/io.h>
12 #include <linux/spinlock.h>
13 
14 #include "ccu_sdm.h"
15 
16 bool ccu_sdm_helper_is_enabled(struct ccu_common *common,
17 			       struct ccu_sdm_internal *sdm)
18 {
19 	if (!(common->features & CCU_FEATURE_SIGMA_DELTA_MOD))
20 		return false;
21 
22 	if (sdm->enable && !(readl(common->base + common->reg) & sdm->enable))
23 		return false;
24 
25 	return !!(readl(common->base + sdm->tuning_reg) & sdm->tuning_enable);
26 }
27 
28 void ccu_sdm_helper_enable(struct ccu_common *common,
29 			   struct ccu_sdm_internal *sdm,
30 			   unsigned long rate)
31 {
32 	unsigned long flags;
33 	unsigned int i;
34 	u32 reg;
35 
36 	if (!(common->features & CCU_FEATURE_SIGMA_DELTA_MOD))
37 		return;
38 
39 	/* Set the pattern */
40 	for (i = 0; i < sdm->table_size; i++)
41 		if (sdm->table[i].rate == rate)
42 			writel(sdm->table[i].pattern,
43 			       common->base + sdm->tuning_reg);
44 
45 	/* Make sure SDM is enabled */
46 	spin_lock_irqsave(common->lock, flags);
47 	reg = readl(common->base + sdm->tuning_reg);
48 	writel(reg | sdm->tuning_enable, common->base + sdm->tuning_reg);
49 	spin_unlock_irqrestore(common->lock, flags);
50 
51 	spin_lock_irqsave(common->lock, flags);
52 	reg = readl(common->base + common->reg);
53 	writel(reg | sdm->enable, common->base + common->reg);
54 	spin_unlock_irqrestore(common->lock, flags);
55 }
56 
57 void ccu_sdm_helper_disable(struct ccu_common *common,
58 			    struct ccu_sdm_internal *sdm)
59 {
60 	unsigned long flags;
61 	u32 reg;
62 
63 	if (!(common->features & CCU_FEATURE_SIGMA_DELTA_MOD))
64 		return;
65 
66 	spin_lock_irqsave(common->lock, flags);
67 	reg = readl(common->base + common->reg);
68 	writel(reg & ~sdm->enable, common->base + common->reg);
69 	spin_unlock_irqrestore(common->lock, flags);
70 
71 	spin_lock_irqsave(common->lock, flags);
72 	reg = readl(common->base + sdm->tuning_reg);
73 	writel(reg & ~sdm->tuning_enable, common->base + sdm->tuning_reg);
74 	spin_unlock_irqrestore(common->lock, flags);
75 }
76 
77 /*
78  * Sigma delta modulation provides a way to do fractional-N frequency
79  * synthesis, in essence allowing the PLL to output any frequency
80  * within its operational range. On earlier SoCs such as the A10/A20,
81  * some PLLs support this. On later SoCs, all PLLs support this.
82  *
83  * The datasheets do not explain what the "wave top" and "wave bottom"
84  * parameters mean or do, nor how to calculate the effective output
85  * frequency. The only examples (and real world usage) are for the audio
86  * PLL to generate 24.576 and 22.5792 MHz clock rates used by the audio
87  * peripherals. The author lacks the underlying domain knowledge to
88  * pursue this.
89  *
90  * The goal and function of the following code is to support the two
91  * clock rates used by the audio subsystem, allowing for proper audio
92  * playback and capture without any pitch or speed changes.
93  */
94 bool ccu_sdm_helper_has_rate(struct ccu_common *common,
95 			     struct ccu_sdm_internal *sdm,
96 			     unsigned long rate)
97 {
98 	unsigned int i;
99 
100 	if (!(common->features & CCU_FEATURE_SIGMA_DELTA_MOD))
101 		return false;
102 
103 	for (i = 0; i < sdm->table_size; i++)
104 		if (sdm->table[i].rate == rate)
105 			return true;
106 
107 	return false;
108 }
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 
140 int ccu_sdm_helper_get_factors(struct ccu_common *common,
141 			       struct ccu_sdm_internal *sdm,
142 			       unsigned long rate,
143 			       unsigned long *m, unsigned long *n)
144 {
145 	unsigned int i;
146 
147 	if (!(common->features & CCU_FEATURE_SIGMA_DELTA_MOD))
148 		return -EINVAL;
149 
150 	for (i = 0; i < sdm->table_size; i++)
151 		if (sdm->table[i].rate == rate) {
152 			*m = sdm->table[i].m;
153 			*n = sdm->table[i].n;
154 			return 0;
155 		}
156 
157 	/* nothing found */
158 	return -EINVAL;
159 }
160