1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Copyright 2021 NXP |
4 | */ |
5 | |
6 | #include <linux/bitfield.h> |
7 | #include <linux/clk.h> |
8 | #include <linux/delay.h> |
9 | #include <linux/io.h> |
10 | #include <linux/iopoll.h> |
11 | #include <linux/mfd/syscon.h> |
12 | #include <linux/mfd/syscon/imx7-iomuxc-gpr.h> |
13 | #include <linux/module.h> |
14 | #include <linux/of.h> |
15 | #include <linux/phy/phy.h> |
16 | #include <linux/platform_device.h> |
17 | #include <linux/regmap.h> |
18 | #include <linux/reset.h> |
19 | |
20 | #include <dt-bindings/phy/phy-imx8-pcie.h> |
21 | |
22 | #define IMX8MM_PCIE_PHY_CMN_REG061 0x184 |
23 | #define ANA_PLL_CLK_OUT_TO_EXT_IO_EN BIT(0) |
24 | #define IMX8MM_PCIE_PHY_CMN_REG062 0x188 |
25 | #define ANA_PLL_CLK_OUT_TO_EXT_IO_SEL BIT(3) |
26 | #define IMX8MM_PCIE_PHY_CMN_REG063 0x18C |
27 | #define AUX_PLL_REFCLK_SEL_SYS_PLL GENMASK(7, 6) |
28 | #define IMX8MM_PCIE_PHY_CMN_REG064 0x190 |
29 | #define ANA_AUX_RX_TX_SEL_TX BIT(7) |
30 | #define ANA_AUX_RX_TERM_GND_EN BIT(3) |
31 | #define ANA_AUX_TX_TERM BIT(2) |
32 | #define IMX8MM_PCIE_PHY_CMN_REG065 0x194 |
33 | #define ANA_AUX_RX_TERM (BIT(7) | BIT(4)) |
34 | #define ANA_AUX_TX_LVL GENMASK(3, 0) |
35 | #define IMX8MM_PCIE_PHY_CMN_REG075 0x1D4 |
36 | #define ANA_PLL_DONE 0x3 |
37 | #define PCIE_PHY_TRSV_REG5 0x414 |
38 | #define PCIE_PHY_TRSV_REG6 0x418 |
39 | |
40 | #define IMX8MM_GPR_PCIE_REF_CLK_SEL GENMASK(25, 24) |
41 | #define IMX8MM_GPR_PCIE_REF_CLK_PLL FIELD_PREP(IMX8MM_GPR_PCIE_REF_CLK_SEL, 0x3) |
42 | #define IMX8MM_GPR_PCIE_REF_CLK_EXT FIELD_PREP(IMX8MM_GPR_PCIE_REF_CLK_SEL, 0x2) |
43 | #define IMX8MM_GPR_PCIE_AUX_EN BIT(19) |
44 | #define IMX8MM_GPR_PCIE_CMN_RST BIT(18) |
45 | #define IMX8MM_GPR_PCIE_POWER_OFF BIT(17) |
46 | #define IMX8MM_GPR_PCIE_SSC_EN BIT(16) |
47 | #define IMX8MM_GPR_PCIE_AUX_EN_OVERRIDE BIT(9) |
48 | |
49 | enum imx8_pcie_phy_type { |
50 | IMX8MM, |
51 | IMX8MP, |
52 | }; |
53 | |
54 | struct imx8_pcie_phy_drvdata { |
55 | const char *gpr; |
56 | enum imx8_pcie_phy_type variant; |
57 | }; |
58 | |
59 | struct imx8_pcie_phy { |
60 | void __iomem *base; |
61 | struct clk *clk; |
62 | struct phy *phy; |
63 | struct regmap *iomuxc_gpr; |
64 | struct reset_control *perst; |
65 | struct reset_control *reset; |
66 | u32 refclk_pad_mode; |
67 | u32 tx_deemph_gen1; |
68 | u32 tx_deemph_gen2; |
69 | bool clkreq_unused; |
70 | const struct imx8_pcie_phy_drvdata *drvdata; |
71 | }; |
72 | |
73 | static int imx8_pcie_phy_power_on(struct phy *phy) |
74 | { |
75 | int ret; |
76 | u32 val, pad_mode; |
77 | struct imx8_pcie_phy *imx8_phy = phy_get_drvdata(phy); |
78 | |
79 | pad_mode = imx8_phy->refclk_pad_mode; |
80 | switch (imx8_phy->drvdata->variant) { |
81 | case IMX8MM: |
82 | reset_control_assert(rstc: imx8_phy->reset); |
83 | |
84 | /* Tune PHY de-emphasis setting to pass PCIe compliance. */ |
85 | if (imx8_phy->tx_deemph_gen1) |
86 | writel(val: imx8_phy->tx_deemph_gen1, |
87 | addr: imx8_phy->base + PCIE_PHY_TRSV_REG5); |
88 | if (imx8_phy->tx_deemph_gen2) |
89 | writel(val: imx8_phy->tx_deemph_gen2, |
90 | addr: imx8_phy->base + PCIE_PHY_TRSV_REG6); |
91 | break; |
92 | case IMX8MP: /* Do nothing. */ |
93 | break; |
94 | } |
95 | |
96 | if (pad_mode == IMX8_PCIE_REFCLK_PAD_INPUT || |
97 | pad_mode == IMX8_PCIE_REFCLK_PAD_UNUSED) { |
98 | /* Configure the pad as input */ |
99 | val = readl(addr: imx8_phy->base + IMX8MM_PCIE_PHY_CMN_REG061); |
100 | writel(val: val & ~ANA_PLL_CLK_OUT_TO_EXT_IO_EN, |
101 | addr: imx8_phy->base + IMX8MM_PCIE_PHY_CMN_REG061); |
102 | } else { |
103 | /* Configure the PHY to output the refclock via pad */ |
104 | writel(ANA_PLL_CLK_OUT_TO_EXT_IO_EN, |
105 | addr: imx8_phy->base + IMX8MM_PCIE_PHY_CMN_REG061); |
106 | } |
107 | |
108 | if (pad_mode == IMX8_PCIE_REFCLK_PAD_OUTPUT || |
109 | pad_mode == IMX8_PCIE_REFCLK_PAD_UNUSED) { |
110 | /* Source clock from SoC internal PLL */ |
111 | writel(ANA_PLL_CLK_OUT_TO_EXT_IO_SEL, |
112 | addr: imx8_phy->base + IMX8MM_PCIE_PHY_CMN_REG062); |
113 | writel(AUX_PLL_REFCLK_SEL_SYS_PLL, |
114 | addr: imx8_phy->base + IMX8MM_PCIE_PHY_CMN_REG063); |
115 | val = ANA_AUX_RX_TX_SEL_TX | ANA_AUX_TX_TERM; |
116 | writel(val: val | ANA_AUX_RX_TERM_GND_EN, |
117 | addr: imx8_phy->base + IMX8MM_PCIE_PHY_CMN_REG064); |
118 | writel(ANA_AUX_RX_TERM | ANA_AUX_TX_LVL, |
119 | addr: imx8_phy->base + IMX8MM_PCIE_PHY_CMN_REG065); |
120 | } |
121 | |
122 | /* Set AUX_EN_OVERRIDE 1'b0, when the CLKREQ# isn't hooked */ |
123 | regmap_update_bits(map: imx8_phy->iomuxc_gpr, IOMUXC_GPR14, |
124 | IMX8MM_GPR_PCIE_AUX_EN_OVERRIDE, |
125 | val: imx8_phy->clkreq_unused ? |
126 | 0 : IMX8MM_GPR_PCIE_AUX_EN_OVERRIDE); |
127 | regmap_update_bits(map: imx8_phy->iomuxc_gpr, IOMUXC_GPR14, |
128 | IMX8MM_GPR_PCIE_AUX_EN, |
129 | IMX8MM_GPR_PCIE_AUX_EN); |
130 | regmap_update_bits(map: imx8_phy->iomuxc_gpr, IOMUXC_GPR14, |
131 | IMX8MM_GPR_PCIE_POWER_OFF, val: 0); |
132 | regmap_update_bits(map: imx8_phy->iomuxc_gpr, IOMUXC_GPR14, |
133 | IMX8MM_GPR_PCIE_SSC_EN, val: 0); |
134 | |
135 | regmap_update_bits(map: imx8_phy->iomuxc_gpr, IOMUXC_GPR14, |
136 | IMX8MM_GPR_PCIE_REF_CLK_SEL, |
137 | val: pad_mode == IMX8_PCIE_REFCLK_PAD_INPUT ? |
138 | IMX8MM_GPR_PCIE_REF_CLK_EXT : |
139 | IMX8MM_GPR_PCIE_REF_CLK_PLL); |
140 | usleep_range(min: 100, max: 200); |
141 | |
142 | /* Do the PHY common block reset */ |
143 | regmap_update_bits(map: imx8_phy->iomuxc_gpr, IOMUXC_GPR14, |
144 | IMX8MM_GPR_PCIE_CMN_RST, |
145 | IMX8MM_GPR_PCIE_CMN_RST); |
146 | |
147 | switch (imx8_phy->drvdata->variant) { |
148 | case IMX8MP: |
149 | reset_control_deassert(rstc: imx8_phy->perst); |
150 | fallthrough; |
151 | case IMX8MM: |
152 | reset_control_deassert(rstc: imx8_phy->reset); |
153 | usleep_range(min: 200, max: 500); |
154 | break; |
155 | } |
156 | |
157 | /* Polling to check the phy is ready or not. */ |
158 | ret = readl_poll_timeout(imx8_phy->base + IMX8MM_PCIE_PHY_CMN_REG075, |
159 | val, val == ANA_PLL_DONE, 10, 20000); |
160 | return ret; |
161 | } |
162 | |
163 | static int imx8_pcie_phy_init(struct phy *phy) |
164 | { |
165 | struct imx8_pcie_phy *imx8_phy = phy_get_drvdata(phy); |
166 | |
167 | return clk_prepare_enable(clk: imx8_phy->clk); |
168 | } |
169 | |
170 | static int imx8_pcie_phy_exit(struct phy *phy) |
171 | { |
172 | struct imx8_pcie_phy *imx8_phy = phy_get_drvdata(phy); |
173 | |
174 | clk_disable_unprepare(clk: imx8_phy->clk); |
175 | |
176 | return 0; |
177 | } |
178 | |
179 | static const struct phy_ops imx8_pcie_phy_ops = { |
180 | .init = imx8_pcie_phy_init, |
181 | .exit = imx8_pcie_phy_exit, |
182 | .power_on = imx8_pcie_phy_power_on, |
183 | .owner = THIS_MODULE, |
184 | }; |
185 | |
186 | static const struct imx8_pcie_phy_drvdata imx8mm_drvdata = { |
187 | .gpr = "fsl,imx8mm-iomuxc-gpr" , |
188 | .variant = IMX8MM, |
189 | }; |
190 | |
191 | static const struct imx8_pcie_phy_drvdata imx8mp_drvdata = { |
192 | .gpr = "fsl,imx8mp-iomuxc-gpr" , |
193 | .variant = IMX8MP, |
194 | }; |
195 | |
196 | static const struct of_device_id imx8_pcie_phy_of_match[] = { |
197 | {.compatible = "fsl,imx8mm-pcie-phy" , .data = &imx8mm_drvdata, }, |
198 | {.compatible = "fsl,imx8mp-pcie-phy" , .data = &imx8mp_drvdata, }, |
199 | { }, |
200 | }; |
201 | MODULE_DEVICE_TABLE(of, imx8_pcie_phy_of_match); |
202 | |
203 | static int imx8_pcie_phy_probe(struct platform_device *pdev) |
204 | { |
205 | struct phy_provider *phy_provider; |
206 | struct device *dev = &pdev->dev; |
207 | struct device_node *np = dev->of_node; |
208 | struct imx8_pcie_phy *imx8_phy; |
209 | |
210 | imx8_phy = devm_kzalloc(dev, size: sizeof(*imx8_phy), GFP_KERNEL); |
211 | if (!imx8_phy) |
212 | return -ENOMEM; |
213 | |
214 | imx8_phy->drvdata = of_device_get_match_data(dev); |
215 | |
216 | /* get PHY refclk pad mode */ |
217 | of_property_read_u32(np, propname: "fsl,refclk-pad-mode" , |
218 | out_value: &imx8_phy->refclk_pad_mode); |
219 | |
220 | if (of_property_read_u32(np, propname: "fsl,tx-deemph-gen1" , |
221 | out_value: &imx8_phy->tx_deemph_gen1)) |
222 | imx8_phy->tx_deemph_gen1 = 0; |
223 | |
224 | if (of_property_read_u32(np, propname: "fsl,tx-deemph-gen2" , |
225 | out_value: &imx8_phy->tx_deemph_gen2)) |
226 | imx8_phy->tx_deemph_gen2 = 0; |
227 | |
228 | if (of_property_read_bool(np, propname: "fsl,clkreq-unsupported" )) |
229 | imx8_phy->clkreq_unused = true; |
230 | else |
231 | imx8_phy->clkreq_unused = false; |
232 | |
233 | imx8_phy->clk = devm_clk_get(dev, id: "ref" ); |
234 | if (IS_ERR(ptr: imx8_phy->clk)) { |
235 | dev_err(dev, "failed to get imx pcie phy clock\n" ); |
236 | return PTR_ERR(ptr: imx8_phy->clk); |
237 | } |
238 | |
239 | /* Grab GPR config register range */ |
240 | imx8_phy->iomuxc_gpr = |
241 | syscon_regmap_lookup_by_compatible(s: imx8_phy->drvdata->gpr); |
242 | if (IS_ERR(ptr: imx8_phy->iomuxc_gpr)) { |
243 | dev_err(dev, "unable to find iomuxc registers\n" ); |
244 | return PTR_ERR(ptr: imx8_phy->iomuxc_gpr); |
245 | } |
246 | |
247 | imx8_phy->reset = devm_reset_control_get_exclusive(dev, id: "pciephy" ); |
248 | if (IS_ERR(ptr: imx8_phy->reset)) { |
249 | dev_err(dev, "Failed to get PCIEPHY reset control\n" ); |
250 | return PTR_ERR(ptr: imx8_phy->reset); |
251 | } |
252 | |
253 | if (imx8_phy->drvdata->variant == IMX8MP) { |
254 | imx8_phy->perst = |
255 | devm_reset_control_get_exclusive(dev, id: "perst" ); |
256 | if (IS_ERR(ptr: imx8_phy->perst)) |
257 | return dev_err_probe(dev, err: PTR_ERR(ptr: imx8_phy->perst), |
258 | fmt: "Failed to get PCIE PHY PERST control\n" ); |
259 | } |
260 | |
261 | imx8_phy->base = devm_platform_ioremap_resource(pdev, index: 0); |
262 | if (IS_ERR(ptr: imx8_phy->base)) |
263 | return PTR_ERR(ptr: imx8_phy->base); |
264 | |
265 | imx8_phy->phy = devm_phy_create(dev, NULL, ops: &imx8_pcie_phy_ops); |
266 | if (IS_ERR(ptr: imx8_phy->phy)) |
267 | return PTR_ERR(ptr: imx8_phy->phy); |
268 | |
269 | phy_set_drvdata(phy: imx8_phy->phy, data: imx8_phy); |
270 | |
271 | phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); |
272 | |
273 | return PTR_ERR_OR_ZERO(ptr: phy_provider); |
274 | } |
275 | |
276 | static struct platform_driver imx8_pcie_phy_driver = { |
277 | .probe = imx8_pcie_phy_probe, |
278 | .driver = { |
279 | .name = "imx8-pcie-phy" , |
280 | .of_match_table = imx8_pcie_phy_of_match, |
281 | } |
282 | }; |
283 | module_platform_driver(imx8_pcie_phy_driver); |
284 | |
285 | MODULE_DESCRIPTION("FSL IMX8 PCIE PHY driver" ); |
286 | MODULE_LICENSE("GPL v2" ); |
287 | |