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 | if (imx8_phy->drvdata->variant != IMX8MM) { |
114 | writel(AUX_PLL_REFCLK_SEL_SYS_PLL, |
115 | addr: imx8_phy->base + IMX8MM_PCIE_PHY_CMN_REG063); |
116 | } |
117 | val = ANA_AUX_RX_TX_SEL_TX | ANA_AUX_TX_TERM; |
118 | writel(val: val | ANA_AUX_RX_TERM_GND_EN, |
119 | addr: imx8_phy->base + IMX8MM_PCIE_PHY_CMN_REG064); |
120 | writel(ANA_AUX_RX_TERM | ANA_AUX_TX_LVL, |
121 | addr: imx8_phy->base + IMX8MM_PCIE_PHY_CMN_REG065); |
122 | } |
123 | |
124 | /* Set AUX_EN_OVERRIDE 1'b0, when the CLKREQ# isn't hooked */ |
125 | regmap_update_bits(map: imx8_phy->iomuxc_gpr, IOMUXC_GPR14, |
126 | IMX8MM_GPR_PCIE_AUX_EN_OVERRIDE, |
127 | val: imx8_phy->clkreq_unused ? |
128 | 0 : IMX8MM_GPR_PCIE_AUX_EN_OVERRIDE); |
129 | regmap_update_bits(map: imx8_phy->iomuxc_gpr, IOMUXC_GPR14, |
130 | IMX8MM_GPR_PCIE_AUX_EN, |
131 | IMX8MM_GPR_PCIE_AUX_EN); |
132 | regmap_update_bits(map: imx8_phy->iomuxc_gpr, IOMUXC_GPR14, |
133 | IMX8MM_GPR_PCIE_POWER_OFF, val: 0); |
134 | regmap_update_bits(map: imx8_phy->iomuxc_gpr, IOMUXC_GPR14, |
135 | IMX8MM_GPR_PCIE_SSC_EN, val: 0); |
136 | |
137 | regmap_update_bits(map: imx8_phy->iomuxc_gpr, IOMUXC_GPR14, |
138 | IMX8MM_GPR_PCIE_REF_CLK_SEL, |
139 | val: pad_mode == IMX8_PCIE_REFCLK_PAD_INPUT ? |
140 | IMX8MM_GPR_PCIE_REF_CLK_EXT : |
141 | IMX8MM_GPR_PCIE_REF_CLK_PLL); |
142 | usleep_range(min: 100, max: 200); |
143 | |
144 | reset_control_deassert(rstc: imx8_phy->perst); |
145 | reset_control_deassert(rstc: imx8_phy->reset); |
146 | usleep_range(min: 200, max: 500); |
147 | |
148 | /* Do the PHY common block reset */ |
149 | regmap_update_bits(map: imx8_phy->iomuxc_gpr, IOMUXC_GPR14, |
150 | IMX8MM_GPR_PCIE_CMN_RST, |
151 | IMX8MM_GPR_PCIE_CMN_RST); |
152 | |
153 | /* Polling to check the phy is ready or not. */ |
154 | ret = readl_poll_timeout(imx8_phy->base + IMX8MM_PCIE_PHY_CMN_REG075, |
155 | val, val == ANA_PLL_DONE, 10, 20000); |
156 | return ret; |
157 | } |
158 | |
159 | static int imx8_pcie_phy_power_off(struct phy *phy) |
160 | { |
161 | struct imx8_pcie_phy *imx8_phy = phy_get_drvdata(phy); |
162 | |
163 | reset_control_assert(rstc: imx8_phy->reset); |
164 | reset_control_assert(rstc: imx8_phy->perst); |
165 | |
166 | return 0; |
167 | } |
168 | |
169 | static int imx8_pcie_phy_init(struct phy *phy) |
170 | { |
171 | struct imx8_pcie_phy *imx8_phy = phy_get_drvdata(phy); |
172 | |
173 | return clk_prepare_enable(clk: imx8_phy->clk); |
174 | } |
175 | |
176 | static int imx8_pcie_phy_exit(struct phy *phy) |
177 | { |
178 | struct imx8_pcie_phy *imx8_phy = phy_get_drvdata(phy); |
179 | |
180 | clk_disable_unprepare(clk: imx8_phy->clk); |
181 | |
182 | return 0; |
183 | } |
184 | |
185 | static const struct phy_ops imx8_pcie_phy_ops = { |
186 | .init = imx8_pcie_phy_init, |
187 | .exit = imx8_pcie_phy_exit, |
188 | .power_on = imx8_pcie_phy_power_on, |
189 | .power_off = imx8_pcie_phy_power_off, |
190 | .owner = THIS_MODULE, |
191 | }; |
192 | |
193 | static const struct imx8_pcie_phy_drvdata imx8mm_drvdata = { |
194 | .gpr = "fsl,imx8mm-iomuxc-gpr" , |
195 | .variant = IMX8MM, |
196 | }; |
197 | |
198 | static const struct imx8_pcie_phy_drvdata imx8mp_drvdata = { |
199 | .gpr = "fsl,imx8mp-iomuxc-gpr" , |
200 | .variant = IMX8MP, |
201 | }; |
202 | |
203 | static const struct of_device_id imx8_pcie_phy_of_match[] = { |
204 | {.compatible = "fsl,imx8mm-pcie-phy" , .data = &imx8mm_drvdata, }, |
205 | {.compatible = "fsl,imx8mp-pcie-phy" , .data = &imx8mp_drvdata, }, |
206 | { }, |
207 | }; |
208 | MODULE_DEVICE_TABLE(of, imx8_pcie_phy_of_match); |
209 | |
210 | static int imx8_pcie_phy_probe(struct platform_device *pdev) |
211 | { |
212 | struct phy_provider *phy_provider; |
213 | struct device *dev = &pdev->dev; |
214 | struct device_node *np = dev->of_node; |
215 | struct imx8_pcie_phy *imx8_phy; |
216 | |
217 | imx8_phy = devm_kzalloc(dev, size: sizeof(*imx8_phy), GFP_KERNEL); |
218 | if (!imx8_phy) |
219 | return -ENOMEM; |
220 | |
221 | imx8_phy->drvdata = of_device_get_match_data(dev); |
222 | |
223 | /* get PHY refclk pad mode */ |
224 | of_property_read_u32(np, propname: "fsl,refclk-pad-mode" , |
225 | out_value: &imx8_phy->refclk_pad_mode); |
226 | |
227 | if (of_property_read_u32(np, propname: "fsl,tx-deemph-gen1" , |
228 | out_value: &imx8_phy->tx_deemph_gen1)) |
229 | imx8_phy->tx_deemph_gen1 = 0; |
230 | |
231 | if (of_property_read_u32(np, propname: "fsl,tx-deemph-gen2" , |
232 | out_value: &imx8_phy->tx_deemph_gen2)) |
233 | imx8_phy->tx_deemph_gen2 = 0; |
234 | |
235 | if (of_property_read_bool(np, propname: "fsl,clkreq-unsupported" )) |
236 | imx8_phy->clkreq_unused = true; |
237 | else |
238 | imx8_phy->clkreq_unused = false; |
239 | |
240 | imx8_phy->clk = devm_clk_get(dev, id: "ref" ); |
241 | if (IS_ERR(ptr: imx8_phy->clk)) |
242 | return dev_err_probe(dev, err: PTR_ERR(ptr: imx8_phy->clk), |
243 | fmt: "failed to get imx pcie phy clock\n" ); |
244 | |
245 | /* Grab GPR config register range */ |
246 | imx8_phy->iomuxc_gpr = |
247 | syscon_regmap_lookup_by_compatible(s: imx8_phy->drvdata->gpr); |
248 | if (IS_ERR(ptr: imx8_phy->iomuxc_gpr)) |
249 | return dev_err_probe(dev, err: PTR_ERR(ptr: imx8_phy->iomuxc_gpr), |
250 | fmt: "unable to find iomuxc registers\n" ); |
251 | |
252 | imx8_phy->reset = devm_reset_control_get_exclusive(dev, id: "pciephy" ); |
253 | if (IS_ERR(ptr: imx8_phy->reset)) |
254 | return dev_err_probe(dev, err: PTR_ERR(ptr: imx8_phy->reset), |
255 | fmt: "Failed to get PCIEPHY reset control\n" ); |
256 | |
257 | if (imx8_phy->drvdata->variant == IMX8MP) { |
258 | imx8_phy->perst = |
259 | devm_reset_control_get_exclusive(dev, id: "perst" ); |
260 | if (IS_ERR(ptr: imx8_phy->perst)) |
261 | return dev_err_probe(dev, err: PTR_ERR(ptr: imx8_phy->perst), |
262 | fmt: "Failed to get PCIE PHY PERST control\n" ); |
263 | } |
264 | |
265 | imx8_phy->base = devm_platform_ioremap_resource(pdev, index: 0); |
266 | if (IS_ERR(ptr: imx8_phy->base)) |
267 | return PTR_ERR(ptr: imx8_phy->base); |
268 | |
269 | imx8_phy->phy = devm_phy_create(dev, NULL, ops: &imx8_pcie_phy_ops); |
270 | if (IS_ERR(ptr: imx8_phy->phy)) |
271 | return PTR_ERR(ptr: imx8_phy->phy); |
272 | |
273 | phy_set_drvdata(phy: imx8_phy->phy, data: imx8_phy); |
274 | |
275 | phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); |
276 | |
277 | return PTR_ERR_OR_ZERO(ptr: phy_provider); |
278 | } |
279 | |
280 | static struct platform_driver imx8_pcie_phy_driver = { |
281 | .probe = imx8_pcie_phy_probe, |
282 | .driver = { |
283 | .name = "imx8-pcie-phy" , |
284 | .of_match_table = imx8_pcie_phy_of_match, |
285 | } |
286 | }; |
287 | module_platform_driver(imx8_pcie_phy_driver); |
288 | |
289 | MODULE_DESCRIPTION("FSL IMX8 PCIE PHY driver" ); |
290 | MODULE_LICENSE("GPL v2" ); |
291 | |