1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (C) 2018 Marvell |
4 | * |
5 | * Authors: |
6 | * Igal Liberman <igall@marvell.com> |
7 | * Miquèl Raynal <miquel.raynal@bootlin.com> |
8 | * |
9 | * Marvell A3700 UTMI PHY driver |
10 | */ |
11 | |
12 | #include <linux/io.h> |
13 | #include <linux/iopoll.h> |
14 | #include <linux/mfd/syscon.h> |
15 | #include <linux/module.h> |
16 | #include <linux/of.h> |
17 | #include <linux/phy/phy.h> |
18 | #include <linux/platform_device.h> |
19 | #include <linux/regmap.h> |
20 | |
21 | /* Armada 3700 UTMI PHY registers */ |
22 | #define USB2_PHY_PLL_CTRL_REG0 0x0 |
23 | #define PLL_REF_DIV_OFF 0 |
24 | #define PLL_REF_DIV_MASK GENMASK(6, 0) |
25 | #define PLL_REF_DIV_5 5 |
26 | #define PLL_FB_DIV_OFF 16 |
27 | #define PLL_FB_DIV_MASK GENMASK(24, 16) |
28 | #define PLL_FB_DIV_96 96 |
29 | #define PLL_SEL_LPFR_OFF 28 |
30 | #define PLL_SEL_LPFR_MASK GENMASK(29, 28) |
31 | #define PLL_READY BIT(31) |
32 | #define USB2_PHY_CAL_CTRL 0x8 |
33 | #define PHY_PLLCAL_DONE BIT(31) |
34 | #define PHY_IMPCAL_DONE BIT(23) |
35 | #define USB2_RX_CHAN_CTRL1 0x18 |
36 | #define USB2PHY_SQCAL_DONE BIT(31) |
37 | #define USB2_PHY_OTG_CTRL 0x34 |
38 | #define PHY_PU_OTG BIT(4) |
39 | #define USB2_PHY_CHRGR_DETECT 0x38 |
40 | #define PHY_CDP_EN BIT(2) |
41 | #define PHY_DCP_EN BIT(3) |
42 | #define PHY_PD_EN BIT(4) |
43 | #define PHY_PU_CHRG_DTC BIT(5) |
44 | #define PHY_CDP_DM_AUTO BIT(7) |
45 | #define PHY_ENSWITCH_DP BIT(12) |
46 | #define PHY_ENSWITCH_DM BIT(13) |
47 | |
48 | /* Armada 3700 USB miscellaneous registers */ |
49 | #define USB2_PHY_CTRL(usb32) (usb32 ? 0x20 : 0x4) |
50 | #define RB_USB2PHY_PU BIT(0) |
51 | #define USB2_DP_PULLDN_DEV_MODE BIT(5) |
52 | #define USB2_DM_PULLDN_DEV_MODE BIT(6) |
53 | #define RB_USB2PHY_SUSPM(usb32) (usb32 ? BIT(14) : BIT(7)) |
54 | |
55 | #define PLL_LOCK_DELAY_US 10000 |
56 | #define PLL_LOCK_TIMEOUT_US 1000000 |
57 | |
58 | /** |
59 | * struct mvebu_a3700_utmi_caps - PHY capabilities |
60 | * |
61 | * @usb32: Flag indicating which PHY is in use (impacts the register map): |
62 | * - The UTMI PHY wired to the USB3/USB2 controller (otg) |
63 | * - The UTMI PHY wired to the USB2 controller (host only) |
64 | * @ops: PHY operations |
65 | */ |
66 | struct mvebu_a3700_utmi_caps { |
67 | int usb32; |
68 | const struct phy_ops *ops; |
69 | }; |
70 | |
71 | /** |
72 | * struct mvebu_a3700_utmi - PHY driver data |
73 | * |
74 | * @regs: PHY registers |
75 | * @usb_misc: Regmap with USB miscellaneous registers including PHY ones |
76 | * @caps: PHY capabilities |
77 | * @phy: PHY handle |
78 | */ |
79 | struct mvebu_a3700_utmi { |
80 | void __iomem *regs; |
81 | struct regmap *usb_misc; |
82 | const struct mvebu_a3700_utmi_caps *caps; |
83 | struct phy *phy; |
84 | }; |
85 | |
86 | static int mvebu_a3700_utmi_phy_power_on(struct phy *phy) |
87 | { |
88 | struct mvebu_a3700_utmi *utmi = phy_get_drvdata(phy); |
89 | struct device *dev = &phy->dev; |
90 | int usb32 = utmi->caps->usb32; |
91 | int ret = 0; |
92 | u32 reg; |
93 | |
94 | /* |
95 | * Setup PLL. 40MHz clock used to be the default, being 25MHz now. |
96 | * See "PLL Settings for Typical REFCLK" table. |
97 | */ |
98 | reg = readl(addr: utmi->regs + USB2_PHY_PLL_CTRL_REG0); |
99 | reg &= ~(PLL_REF_DIV_MASK | PLL_FB_DIV_MASK | PLL_SEL_LPFR_MASK); |
100 | reg |= (PLL_REF_DIV_5 << PLL_REF_DIV_OFF) | |
101 | (PLL_FB_DIV_96 << PLL_FB_DIV_OFF); |
102 | writel(val: reg, addr: utmi->regs + USB2_PHY_PLL_CTRL_REG0); |
103 | |
104 | /* Enable PHY pull up and disable USB2 suspend */ |
105 | regmap_update_bits(map: utmi->usb_misc, USB2_PHY_CTRL(usb32), |
106 | RB_USB2PHY_SUSPM(usb32) | RB_USB2PHY_PU, |
107 | RB_USB2PHY_SUSPM(usb32) | RB_USB2PHY_PU); |
108 | |
109 | if (usb32) { |
110 | /* Power up OTG module */ |
111 | reg = readl(addr: utmi->regs + USB2_PHY_OTG_CTRL); |
112 | reg |= PHY_PU_OTG; |
113 | writel(val: reg, addr: utmi->regs + USB2_PHY_OTG_CTRL); |
114 | |
115 | /* Disable PHY charger detection */ |
116 | reg = readl(addr: utmi->regs + USB2_PHY_CHRGR_DETECT); |
117 | reg &= ~(PHY_CDP_EN | PHY_DCP_EN | PHY_PD_EN | PHY_PU_CHRG_DTC | |
118 | PHY_CDP_DM_AUTO | PHY_ENSWITCH_DP | PHY_ENSWITCH_DM); |
119 | writel(val: reg, addr: utmi->regs + USB2_PHY_CHRGR_DETECT); |
120 | |
121 | /* Disable PHY DP/DM pull-down (used for device mode) */ |
122 | regmap_update_bits(map: utmi->usb_misc, USB2_PHY_CTRL(usb32), |
123 | USB2_DP_PULLDN_DEV_MODE | |
124 | USB2_DM_PULLDN_DEV_MODE, val: 0); |
125 | } |
126 | |
127 | /* Wait for PLL calibration */ |
128 | ret = readl_poll_timeout(utmi->regs + USB2_PHY_CAL_CTRL, reg, |
129 | reg & PHY_PLLCAL_DONE, |
130 | PLL_LOCK_DELAY_US, PLL_LOCK_TIMEOUT_US); |
131 | if (ret) { |
132 | dev_err(dev, "Failed to end USB2 PLL calibration\n" ); |
133 | return ret; |
134 | } |
135 | |
136 | /* Wait for impedance calibration */ |
137 | ret = readl_poll_timeout(utmi->regs + USB2_PHY_CAL_CTRL, reg, |
138 | reg & PHY_IMPCAL_DONE, |
139 | PLL_LOCK_DELAY_US, PLL_LOCK_TIMEOUT_US); |
140 | if (ret) { |
141 | dev_err(dev, "Failed to end USB2 impedance calibration\n" ); |
142 | return ret; |
143 | } |
144 | |
145 | /* Wait for squelch calibration */ |
146 | ret = readl_poll_timeout(utmi->regs + USB2_RX_CHAN_CTRL1, reg, |
147 | reg & USB2PHY_SQCAL_DONE, |
148 | PLL_LOCK_DELAY_US, PLL_LOCK_TIMEOUT_US); |
149 | if (ret) { |
150 | dev_err(dev, "Failed to end USB2 unknown calibration\n" ); |
151 | return ret; |
152 | } |
153 | |
154 | /* Wait for PLL to be locked */ |
155 | ret = readl_poll_timeout(utmi->regs + USB2_PHY_PLL_CTRL_REG0, reg, |
156 | reg & PLL_READY, |
157 | PLL_LOCK_DELAY_US, PLL_LOCK_TIMEOUT_US); |
158 | if (ret) |
159 | dev_err(dev, "Failed to lock USB2 PLL\n" ); |
160 | |
161 | return ret; |
162 | } |
163 | |
164 | static int mvebu_a3700_utmi_phy_power_off(struct phy *phy) |
165 | { |
166 | struct mvebu_a3700_utmi *utmi = phy_get_drvdata(phy); |
167 | int usb32 = utmi->caps->usb32; |
168 | u32 reg; |
169 | |
170 | /* Disable PHY pull-up and enable USB2 suspend */ |
171 | reg = readl(addr: utmi->regs + USB2_PHY_CTRL(usb32)); |
172 | reg &= ~(RB_USB2PHY_PU | RB_USB2PHY_SUSPM(usb32)); |
173 | writel(val: reg, addr: utmi->regs + USB2_PHY_CTRL(usb32)); |
174 | |
175 | /* Power down OTG module */ |
176 | if (usb32) { |
177 | reg = readl(addr: utmi->regs + USB2_PHY_OTG_CTRL); |
178 | reg &= ~PHY_PU_OTG; |
179 | writel(val: reg, addr: utmi->regs + USB2_PHY_OTG_CTRL); |
180 | } |
181 | |
182 | return 0; |
183 | } |
184 | |
185 | static const struct phy_ops mvebu_a3700_utmi_phy_ops = { |
186 | .power_on = mvebu_a3700_utmi_phy_power_on, |
187 | .power_off = mvebu_a3700_utmi_phy_power_off, |
188 | .owner = THIS_MODULE, |
189 | }; |
190 | |
191 | static const struct mvebu_a3700_utmi_caps mvebu_a3700_utmi_otg_phy_caps = { |
192 | .usb32 = true, |
193 | .ops = &mvebu_a3700_utmi_phy_ops, |
194 | }; |
195 | |
196 | static const struct mvebu_a3700_utmi_caps mvebu_a3700_utmi_host_phy_caps = { |
197 | .usb32 = false, |
198 | .ops = &mvebu_a3700_utmi_phy_ops, |
199 | }; |
200 | |
201 | static const struct of_device_id mvebu_a3700_utmi_of_match[] = { |
202 | { |
203 | .compatible = "marvell,a3700-utmi-otg-phy" , |
204 | .data = &mvebu_a3700_utmi_otg_phy_caps, |
205 | }, |
206 | { |
207 | .compatible = "marvell,a3700-utmi-host-phy" , |
208 | .data = &mvebu_a3700_utmi_host_phy_caps, |
209 | }, |
210 | {}, |
211 | }; |
212 | MODULE_DEVICE_TABLE(of, mvebu_a3700_utmi_of_match); |
213 | |
214 | static int mvebu_a3700_utmi_phy_probe(struct platform_device *pdev) |
215 | { |
216 | struct device *dev = &pdev->dev; |
217 | struct mvebu_a3700_utmi *utmi; |
218 | struct phy_provider *provider; |
219 | |
220 | utmi = devm_kzalloc(dev, size: sizeof(*utmi), GFP_KERNEL); |
221 | if (!utmi) |
222 | return -ENOMEM; |
223 | |
224 | /* Get UTMI memory region */ |
225 | utmi->regs = devm_platform_ioremap_resource(pdev, index: 0); |
226 | if (IS_ERR(ptr: utmi->regs)) |
227 | return PTR_ERR(ptr: utmi->regs); |
228 | |
229 | /* Get miscellaneous Host/PHY region */ |
230 | utmi->usb_misc = syscon_regmap_lookup_by_phandle(np: dev->of_node, |
231 | property: "marvell,usb-misc-reg" ); |
232 | if (IS_ERR(ptr: utmi->usb_misc)) { |
233 | dev_err(dev, |
234 | "Missing USB misc purpose system controller\n" ); |
235 | return PTR_ERR(ptr: utmi->usb_misc); |
236 | } |
237 | |
238 | /* Retrieve PHY capabilities */ |
239 | utmi->caps = of_device_get_match_data(dev); |
240 | |
241 | /* Instantiate the PHY */ |
242 | utmi->phy = devm_phy_create(dev, NULL, ops: utmi->caps->ops); |
243 | if (IS_ERR(ptr: utmi->phy)) { |
244 | dev_err(dev, "Failed to create the UTMI PHY\n" ); |
245 | return PTR_ERR(ptr: utmi->phy); |
246 | } |
247 | |
248 | phy_set_drvdata(phy: utmi->phy, data: utmi); |
249 | |
250 | /* Ensure the PHY is powered off */ |
251 | utmi->caps->ops->power_off(utmi->phy); |
252 | |
253 | provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); |
254 | |
255 | return PTR_ERR_OR_ZERO(ptr: provider); |
256 | } |
257 | |
258 | static struct platform_driver mvebu_a3700_utmi_driver = { |
259 | .probe = mvebu_a3700_utmi_phy_probe, |
260 | .driver = { |
261 | .name = "mvebu-a3700-utmi-phy" , |
262 | .of_match_table = mvebu_a3700_utmi_of_match, |
263 | }, |
264 | }; |
265 | module_platform_driver(mvebu_a3700_utmi_driver); |
266 | |
267 | MODULE_AUTHOR("Igal Liberman <igall@marvell.com>" ); |
268 | MODULE_AUTHOR("Miquel Raynal <miquel.raynal@bootlin.com>" ); |
269 | MODULE_DESCRIPTION("Marvell EBU A3700 UTMI PHY driver" ); |
270 | MODULE_LICENSE("GPL v2" ); |
271 | |