xref: /linux/drivers/clk/spacemit/ccu_pll.c (revision 522ba450b56fff29f868b1552bdc2965f55de7ed)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Copyright (c) 2024 SpacemiT Technology Co. Ltd
4  * Copyright (c) 2024-2025 Haylen Chu <heylenay@4d2.org>
5  */
6 
7 #include <linux/clk-provider.h>
8 #include <linux/math.h>
9 #include <linux/regmap.h>
10 
11 #include "ccu_common.h"
12 #include "ccu_pll.h"
13 
14 #define PLL_TIMEOUT_US		3000
15 #define PLL_DELAY_US		5
16 
17 #define PLL_SWCR3_EN		((u32)BIT(31))
18 #define PLL_SWCR3_MASK		GENMASK(30, 0)
19 
ccu_pll_lookup_best_rate(struct ccu_pll * pll,unsigned long rate)20 static const struct ccu_pll_rate_tbl *ccu_pll_lookup_best_rate(struct ccu_pll *pll,
21 							       unsigned long rate)
22 {
23 	struct ccu_pll_config *config = &pll->config;
24 	const struct ccu_pll_rate_tbl *best_entry;
25 	unsigned long best_delta = ULONG_MAX;
26 	int i;
27 
28 	for (i = 0; i < config->tbl_num; i++) {
29 		const struct ccu_pll_rate_tbl *entry = &config->rate_tbl[i];
30 		unsigned long delta = abs_diff(entry->rate, rate);
31 
32 		if (delta < best_delta) {
33 			best_delta = delta;
34 			best_entry = entry;
35 		}
36 	}
37 
38 	return best_entry;
39 }
40 
ccu_pll_lookup_matched_entry(struct ccu_pll * pll)41 static const struct ccu_pll_rate_tbl *ccu_pll_lookup_matched_entry(struct ccu_pll *pll)
42 {
43 	struct ccu_pll_config *config = &pll->config;
44 	u32 swcr1, swcr3;
45 	int i;
46 
47 	swcr1 = ccu_read(&pll->common, swcr1);
48 	swcr3 = ccu_read(&pll->common, swcr3);
49 	swcr3 &= PLL_SWCR3_MASK;
50 
51 	for (i = 0; i < config->tbl_num; i++) {
52 		const struct ccu_pll_rate_tbl *entry = &config->rate_tbl[i];
53 
54 		if (swcr1 == entry->swcr1 && swcr3 == entry->swcr3)
55 			return entry;
56 	}
57 
58 	return NULL;
59 }
60 
ccu_pll_update_param(struct ccu_pll * pll,const struct ccu_pll_rate_tbl * entry)61 static void ccu_pll_update_param(struct ccu_pll *pll, const struct ccu_pll_rate_tbl *entry)
62 {
63 	struct ccu_common *common = &pll->common;
64 
65 	regmap_write(common->regmap, common->reg_swcr1, entry->swcr1);
66 	ccu_update(common, swcr3, PLL_SWCR3_MASK, entry->swcr3);
67 }
68 
ccu_pll_is_enabled(struct clk_hw * hw)69 static int ccu_pll_is_enabled(struct clk_hw *hw)
70 {
71 	struct ccu_common *common = hw_to_ccu_common(hw);
72 
73 	return ccu_read(common, swcr3) & PLL_SWCR3_EN;
74 }
75 
ccu_pll_enable(struct clk_hw * hw)76 static int ccu_pll_enable(struct clk_hw *hw)
77 {
78 	struct ccu_pll *pll = hw_to_ccu_pll(hw);
79 	struct ccu_common *common = &pll->common;
80 	unsigned int tmp;
81 
82 	ccu_update(common, swcr3, PLL_SWCR3_EN, PLL_SWCR3_EN);
83 
84 	/* check lock status */
85 	return regmap_read_poll_timeout_atomic(common->lock_regmap,
86 					       pll->config.reg_lock,
87 					       tmp,
88 					       tmp & pll->config.mask_lock,
89 					       PLL_DELAY_US, PLL_TIMEOUT_US);
90 }
91 
ccu_pll_disable(struct clk_hw * hw)92 static void ccu_pll_disable(struct clk_hw *hw)
93 {
94 	struct ccu_common *common = hw_to_ccu_common(hw);
95 
96 	ccu_update(common, swcr3, PLL_SWCR3_EN, 0);
97 }
98 
99 /*
100  * PLLs must be gated before changing rate, which is ensured by
101  * flag CLK_SET_RATE_GATE.
102  */
ccu_pll_set_rate(struct clk_hw * hw,unsigned long rate,unsigned long parent_rate)103 static int ccu_pll_set_rate(struct clk_hw *hw, unsigned long rate,
104 			    unsigned long parent_rate)
105 {
106 	struct ccu_pll *pll = hw_to_ccu_pll(hw);
107 	const struct ccu_pll_rate_tbl *entry;
108 
109 	entry = ccu_pll_lookup_best_rate(pll, rate);
110 	ccu_pll_update_param(pll, entry);
111 
112 	return 0;
113 }
114 
ccu_pll_recalc_rate(struct clk_hw * hw,unsigned long parent_rate)115 static unsigned long ccu_pll_recalc_rate(struct clk_hw *hw,
116 					 unsigned long parent_rate)
117 {
118 	struct ccu_pll *pll = hw_to_ccu_pll(hw);
119 	const struct ccu_pll_rate_tbl *entry;
120 
121 	entry = ccu_pll_lookup_matched_entry(pll);
122 
123 	WARN_ON_ONCE(!entry);
124 
125 	return entry ? entry->rate : 0;
126 }
127 
ccu_pll_determine_rate(struct clk_hw * hw,struct clk_rate_request * req)128 static int ccu_pll_determine_rate(struct clk_hw *hw,
129 				  struct clk_rate_request *req)
130 {
131 	struct ccu_pll *pll = hw_to_ccu_pll(hw);
132 
133 	req->rate = ccu_pll_lookup_best_rate(pll, req->rate)->rate;
134 
135 	return 0;
136 }
137 
ccu_pll_init(struct clk_hw * hw)138 static int ccu_pll_init(struct clk_hw *hw)
139 {
140 	struct ccu_pll *pll = hw_to_ccu_pll(hw);
141 
142 	if (ccu_pll_lookup_matched_entry(pll))
143 		return 0;
144 
145 	ccu_pll_disable(hw);
146 	ccu_pll_update_param(pll, &pll->config.rate_tbl[0]);
147 
148 	return 0;
149 }
150 
151 const struct clk_ops spacemit_ccu_pll_ops = {
152 	.init		= ccu_pll_init,
153 	.enable		= ccu_pll_enable,
154 	.disable	= ccu_pll_disable,
155 	.set_rate	= ccu_pll_set_rate,
156 	.recalc_rate	= ccu_pll_recalc_rate,
157 	.determine_rate = ccu_pll_determine_rate,
158 	.is_enabled	= ccu_pll_is_enabled,
159 };
160