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(addr: common->base + common->reg) & sdm->enable)) |
19 | return false; |
20 | |
21 | return !!(readl(addr: 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(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 | } |
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(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 | } |
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(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 | } |
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 | |