1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Mediatek MT7621 PCI PHY Driver |
4 | * Author: Sergio Paracuellos <sergio.paracuellos@gmail.com> |
5 | */ |
6 | |
7 | #include <dt-bindings/phy/phy.h> |
8 | #include <linux/clk.h> |
9 | #include <linux/bitfield.h> |
10 | #include <linux/bitops.h> |
11 | #include <linux/module.h> |
12 | #include <linux/of.h> |
13 | #include <linux/phy/phy.h> |
14 | #include <linux/platform_device.h> |
15 | #include <linux/regmap.h> |
16 | #include <linux/sys_soc.h> |
17 | |
18 | #define RG_PE1_PIPE_REG 0x02c |
19 | #define RG_PE1_PIPE_RST BIT(12) |
20 | #define RG_PE1_PIPE_CMD_FRC BIT(4) |
21 | |
22 | #define RG_P0_TO_P1_WIDTH 0x100 |
23 | #define RG_PE1_H_LCDDS_REG 0x49c |
24 | #define RG_PE1_H_LCDDS_PCW GENMASK(30, 0) |
25 | |
26 | #define RG_PE1_FRC_H_XTAL_REG 0x400 |
27 | #define RG_PE1_FRC_H_XTAL_TYPE BIT(8) |
28 | #define RG_PE1_H_XTAL_TYPE GENMASK(10, 9) |
29 | |
30 | #define RG_PE1_FRC_PHY_REG 0x000 |
31 | #define RG_PE1_FRC_PHY_EN BIT(4) |
32 | #define RG_PE1_PHY_EN BIT(5) |
33 | |
34 | #define RG_PE1_H_PLL_REG 0x490 |
35 | #define RG_PE1_H_PLL_BC GENMASK(23, 22) |
36 | #define RG_PE1_H_PLL_BP GENMASK(21, 18) |
37 | #define RG_PE1_H_PLL_IR GENMASK(15, 12) |
38 | #define RG_PE1_H_PLL_IC GENMASK(11, 8) |
39 | #define RG_PE1_H_PLL_PREDIV GENMASK(7, 6) |
40 | #define RG_PE1_PLL_DIVEN GENMASK(3, 1) |
41 | |
42 | #define RG_PE1_H_PLL_FBKSEL_REG 0x4bc |
43 | #define RG_PE1_H_PLL_FBKSEL GENMASK(5, 4) |
44 | |
45 | #define RG_PE1_H_LCDDS_SSC_PRD_REG 0x4a4 |
46 | #define RG_PE1_H_LCDDS_SSC_PRD GENMASK(15, 0) |
47 | |
48 | #define RG_PE1_H_LCDDS_SSC_DELTA_REG 0x4a8 |
49 | #define RG_PE1_H_LCDDS_SSC_DELTA GENMASK(11, 0) |
50 | #define RG_PE1_H_LCDDS_SSC_DELTA1 GENMASK(27, 16) |
51 | |
52 | #define RG_PE1_LCDDS_CLK_PH_INV_REG 0x4a0 |
53 | #define RG_PE1_LCDDS_CLK_PH_INV BIT(5) |
54 | |
55 | #define RG_PE1_H_PLL_BR_REG 0x4ac |
56 | #define RG_PE1_H_PLL_BR GENMASK(18, 16) |
57 | |
58 | #define RG_PE1_MSTCKDIV_REG 0x414 |
59 | #define RG_PE1_MSTCKDIV GENMASK(7, 6) |
60 | |
61 | #define RG_PE1_FRC_MSTCKDIV BIT(5) |
62 | |
63 | #define MAX_PHYS 2 |
64 | |
65 | /** |
66 | * struct mt7621_pci_phy - Mt7621 Pcie PHY core |
67 | * @dev: pointer to device |
68 | * @regmap: kernel regmap pointer |
69 | * @phy: pointer to the kernel PHY device |
70 | * @sys_clk: pointer to the system XTAL clock |
71 | * @port_base: base register |
72 | * @has_dual_port: if the phy has dual ports. |
73 | * @bypass_pipe_rst: mark if 'mt7621_bypass_pipe_rst' |
74 | * needs to be executed. Depends on chip revision. |
75 | */ |
76 | struct mt7621_pci_phy { |
77 | struct device *dev; |
78 | struct regmap *regmap; |
79 | struct phy *phy; |
80 | struct clk *sys_clk; |
81 | void __iomem *port_base; |
82 | bool has_dual_port; |
83 | bool bypass_pipe_rst; |
84 | }; |
85 | |
86 | static inline void mt7621_phy_rmw(struct mt7621_pci_phy *phy, |
87 | u32 reg, u32 clr, u32 set) |
88 | { |
89 | u32 val; |
90 | |
91 | /* |
92 | * We cannot use 'regmap_write_bits' here because internally |
93 | * 'set' is masked before is set to the value that will be |
94 | * written to the register. That way results in no reliable |
95 | * pci setup. Avoid to mask 'set' before set value to 'val' |
96 | * completely avoid the problem. |
97 | */ |
98 | regmap_read(map: phy->regmap, reg, val: &val); |
99 | val &= ~clr; |
100 | val |= set; |
101 | regmap_write(map: phy->regmap, reg, val); |
102 | } |
103 | |
104 | static void mt7621_bypass_pipe_rst(struct mt7621_pci_phy *phy) |
105 | { |
106 | mt7621_phy_rmw(phy, RG_PE1_PIPE_REG, clr: 0, RG_PE1_PIPE_RST); |
107 | mt7621_phy_rmw(phy, RG_PE1_PIPE_REG, clr: 0, RG_PE1_PIPE_CMD_FRC); |
108 | |
109 | if (phy->has_dual_port) { |
110 | mt7621_phy_rmw(phy, RG_PE1_PIPE_REG + RG_P0_TO_P1_WIDTH, |
111 | clr: 0, RG_PE1_PIPE_RST); |
112 | mt7621_phy_rmw(phy, RG_PE1_PIPE_REG + RG_P0_TO_P1_WIDTH, |
113 | clr: 0, RG_PE1_PIPE_CMD_FRC); |
114 | } |
115 | } |
116 | |
117 | static int mt7621_set_phy_for_ssc(struct mt7621_pci_phy *phy) |
118 | { |
119 | struct device *dev = phy->dev; |
120 | unsigned long clk_rate; |
121 | |
122 | clk_rate = clk_get_rate(clk: phy->sys_clk); |
123 | if (!clk_rate) |
124 | return -EINVAL; |
125 | |
126 | /* Set PCIe Port PHY to disable SSC */ |
127 | /* Debug Xtal Type */ |
128 | mt7621_phy_rmw(phy, RG_PE1_FRC_H_XTAL_REG, |
129 | RG_PE1_FRC_H_XTAL_TYPE | RG_PE1_H_XTAL_TYPE, |
130 | RG_PE1_FRC_H_XTAL_TYPE | |
131 | FIELD_PREP(RG_PE1_H_XTAL_TYPE, 0x00)); |
132 | |
133 | /* disable port */ |
134 | mt7621_phy_rmw(phy, RG_PE1_FRC_PHY_REG, RG_PE1_PHY_EN, |
135 | RG_PE1_FRC_PHY_EN); |
136 | |
137 | if (phy->has_dual_port) { |
138 | mt7621_phy_rmw(phy, RG_PE1_FRC_PHY_REG + RG_P0_TO_P1_WIDTH, |
139 | RG_PE1_PHY_EN, RG_PE1_FRC_PHY_EN); |
140 | } |
141 | |
142 | if (clk_rate == 40000000) { /* 40MHz Xtal */ |
143 | /* Set Pre-divider ratio (for host mode) */ |
144 | mt7621_phy_rmw(phy, RG_PE1_H_PLL_REG, RG_PE1_H_PLL_PREDIV, |
145 | FIELD_PREP(RG_PE1_H_PLL_PREDIV, 0x01)); |
146 | |
147 | dev_dbg(dev, "Xtal is 40MHz\n" ); |
148 | } else if (clk_rate == 25000000) { /* 25MHz Xal */ |
149 | mt7621_phy_rmw(phy, RG_PE1_H_PLL_REG, RG_PE1_H_PLL_PREDIV, |
150 | FIELD_PREP(RG_PE1_H_PLL_PREDIV, 0x00)); |
151 | |
152 | /* Select feedback clock */ |
153 | mt7621_phy_rmw(phy, RG_PE1_H_PLL_FBKSEL_REG, |
154 | RG_PE1_H_PLL_FBKSEL, |
155 | FIELD_PREP(RG_PE1_H_PLL_FBKSEL, 0x01)); |
156 | |
157 | /* DDS NCPO PCW (for host mode) */ |
158 | mt7621_phy_rmw(phy, RG_PE1_H_LCDDS_SSC_PRD_REG, |
159 | RG_PE1_H_LCDDS_SSC_PRD, |
160 | FIELD_PREP(RG_PE1_H_LCDDS_SSC_PRD, 0x00)); |
161 | |
162 | /* DDS SSC dither period control */ |
163 | mt7621_phy_rmw(phy, RG_PE1_H_LCDDS_SSC_PRD_REG, |
164 | RG_PE1_H_LCDDS_SSC_PRD, |
165 | FIELD_PREP(RG_PE1_H_LCDDS_SSC_PRD, 0x18d)); |
166 | |
167 | /* DDS SSC dither amplitude control */ |
168 | mt7621_phy_rmw(phy, RG_PE1_H_LCDDS_SSC_DELTA_REG, |
169 | RG_PE1_H_LCDDS_SSC_DELTA | |
170 | RG_PE1_H_LCDDS_SSC_DELTA1, |
171 | FIELD_PREP(RG_PE1_H_LCDDS_SSC_DELTA, 0x4a) | |
172 | FIELD_PREP(RG_PE1_H_LCDDS_SSC_DELTA1, 0x4a)); |
173 | |
174 | dev_dbg(dev, "Xtal is 25MHz\n" ); |
175 | } else { /* 20MHz Xtal */ |
176 | mt7621_phy_rmw(phy, RG_PE1_H_PLL_REG, RG_PE1_H_PLL_PREDIV, |
177 | FIELD_PREP(RG_PE1_H_PLL_PREDIV, 0x00)); |
178 | |
179 | dev_dbg(dev, "Xtal is 20MHz\n" ); |
180 | } |
181 | |
182 | /* DDS clock inversion */ |
183 | mt7621_phy_rmw(phy, RG_PE1_LCDDS_CLK_PH_INV_REG, |
184 | RG_PE1_LCDDS_CLK_PH_INV, RG_PE1_LCDDS_CLK_PH_INV); |
185 | |
186 | /* Set PLL bits */ |
187 | mt7621_phy_rmw(phy, RG_PE1_H_PLL_REG, |
188 | RG_PE1_H_PLL_BC | RG_PE1_H_PLL_BP | RG_PE1_H_PLL_IR | |
189 | RG_PE1_H_PLL_IC | RG_PE1_PLL_DIVEN, |
190 | FIELD_PREP(RG_PE1_H_PLL_BC, 0x02) | |
191 | FIELD_PREP(RG_PE1_H_PLL_BP, 0x06) | |
192 | FIELD_PREP(RG_PE1_H_PLL_IR, 0x02) | |
193 | FIELD_PREP(RG_PE1_H_PLL_IC, 0x01) | |
194 | FIELD_PREP(RG_PE1_PLL_DIVEN, 0x02)); |
195 | |
196 | mt7621_phy_rmw(phy, RG_PE1_H_PLL_BR_REG, RG_PE1_H_PLL_BR, |
197 | FIELD_PREP(RG_PE1_H_PLL_BR, 0x00)); |
198 | |
199 | if (clk_rate == 40000000) { /* 40MHz Xtal */ |
200 | /* set force mode enable of da_pe1_mstckdiv */ |
201 | mt7621_phy_rmw(phy, RG_PE1_MSTCKDIV_REG, |
202 | RG_PE1_MSTCKDIV | RG_PE1_FRC_MSTCKDIV, |
203 | FIELD_PREP(RG_PE1_MSTCKDIV, 0x01) | |
204 | RG_PE1_FRC_MSTCKDIV); |
205 | } |
206 | |
207 | return 0; |
208 | } |
209 | |
210 | static int mt7621_pci_phy_init(struct phy *phy) |
211 | { |
212 | struct mt7621_pci_phy *mphy = phy_get_drvdata(phy); |
213 | |
214 | if (mphy->bypass_pipe_rst) |
215 | mt7621_bypass_pipe_rst(phy: mphy); |
216 | |
217 | return mt7621_set_phy_for_ssc(phy: mphy); |
218 | } |
219 | |
220 | static int mt7621_pci_phy_power_on(struct phy *phy) |
221 | { |
222 | struct mt7621_pci_phy *mphy = phy_get_drvdata(phy); |
223 | |
224 | /* Enable PHY and disable force mode */ |
225 | mt7621_phy_rmw(phy: mphy, RG_PE1_FRC_PHY_REG, |
226 | RG_PE1_FRC_PHY_EN, RG_PE1_PHY_EN); |
227 | |
228 | if (mphy->has_dual_port) { |
229 | mt7621_phy_rmw(phy: mphy, RG_PE1_FRC_PHY_REG + RG_P0_TO_P1_WIDTH, |
230 | RG_PE1_FRC_PHY_EN, RG_PE1_PHY_EN); |
231 | } |
232 | |
233 | return 0; |
234 | } |
235 | |
236 | static int mt7621_pci_phy_power_off(struct phy *phy) |
237 | { |
238 | struct mt7621_pci_phy *mphy = phy_get_drvdata(phy); |
239 | |
240 | /* Disable PHY */ |
241 | mt7621_phy_rmw(phy: mphy, RG_PE1_FRC_PHY_REG, |
242 | RG_PE1_PHY_EN, RG_PE1_FRC_PHY_EN); |
243 | |
244 | if (mphy->has_dual_port) { |
245 | mt7621_phy_rmw(phy: mphy, RG_PE1_FRC_PHY_REG + RG_P0_TO_P1_WIDTH, |
246 | RG_PE1_PHY_EN, RG_PE1_FRC_PHY_EN); |
247 | } |
248 | |
249 | return 0; |
250 | } |
251 | |
252 | static int mt7621_pci_phy_exit(struct phy *phy) |
253 | { |
254 | return 0; |
255 | } |
256 | |
257 | static const struct phy_ops mt7621_pci_phy_ops = { |
258 | .init = mt7621_pci_phy_init, |
259 | .exit = mt7621_pci_phy_exit, |
260 | .power_on = mt7621_pci_phy_power_on, |
261 | .power_off = mt7621_pci_phy_power_off, |
262 | .owner = THIS_MODULE, |
263 | }; |
264 | |
265 | static struct phy *mt7621_pcie_phy_of_xlate(struct device *dev, |
266 | const struct of_phandle_args *args) |
267 | { |
268 | struct mt7621_pci_phy *mt7621_phy = dev_get_drvdata(dev); |
269 | |
270 | if (WARN_ON(args->args[0] >= MAX_PHYS)) |
271 | return ERR_PTR(error: -ENODEV); |
272 | |
273 | mt7621_phy->has_dual_port = args->args[0]; |
274 | |
275 | dev_dbg(dev, "PHY for 0x%px (dual port = %d)\n" , |
276 | mt7621_phy->port_base, mt7621_phy->has_dual_port); |
277 | |
278 | return mt7621_phy->phy; |
279 | } |
280 | |
281 | static const struct soc_device_attribute mt7621_pci_quirks_match[] = { |
282 | { .soc_id = "mt7621" , .revision = "E2" }, |
283 | { /* sentinel */ } |
284 | }; |
285 | |
286 | static const struct regmap_config mt7621_pci_phy_regmap_config = { |
287 | .reg_bits = 32, |
288 | .val_bits = 32, |
289 | .reg_stride = 4, |
290 | .max_register = 0x700, |
291 | }; |
292 | |
293 | static int mt7621_pci_phy_probe(struct platform_device *pdev) |
294 | { |
295 | struct device *dev = &pdev->dev; |
296 | const struct soc_device_attribute *attr; |
297 | struct phy_provider *provider; |
298 | struct mt7621_pci_phy *phy; |
299 | |
300 | phy = devm_kzalloc(dev, size: sizeof(*phy), GFP_KERNEL); |
301 | if (!phy) |
302 | return -ENOMEM; |
303 | |
304 | attr = soc_device_match(matches: mt7621_pci_quirks_match); |
305 | if (attr) |
306 | phy->bypass_pipe_rst = true; |
307 | |
308 | phy->dev = dev; |
309 | platform_set_drvdata(pdev, data: phy); |
310 | |
311 | phy->port_base = devm_platform_ioremap_resource(pdev, index: 0); |
312 | if (IS_ERR(ptr: phy->port_base)) { |
313 | dev_err(dev, "failed to remap phy regs\n" ); |
314 | return PTR_ERR(ptr: phy->port_base); |
315 | } |
316 | |
317 | phy->regmap = devm_regmap_init_mmio(phy->dev, phy->port_base, |
318 | &mt7621_pci_phy_regmap_config); |
319 | if (IS_ERR(ptr: phy->regmap)) |
320 | return PTR_ERR(ptr: phy->regmap); |
321 | |
322 | phy->phy = devm_phy_create(dev, node: dev->of_node, ops: &mt7621_pci_phy_ops); |
323 | if (IS_ERR(ptr: phy->phy)) { |
324 | dev_err(dev, "failed to create phy\n" ); |
325 | return PTR_ERR(ptr: phy->phy); |
326 | } |
327 | |
328 | phy->sys_clk = devm_clk_get(dev, NULL); |
329 | if (IS_ERR(ptr: phy->sys_clk)) { |
330 | dev_err(dev, "failed to get phy clock\n" ); |
331 | return PTR_ERR(ptr: phy->sys_clk); |
332 | } |
333 | |
334 | phy_set_drvdata(phy: phy->phy, data: phy); |
335 | |
336 | provider = devm_of_phy_provider_register(dev, mt7621_pcie_phy_of_xlate); |
337 | |
338 | return PTR_ERR_OR_ZERO(ptr: provider); |
339 | } |
340 | |
341 | static const struct of_device_id mt7621_pci_phy_ids[] = { |
342 | { .compatible = "mediatek,mt7621-pci-phy" }, |
343 | {}, |
344 | }; |
345 | MODULE_DEVICE_TABLE(of, mt7621_pci_phy_ids); |
346 | |
347 | static struct platform_driver mt7621_pci_phy_driver = { |
348 | .probe = mt7621_pci_phy_probe, |
349 | .driver = { |
350 | .name = "mt7621-pci-phy" , |
351 | .of_match_table = mt7621_pci_phy_ids, |
352 | }, |
353 | }; |
354 | |
355 | builtin_platform_driver(mt7621_pci_phy_driver); |
356 | |
357 | MODULE_AUTHOR("Sergio Paracuellos <sergio.paracuellos@gmail.com>" ); |
358 | MODULE_DESCRIPTION("MediaTek MT7621 PCIe PHY driver" ); |
359 | MODULE_LICENSE("GPL v2" ); |
360 | |