1 // SPDX-License-Identifier: GPL-2.0
3 * Intel LGM USB PHY driver
5 * Copyright (C) 2020 Intel Corporation.
8 #include <linux/bitfield.h>
9 #include <linux/delay.h>
10 #include <linux/iopoll.h>
11 #include <linux/module.h>
13 #include <linux/platform_device.h>
14 #include <linux/regulator/consumer.h>
15 #include <linux/reset.h>
16 #include <linux/usb/phy.h>
17 #include <linux/workqueue.h>
19 #define CTRL1_OFFSET 0x14
20 #define SRAM_EXT_LD_DONE BIT(25)
21 #define SRAM_INIT_DONE BIT(26)
23 #define TCPC_OFFSET 0x1014
24 #define TCPC_MUX_CTL GENMASK(1, 0)
29 #define TCPC_FLIPPED BIT(2)
30 #define TCPC_LOW_POWER_EN BIT(3)
31 #define TCPC_VALID BIT(4)
33 (TCPC_VALID | FIELD_PREP(TCPC_MUX_CTL, MUX_USB))
34 #define TCPC_DISCONN \
35 (TCPC_VALID | FIELD_PREP(TCPC_MUX_CTL, MUX_NC) | TCPC_LOW_POWER_EN)
37 static const char *const PHY_RESETS
[] = { "phy31", "phy", };
38 static const char *const CTL_RESETS
[] = { "apb", "ctrl", };
41 struct reset_control
*resets
[ARRAY_SIZE(PHY_RESETS
)];
42 struct regulator
*vbus
;
43 struct work_struct wk
;
46 bool regulator_enabled
;
51 static int get_flipped(struct tca_apb
*ta
, bool *flipped
)
53 union extcon_property_value property
;
56 ret
= extcon_get_property(ta
->phy
.edev
, EXTCON_USB_HOST
,
57 EXTCON_PROP_USB_TYPEC_POLARITY
, &property
);
59 dev_err(ta
->phy
.dev
, "no polarity property from extcon\n");
63 *flipped
= property
.intval
;
68 static int phy_init(struct usb_phy
*phy
)
70 struct tca_apb
*ta
= container_of(phy
, struct tca_apb
, phy
);
71 void __iomem
*ctrl1
= phy
->io_priv
+ CTRL1_OFFSET
;
74 if (ta
->phy_initialized
)
77 for (i
= 0; i
< ARRAY_SIZE(PHY_RESETS
); i
++)
78 reset_control_deassert(ta
->resets
[i
]);
80 ret
= readl_poll_timeout(ctrl1
, val
, val
& SRAM_INIT_DONE
, 10, 10 * 1000);
82 dev_err(ta
->phy
.dev
, "SRAM init failed, 0x%x\n", val
);
86 writel(readl(ctrl1
) | SRAM_EXT_LD_DONE
, ctrl1
);
88 ta
->phy_initialized
= true;
90 writel(TCPC_CONN
, ta
->phy
.io_priv
+ TCPC_OFFSET
);
91 return phy
->set_vbus(phy
, true);
94 schedule_work(&ta
->wk
);
99 static void phy_shutdown(struct usb_phy
*phy
)
101 struct tca_apb
*ta
= container_of(phy
, struct tca_apb
, phy
);
104 if (!ta
->phy_initialized
)
107 ta
->phy_initialized
= false;
109 ta
->phy
.set_vbus(&ta
->phy
, false);
111 ta
->connected
= false;
112 writel(TCPC_DISCONN
, ta
->phy
.io_priv
+ TCPC_OFFSET
);
114 for (i
= 0; i
< ARRAY_SIZE(PHY_RESETS
); i
++)
115 reset_control_assert(ta
->resets
[i
]);
118 static int phy_set_vbus(struct usb_phy
*phy
, int on
)
120 struct tca_apb
*ta
= container_of(phy
, struct tca_apb
, phy
);
123 if (!!on
== ta
->regulator_enabled
)
127 ret
= regulator_enable(ta
->vbus
);
129 ret
= regulator_disable(ta
->vbus
);
132 ta
->regulator_enabled
= on
;
134 dev_dbg(ta
->phy
.dev
, "set vbus: %d\n", on
);
138 static void tca_work(struct work_struct
*work
)
140 struct tca_apb
*ta
= container_of(work
, struct tca_apb
, wk
);
142 bool flipped
= false;
146 ret
= get_flipped(ta
, &flipped
);
150 connected
= extcon_get_state(ta
->phy
.edev
, EXTCON_USB_HOST
);
151 if (connected
== ta
->connected
)
154 ta
->connected
= connected
;
159 dev_dbg(ta
->phy
.dev
, "connected%s\n", flipped
? " flipped" : "");
162 dev_dbg(ta
->phy
.dev
, "disconnected\n");
165 writel(val
, ta
->phy
.io_priv
+ TCPC_OFFSET
);
167 ret
= ta
->phy
.set_vbus(&ta
->phy
, connected
);
169 dev_err(ta
->phy
.dev
, "failed to set VBUS\n");
172 static int id_notifier(struct notifier_block
*nb
, unsigned long event
, void *ptr
)
174 struct tca_apb
*ta
= container_of(nb
, struct tca_apb
, phy
.id_nb
);
176 if (ta
->phy_initialized
)
177 schedule_work(&ta
->wk
);
182 static int vbus_notifier(struct notifier_block
*nb
, unsigned long evnt
, void *ptr
)
187 static int phy_probe(struct platform_device
*pdev
)
189 struct reset_control
*resets
[ARRAY_SIZE(CTL_RESETS
)];
190 struct device
*dev
= &pdev
->dev
;
195 ta
= devm_kzalloc(dev
, sizeof(*ta
), GFP_KERNEL
);
199 platform_set_drvdata(pdev
, ta
);
200 INIT_WORK(&ta
->wk
, tca_work
);
204 phy
->label
= dev_name(dev
);
205 phy
->type
= USB_PHY_TYPE_USB3
;
206 phy
->init
= phy_init
;
207 phy
->shutdown
= phy_shutdown
;
208 phy
->set_vbus
= phy_set_vbus
;
209 phy
->id_nb
.notifier_call
= id_notifier
;
210 phy
->vbus_nb
.notifier_call
= vbus_notifier
;
212 phy
->io_priv
= devm_platform_ioremap_resource(pdev
, 0);
213 if (IS_ERR(phy
->io_priv
))
214 return PTR_ERR(phy
->io_priv
);
216 ta
->vbus
= devm_regulator_get(dev
, "vbus");
217 if (IS_ERR(ta
->vbus
))
218 return PTR_ERR(ta
->vbus
);
220 for (i
= 0; i
< ARRAY_SIZE(CTL_RESETS
); i
++) {
221 resets
[i
] = devm_reset_control_get_exclusive(dev
, CTL_RESETS
[i
]);
222 if (IS_ERR(resets
[i
])) {
223 dev_err(dev
, "%s reset not found\n", CTL_RESETS
[i
]);
224 return PTR_ERR(resets
[i
]);
228 for (i
= 0; i
< ARRAY_SIZE(PHY_RESETS
); i
++) {
229 ta
->resets
[i
] = devm_reset_control_get_exclusive(dev
, PHY_RESETS
[i
]);
230 if (IS_ERR(ta
->resets
[i
])) {
231 dev_err(dev
, "%s reset not found\n", PHY_RESETS
[i
]);
232 return PTR_ERR(ta
->resets
[i
]);
236 for (i
= 0; i
< ARRAY_SIZE(CTL_RESETS
); i
++)
237 reset_control_assert(resets
[i
]);
239 for (i
= 0; i
< ARRAY_SIZE(PHY_RESETS
); i
++)
240 reset_control_assert(ta
->resets
[i
]);
242 * Out-of-band reset of the controller after PHY reset will cause
243 * controller malfunctioning, so we should use in-band controller
244 * reset only and leave the controller de-asserted here.
246 for (i
= 0; i
< ARRAY_SIZE(CTL_RESETS
); i
++)
247 reset_control_deassert(resets
[i
]);
249 /* Need to wait at least 20us after de-assert the controller */
250 usleep_range(20, 100);
252 return usb_add_phy_dev(phy
);
255 static int phy_remove(struct platform_device
*pdev
)
257 struct tca_apb
*ta
= platform_get_drvdata(pdev
);
259 usb_remove_phy(&ta
->phy
);
264 static const struct of_device_id intel_usb_phy_dt_ids
[] = {
265 { .compatible
= "intel,lgm-usb-phy" },
268 MODULE_DEVICE_TABLE(of
, intel_usb_phy_dt_ids
);
270 static struct platform_driver lgm_phy_driver
= {
272 .name
= "lgm-usb-phy",
273 .of_match_table
= intel_usb_phy_dt_ids
,
276 .remove
= phy_remove
,
279 module_platform_driver(lgm_phy_driver
);
281 MODULE_DESCRIPTION("Intel LGM USB PHY driver");
282 MODULE_AUTHOR("Li Yin <yin1.li@intel.com>");
283 MODULE_AUTHOR("Vadivel Murugan R <vadivel.muruganx.ramuthevar@linux.intel.com>");
284 MODULE_LICENSE("GPL v2");