1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Copyright 2022 NXP |
4 | * |
5 | * Peng Fan <peng.fan@nxp.com> |
6 | */ |
7 | |
8 | #include <linux/clk-provider.h> |
9 | #include <linux/errno.h> |
10 | #include <linux/export.h> |
11 | #include <linux/io.h> |
12 | #include <linux/iopoll.h> |
13 | #include <linux/slab.h> |
14 | |
15 | #include "clk.h" |
16 | |
17 | #define DIRECT_OFFSET 0x0 |
18 | |
19 | /* |
20 | * 0b000 - LPCG will be OFF in any CPU mode. |
21 | * 0b100 - LPCG will be ON in any CPU mode. |
22 | */ |
23 | #define LPM_SETTING_OFF 0x0 |
24 | #define LPM_SETTING_ON 0x4 |
25 | |
26 | #define LPM_CUR_OFFSET 0x1c |
27 | |
28 | #define AUTHEN_OFFSET 0x30 |
29 | #define CPULPM_EN BIT(2) |
30 | #define TZ_NS_SHIFT 9 |
31 | #define TZ_NS_MASK BIT(9) |
32 | |
33 | #define WHITE_LIST_SHIFT 16 |
34 | |
35 | struct imx93_clk_gate { |
36 | struct clk_hw hw; |
37 | void __iomem *reg; |
38 | u32 bit_idx; |
39 | u32 val; |
40 | u32 mask; |
41 | spinlock_t *lock; |
42 | unsigned int *share_count; |
43 | }; |
44 | |
45 | #define to_imx93_clk_gate(_hw) container_of(_hw, struct imx93_clk_gate, hw) |
46 | |
47 | static void imx93_clk_gate_do_hardware(struct clk_hw *hw, bool enable) |
48 | { |
49 | struct imx93_clk_gate *gate = to_imx93_clk_gate(hw); |
50 | u32 val; |
51 | |
52 | val = readl(addr: gate->reg + AUTHEN_OFFSET); |
53 | if (val & CPULPM_EN) { |
54 | val = enable ? LPM_SETTING_ON : LPM_SETTING_OFF; |
55 | writel(val, addr: gate->reg + LPM_CUR_OFFSET); |
56 | } else { |
57 | val = readl(addr: gate->reg + DIRECT_OFFSET); |
58 | val &= ~(gate->mask << gate->bit_idx); |
59 | if (enable) |
60 | val |= (gate->val & gate->mask) << gate->bit_idx; |
61 | writel(val, addr: gate->reg + DIRECT_OFFSET); |
62 | } |
63 | } |
64 | |
65 | static int imx93_clk_gate_enable(struct clk_hw *hw) |
66 | { |
67 | struct imx93_clk_gate *gate = to_imx93_clk_gate(hw); |
68 | unsigned long flags; |
69 | |
70 | spin_lock_irqsave(gate->lock, flags); |
71 | |
72 | if (gate->share_count && (*gate->share_count)++ > 0) |
73 | goto out; |
74 | |
75 | imx93_clk_gate_do_hardware(hw, enable: true); |
76 | out: |
77 | spin_unlock_irqrestore(lock: gate->lock, flags); |
78 | |
79 | return 0; |
80 | } |
81 | |
82 | static void imx93_clk_gate_disable(struct clk_hw *hw) |
83 | { |
84 | struct imx93_clk_gate *gate = to_imx93_clk_gate(hw); |
85 | unsigned long flags; |
86 | |
87 | spin_lock_irqsave(gate->lock, flags); |
88 | |
89 | if (gate->share_count) { |
90 | if (WARN_ON(*gate->share_count == 0)) |
91 | goto out; |
92 | else if (--(*gate->share_count) > 0) |
93 | goto out; |
94 | } |
95 | |
96 | imx93_clk_gate_do_hardware(hw, enable: false); |
97 | out: |
98 | spin_unlock_irqrestore(lock: gate->lock, flags); |
99 | } |
100 | |
101 | static int imx93_clk_gate_reg_is_enabled(struct imx93_clk_gate *gate) |
102 | { |
103 | u32 val = readl(addr: gate->reg + AUTHEN_OFFSET); |
104 | |
105 | if (val & CPULPM_EN) { |
106 | val = readl(addr: gate->reg + LPM_CUR_OFFSET); |
107 | if (val == LPM_SETTING_ON) |
108 | return 1; |
109 | } else { |
110 | val = readl(addr: gate->reg); |
111 | if (((val >> gate->bit_idx) & gate->mask) == gate->val) |
112 | return 1; |
113 | } |
114 | |
115 | return 0; |
116 | } |
117 | |
118 | static int imx93_clk_gate_is_enabled(struct clk_hw *hw) |
119 | { |
120 | struct imx93_clk_gate *gate = to_imx93_clk_gate(hw); |
121 | unsigned long flags; |
122 | int ret; |
123 | |
124 | spin_lock_irqsave(gate->lock, flags); |
125 | |
126 | ret = imx93_clk_gate_reg_is_enabled(gate); |
127 | |
128 | spin_unlock_irqrestore(lock: gate->lock, flags); |
129 | |
130 | return ret; |
131 | } |
132 | |
133 | static void imx93_clk_gate_disable_unused(struct clk_hw *hw) |
134 | { |
135 | struct imx93_clk_gate *gate = to_imx93_clk_gate(hw); |
136 | unsigned long flags; |
137 | |
138 | spin_lock_irqsave(gate->lock, flags); |
139 | |
140 | if (!gate->share_count || *gate->share_count == 0) |
141 | imx93_clk_gate_do_hardware(hw, enable: false); |
142 | |
143 | spin_unlock_irqrestore(lock: gate->lock, flags); |
144 | } |
145 | |
146 | static const struct clk_ops imx93_clk_gate_ops = { |
147 | .enable = imx93_clk_gate_enable, |
148 | .disable = imx93_clk_gate_disable, |
149 | .disable_unused = imx93_clk_gate_disable_unused, |
150 | .is_enabled = imx93_clk_gate_is_enabled, |
151 | }; |
152 | |
153 | static const struct clk_ops imx93_clk_gate_ro_ops = { |
154 | .is_enabled = imx93_clk_gate_is_enabled, |
155 | }; |
156 | |
157 | struct clk_hw *imx93_clk_gate(struct device *dev, const char *name, const char *parent_name, |
158 | unsigned long flags, void __iomem *reg, u32 bit_idx, u32 val, |
159 | u32 mask, u32 domain_id, unsigned int *share_count) |
160 | { |
161 | struct imx93_clk_gate *gate; |
162 | struct clk_hw *hw; |
163 | struct clk_init_data init; |
164 | int ret; |
165 | u32 authen; |
166 | |
167 | gate = kzalloc(size: sizeof(struct imx93_clk_gate), GFP_KERNEL); |
168 | if (!gate) |
169 | return ERR_PTR(error: -ENOMEM); |
170 | |
171 | gate->reg = reg; |
172 | gate->lock = &imx_ccm_lock; |
173 | gate->bit_idx = bit_idx; |
174 | gate->val = val; |
175 | gate->mask = mask; |
176 | gate->share_count = share_count; |
177 | |
178 | init.name = name; |
179 | init.ops = &imx93_clk_gate_ops; |
180 | init.flags = flags | CLK_SET_RATE_PARENT | CLK_OPS_PARENT_ENABLE; |
181 | init.parent_names = parent_name ? &parent_name : NULL; |
182 | init.num_parents = parent_name ? 1 : 0; |
183 | |
184 | gate->hw.init = &init; |
185 | hw = &gate->hw; |
186 | |
187 | authen = readl(addr: reg + AUTHEN_OFFSET); |
188 | if (!(authen & TZ_NS_MASK) || !(authen & BIT(WHITE_LIST_SHIFT + domain_id))) |
189 | init.ops = &imx93_clk_gate_ro_ops; |
190 | |
191 | ret = clk_hw_register(dev, hw); |
192 | if (ret) { |
193 | kfree(objp: gate); |
194 | return ERR_PTR(error: ret); |
195 | } |
196 | |
197 | return hw; |
198 | } |
199 | EXPORT_SYMBOL_GPL(imx93_clk_gate); |
200 | |