1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (c) 2022 MediaTek Inc. |
4 | * Author: Edward-JW Yang <edward-jw.yang@mediatek.com> |
5 | */ |
6 | |
7 | #include <linux/io.h> |
8 | #include <linux/iopoll.h> |
9 | |
10 | #include "clk-mtk.h" |
11 | #include "clk-pllfh.h" |
12 | #include "clk-fhctl.h" |
13 | |
14 | #define PERCENT_TO_DDSLMT(dds, percent_m10) \ |
15 | ((((dds) * (percent_m10)) >> 5) / 100) |
16 | |
17 | static const struct fhctl_offset fhctl_offset_v1 = { |
18 | .offset_hp_en = 0x0, |
19 | .offset_clk_con = 0x4, |
20 | .offset_rst_con = 0x8, |
21 | .offset_slope0 = 0xc, |
22 | .offset_slope1 = 0x10, |
23 | .offset_cfg = 0x0, |
24 | .offset_updnlmt = 0x4, |
25 | .offset_dds = 0x8, |
26 | .offset_dvfs = 0xc, |
27 | .offset_mon = 0x10, |
28 | }; |
29 | |
30 | static const struct fhctl_offset fhctl_offset_v2 = { |
31 | .offset_hp_en = 0x0, |
32 | .offset_clk_con = 0x8, |
33 | .offset_rst_con = 0xc, |
34 | .offset_slope0 = 0x10, |
35 | .offset_slope1 = 0x14, |
36 | .offset_cfg = 0x0, |
37 | .offset_updnlmt = 0x4, |
38 | .offset_dds = 0x8, |
39 | .offset_dvfs = 0xc, |
40 | .offset_mon = 0x10, |
41 | }; |
42 | |
43 | const struct fhctl_offset *fhctl_get_offset_table(enum fhctl_variant v) |
44 | { |
45 | switch (v) { |
46 | case FHCTL_PLLFH_V1: |
47 | return &fhctl_offset_v1; |
48 | case FHCTL_PLLFH_V2: |
49 | return &fhctl_offset_v2; |
50 | default: |
51 | return ERR_PTR(error: -EINVAL); |
52 | }; |
53 | } |
54 | |
55 | static void dump_hw(struct mtk_clk_pll *pll, struct fh_pll_regs *regs, |
56 | const struct fh_pll_data *data) |
57 | { |
58 | pr_info("hp_en<%x>,clk_con<%x>,slope0<%x>,slope1<%x>\n" , |
59 | readl(regs->reg_hp_en), readl(regs->reg_clk_con), |
60 | readl(regs->reg_slope0), readl(regs->reg_slope1)); |
61 | pr_info("cfg<%x>,lmt<%x>,dds<%x>,dvfs<%x>,mon<%x>\n" , |
62 | readl(regs->reg_cfg), readl(regs->reg_updnlmt), |
63 | readl(regs->reg_dds), readl(regs->reg_dvfs), |
64 | readl(regs->reg_mon)); |
65 | pr_info("pcw<%x>\n" , readl(pll->pcw_addr)); |
66 | } |
67 | |
68 | static int fhctl_set_ssc_regs(struct mtk_clk_pll *pll, struct fh_pll_regs *regs, |
69 | const struct fh_pll_data *data, u32 rate) |
70 | { |
71 | u32 updnlmt_val, r; |
72 | |
73 | writel(val: (readl(addr: regs->reg_cfg) & ~(data->frddsx_en)), addr: regs->reg_cfg); |
74 | writel(val: (readl(addr: regs->reg_cfg) & ~(data->sfstrx_en)), addr: regs->reg_cfg); |
75 | writel(val: (readl(addr: regs->reg_cfg) & ~(data->fhctlx_en)), addr: regs->reg_cfg); |
76 | |
77 | if (rate > 0) { |
78 | /* Set the relative parameter registers (dt/df/upbnd/downbnd) */ |
79 | r = readl(addr: regs->reg_cfg); |
80 | r &= ~(data->msk_frddsx_dys); |
81 | r |= (data->df_val << (ffs(data->msk_frddsx_dys) - 1)); |
82 | writel(val: r, addr: regs->reg_cfg); |
83 | |
84 | r = readl(addr: regs->reg_cfg); |
85 | r &= ~(data->msk_frddsx_dts); |
86 | r |= (data->dt_val << (ffs(data->msk_frddsx_dts) - 1)); |
87 | writel(val: r, addr: regs->reg_cfg); |
88 | |
89 | writel(val: (readl(addr: pll->pcw_addr) & data->dds_mask) | data->tgl_org, |
90 | addr: regs->reg_dds); |
91 | |
92 | /* Calculate UPDNLMT */ |
93 | updnlmt_val = PERCENT_TO_DDSLMT((readl(regs->reg_dds) & |
94 | data->dds_mask), rate) << |
95 | data->updnlmt_shft; |
96 | |
97 | writel(val: updnlmt_val, addr: regs->reg_updnlmt); |
98 | writel(readl(addr: regs->reg_hp_en) | BIT(data->fh_id), |
99 | addr: regs->reg_hp_en); |
100 | /* Enable SSC */ |
101 | writel(readl(addr: regs->reg_cfg) | data->frddsx_en, addr: regs->reg_cfg); |
102 | /* Enable Hopping control */ |
103 | writel(readl(addr: regs->reg_cfg) | data->fhctlx_en, addr: regs->reg_cfg); |
104 | |
105 | } else { |
106 | /* Switch to APMIXEDSYS control */ |
107 | writel(readl(addr: regs->reg_hp_en) & ~BIT(data->fh_id), |
108 | addr: regs->reg_hp_en); |
109 | /* Wait for DDS to be stable */ |
110 | udelay(30); |
111 | } |
112 | |
113 | return 0; |
114 | } |
115 | |
116 | static int hopping_hw_flow(struct mtk_clk_pll *pll, struct fh_pll_regs *regs, |
117 | const struct fh_pll_data *data, |
118 | struct fh_pll_state *state, unsigned int new_dds) |
119 | { |
120 | u32 dds_mask = data->dds_mask; |
121 | u32 mon_dds = 0; |
122 | u32 con_pcw_tmp; |
123 | int ret; |
124 | |
125 | if (state->ssc_rate) |
126 | fhctl_set_ssc_regs(pll, regs, data, rate: 0); |
127 | |
128 | writel(val: (readl(addr: pll->pcw_addr) & dds_mask) | data->tgl_org, |
129 | addr: regs->reg_dds); |
130 | |
131 | writel(readl(addr: regs->reg_cfg) | data->sfstrx_en, addr: regs->reg_cfg); |
132 | writel(readl(addr: regs->reg_cfg) | data->fhctlx_en, addr: regs->reg_cfg); |
133 | writel(val: data->slope0_value, addr: regs->reg_slope0); |
134 | writel(val: data->slope1_value, addr: regs->reg_slope1); |
135 | |
136 | writel(readl(addr: regs->reg_hp_en) | BIT(data->fh_id), addr: regs->reg_hp_en); |
137 | writel(val: (new_dds) | (data->dvfs_tri), addr: regs->reg_dvfs); |
138 | |
139 | /* Wait 1000 us until DDS stable */ |
140 | ret = readl_poll_timeout_atomic(regs->reg_mon, mon_dds, |
141 | (mon_dds & dds_mask) == new_dds, |
142 | 10, 1000); |
143 | if (ret) { |
144 | pr_warn("%s: FHCTL hopping timeout\n" , pll->data->name); |
145 | dump_hw(pll, regs, data); |
146 | } |
147 | |
148 | con_pcw_tmp = readl(addr: pll->pcw_addr) & (~dds_mask); |
149 | con_pcw_tmp = (con_pcw_tmp | (readl(addr: regs->reg_mon) & dds_mask) | |
150 | data->pcwchg); |
151 | |
152 | writel(val: con_pcw_tmp, addr: pll->pcw_addr); |
153 | writel(readl(addr: regs->reg_hp_en) & ~BIT(data->fh_id), addr: regs->reg_hp_en); |
154 | |
155 | if (state->ssc_rate) |
156 | fhctl_set_ssc_regs(pll, regs, data, rate: state->ssc_rate); |
157 | |
158 | return ret; |
159 | } |
160 | |
161 | static unsigned int __get_postdiv(struct mtk_clk_pll *pll) |
162 | { |
163 | unsigned int regval; |
164 | |
165 | regval = readl(addr: pll->pd_addr) >> pll->data->pd_shift; |
166 | regval &= POSTDIV_MASK; |
167 | |
168 | return BIT(regval); |
169 | } |
170 | |
171 | static void __set_postdiv(struct mtk_clk_pll *pll, unsigned int postdiv) |
172 | { |
173 | unsigned int regval; |
174 | |
175 | regval = readl(addr: pll->pd_addr); |
176 | regval &= ~(POSTDIV_MASK << pll->data->pd_shift); |
177 | regval |= (ffs(postdiv) - 1) << pll->data->pd_shift; |
178 | writel(val: regval, addr: pll->pd_addr); |
179 | } |
180 | |
181 | static int fhctl_hopping(struct mtk_fh *fh, unsigned int new_dds, |
182 | unsigned int postdiv) |
183 | { |
184 | const struct fh_pll_data *data = &fh->pllfh_data->data; |
185 | struct fh_pll_state *state = &fh->pllfh_data->state; |
186 | struct fh_pll_regs *regs = &fh->regs; |
187 | struct mtk_clk_pll *pll = &fh->clk_pll; |
188 | spinlock_t *lock = fh->lock; |
189 | unsigned int pll_postdiv; |
190 | unsigned long flags = 0; |
191 | int ret; |
192 | |
193 | if (postdiv) { |
194 | pll_postdiv = __get_postdiv(pll); |
195 | |
196 | if (postdiv > pll_postdiv) |
197 | __set_postdiv(pll, postdiv); |
198 | } |
199 | |
200 | spin_lock_irqsave(lock, flags); |
201 | |
202 | ret = hopping_hw_flow(pll, regs, data, state, new_dds); |
203 | |
204 | spin_unlock_irqrestore(lock, flags); |
205 | |
206 | if (postdiv && postdiv < pll_postdiv) |
207 | __set_postdiv(pll, postdiv); |
208 | |
209 | return ret; |
210 | } |
211 | |
212 | static int fhctl_ssc_enable(struct mtk_fh *fh, u32 rate) |
213 | { |
214 | const struct fh_pll_data *data = &fh->pllfh_data->data; |
215 | struct fh_pll_state *state = &fh->pllfh_data->state; |
216 | struct fh_pll_regs *regs = &fh->regs; |
217 | struct mtk_clk_pll *pll = &fh->clk_pll; |
218 | spinlock_t *lock = fh->lock; |
219 | unsigned long flags = 0; |
220 | |
221 | spin_lock_irqsave(lock, flags); |
222 | |
223 | fhctl_set_ssc_regs(pll, regs, data, rate); |
224 | state->ssc_rate = rate; |
225 | |
226 | spin_unlock_irqrestore(lock, flags); |
227 | |
228 | return 0; |
229 | } |
230 | |
231 | static const struct fh_operation fhctl_ops = { |
232 | .hopping = fhctl_hopping, |
233 | .ssc_enable = fhctl_ssc_enable, |
234 | }; |
235 | |
236 | const struct fh_operation *fhctl_get_ops(void) |
237 | { |
238 | return &fhctl_ops; |
239 | } |
240 | |
241 | void fhctl_hw_init(struct mtk_fh *fh) |
242 | { |
243 | const struct fh_pll_data data = fh->pllfh_data->data; |
244 | struct fh_pll_state state = fh->pllfh_data->state; |
245 | struct fh_pll_regs regs = fh->regs; |
246 | u32 val; |
247 | |
248 | /* initial hw register */ |
249 | val = readl(addr: regs.reg_clk_con) | BIT(data.fh_id); |
250 | writel(val, addr: regs.reg_clk_con); |
251 | |
252 | val = readl(addr: regs.reg_rst_con) & ~BIT(data.fh_id); |
253 | writel(val, addr: regs.reg_rst_con); |
254 | val = readl(addr: regs.reg_rst_con) | BIT(data.fh_id); |
255 | writel(val, addr: regs.reg_rst_con); |
256 | |
257 | writel(val: 0x0, addr: regs.reg_cfg); |
258 | writel(val: 0x0, addr: regs.reg_updnlmt); |
259 | writel(val: 0x0, addr: regs.reg_dds); |
260 | |
261 | /* enable ssc if needed */ |
262 | if (state.ssc_rate) |
263 | fh->ops->ssc_enable(fh, state.ssc_rate); |
264 | } |
265 | |