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
17static 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
30static 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
43const 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(-EINVAL);
52	};
53}
54
55static 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
68static 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((readl(regs->reg_cfg) & ~(data->frddsx_en)), regs->reg_cfg);
74	writel((readl(regs->reg_cfg) & ~(data->sfstrx_en)), regs->reg_cfg);
75	writel((readl(regs->reg_cfg) & ~(data->fhctlx_en)), regs->reg_cfg);
76
77	if (rate > 0) {
78		/* Set the relative parameter registers (dt/df/upbnd/downbnd) */
79		r = readl(regs->reg_cfg);
80		r &= ~(data->msk_frddsx_dys);
81		r |= (data->df_val << (ffs(data->msk_frddsx_dys) - 1));
82		writel(r, regs->reg_cfg);
83
84		r = readl(regs->reg_cfg);
85		r &= ~(data->msk_frddsx_dts);
86		r |= (data->dt_val << (ffs(data->msk_frddsx_dts) - 1));
87		writel(r, regs->reg_cfg);
88
89		writel((readl(pll->pcw_addr) & data->dds_mask) | data->tgl_org,
90			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(updnlmt_val, regs->reg_updnlmt);
98		writel(readl(regs->reg_hp_en) | BIT(data->fh_id),
99		       regs->reg_hp_en);
100		/* Enable SSC */
101		writel(readl(regs->reg_cfg) | data->frddsx_en, regs->reg_cfg);
102		/* Enable Hopping control */
103		writel(readl(regs->reg_cfg) | data->fhctlx_en, regs->reg_cfg);
104
105	} else {
106		/* Switch to APMIXEDSYS control */
107		writel(readl(regs->reg_hp_en) & ~BIT(data->fh_id),
108		       regs->reg_hp_en);
109		/* Wait for DDS to be stable */
110		udelay(30);
111	}
112
113	return 0;
114}
115
116static 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, 0);
127
128	writel((readl(pll->pcw_addr) & dds_mask) | data->tgl_org,
129		regs->reg_dds);
130
131	writel(readl(regs->reg_cfg) | data->sfstrx_en, regs->reg_cfg);
132	writel(readl(regs->reg_cfg) | data->fhctlx_en, regs->reg_cfg);
133	writel(data->slope0_value, regs->reg_slope0);
134	writel(data->slope1_value, regs->reg_slope1);
135
136	writel(readl(regs->reg_hp_en) | BIT(data->fh_id), regs->reg_hp_en);
137	writel((new_dds) | (data->dvfs_tri), 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(pll->pcw_addr) & (~dds_mask);
149	con_pcw_tmp = (con_pcw_tmp | (readl(regs->reg_mon) & dds_mask) |
150		       data->pcwchg);
151
152	writel(con_pcw_tmp, pll->pcw_addr);
153	writel(readl(regs->reg_hp_en) & ~BIT(data->fh_id), regs->reg_hp_en);
154
155	if (state->ssc_rate)
156		fhctl_set_ssc_regs(pll, regs, data, state->ssc_rate);
157
158	return ret;
159}
160
161static unsigned int __get_postdiv(struct mtk_clk_pll *pll)
162{
163	unsigned int regval;
164
165	regval = readl(pll->pd_addr) >> pll->data->pd_shift;
166	regval &= POSTDIV_MASK;
167
168	return BIT(regval);
169}
170
171static void __set_postdiv(struct mtk_clk_pll *pll, unsigned int postdiv)
172{
173	unsigned int regval;
174
175	regval = readl(pll->pd_addr);
176	regval &= ~(POSTDIV_MASK << pll->data->pd_shift);
177	regval |= (ffs(postdiv) - 1) << pll->data->pd_shift;
178	writel(regval, pll->pd_addr);
179}
180
181static 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
212static 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
231static const struct fh_operation fhctl_ops = {
232	.hopping = fhctl_hopping,
233	.ssc_enable = fhctl_ssc_enable,
234};
235
236const struct fh_operation *fhctl_get_ops(void)
237{
238	return &fhctl_ops;
239}
240
241void 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(regs.reg_clk_con) | BIT(data.fh_id);
250	writel(val, regs.reg_clk_con);
251
252	val = readl(regs.reg_rst_con) & ~BIT(data.fh_id);
253	writel(val, regs.reg_rst_con);
254	val = readl(regs.reg_rst_con) | BIT(data.fh_id);
255	writel(val, regs.reg_rst_con);
256
257	writel(0x0, regs.reg_cfg);
258	writel(0x0, regs.reg_updnlmt);
259	writel(0x0, 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