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
20static 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
41static 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
61static 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(map: common->regmap, reg: common->reg_swcr1, val: entry->swcr1);
66 ccu_update(common, swcr3, PLL_SWCR3_MASK, entry->swcr3);
67}
68
69static 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
76static 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
92static 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 */
103static 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
115static 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 : -EINVAL;
126}
127
128static long ccu_pll_round_rate(struct clk_hw *hw, unsigned long rate,
129 unsigned long *prate)
130{
131 struct ccu_pll *pll = hw_to_ccu_pll(hw);
132
133 return ccu_pll_lookup_best_rate(pll, rate)->rate;
134}
135
136static int ccu_pll_init(struct clk_hw *hw)
137{
138 struct ccu_pll *pll = hw_to_ccu_pll(hw);
139
140 if (ccu_pll_lookup_matched_entry(pll))
141 return 0;
142
143 ccu_pll_disable(hw);
144 ccu_pll_update_param(pll, entry: &pll->config.rate_tbl[0]);
145
146 return 0;
147}
148
149const struct clk_ops spacemit_ccu_pll_ops = {
150 .init = ccu_pll_init,
151 .enable = ccu_pll_enable,
152 .disable = ccu_pll_disable,
153 .set_rate = ccu_pll_set_rate,
154 .recalc_rate = ccu_pll_recalc_rate,
155 .round_rate = ccu_pll_round_rate,
156 .is_enabled = ccu_pll_is_enabled,
157};
158

Provided by KDAB

Privacy Policy
Improve your Profiling and Debugging skills
Find out more

source code of linux/drivers/clk/spacemit/ccu_pll.c