1 From ecf210d50915efb265d93f8b84cfd53a492ffefc Mon Sep 17 00:00:00 2001
3 Date: Sun, 7 Nov 2021 19:24:40 +0100
4 Subject: usb: typec: typec-extcon: Add typec -> extcon bridge driver
6 This bridge connects standard Type C port interfaces for controling
7 muxes, switches and usb roles to muxes, switches and usb role
8 drivers controlled via extcon interface.
10 Signed-off-by: Ondrej Jirman <megi@xff.cz>
12 drivers/usb/typec/Kconfig | 7 +
13 drivers/usb/typec/Makefile | 1 +
14 drivers/usb/typec/typec-extcon.c | 332 +++++++++++++++++++++++++++++++++++++++
15 3 files changed, 340 insertions(+)
16 create mode 100644 drivers/usb/typec/typec-extcon.c
18 diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig
19 index 2f80c2792dbd..40a2934691d6 100644
20 --- a/drivers/usb/typec/Kconfig
21 +++ b/drivers/usb/typec/Kconfig
22 @@ -110,6 +110,13 @@ config TYPEC_WUSB3801
23 If you choose to build this driver as a dynamically linked module, the
24 module will be called wusb3801.ko.
27 + tristate "Type-C switch/mux -> extcon interface bridge driver"
28 + depends on USB_ROLE_SWITCH
30 + Say Y or M here if your system needs bridging between typec class
31 + and extcon interfaces.
33 source "drivers/usb/typec/mux/Kconfig"
35 source "drivers/usb/typec/altmodes/Kconfig"
36 diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile
37 index 7a368fea61bc..fe4bf3b8ff60 100644
38 --- a/drivers/usb/typec/Makefile
39 +++ b/drivers/usb/typec/Makefile
40 @@ -11,4 +11,5 @@ obj-$(CONFIG_TYPEC_HD3SS3220) += hd3ss3220.o
41 obj-$(CONFIG_TYPEC_STUSB160X) += stusb160x.o
42 obj-$(CONFIG_TYPEC_RT1719) += rt1719.o
43 obj-$(CONFIG_TYPEC_WUSB3801) += wusb3801.o
44 +obj-$(CONFIG_TYPEC_EXTCON) += typec-extcon.o
45 obj-$(CONFIG_TYPEC) += mux/
46 diff --git a/drivers/usb/typec/typec-extcon.c b/drivers/usb/typec/typec-extcon.c
48 index 000000000000..e460f35c8390
50 +++ b/drivers/usb/typec/typec-extcon.c
53 + * typec -> extcon bridge
54 + * Copyright (c) 2021 Ondřej Jirman <megi@xff.cz>
56 + * This driver bridges standard type-c interfaces to drivers that
57 + * expect extcon interface.
60 +#include <linux/delay.h>
61 +#include <linux/kernel.h>
62 +#include <linux/module.h>
63 +#include <linux/power_supply.h>
64 +#include <linux/platform_device.h>
65 +#include <linux/usb/pd.h>
66 +#include <linux/usb/role.h>
67 +#include <linux/usb/typec.h>
68 +#include <linux/usb/typec_dp.h>
69 +#include <linux/usb/typec_mux.h>
70 +#include <linux/extcon-provider.h>
72 +struct typec_extcon {
76 + struct usb_role_switch *role_sw;
77 + struct typec_switch_dev *sw;
78 + struct typec_mux_dev *mux;
81 + struct extcon_dev *extcon;
82 + struct notifier_block extcon_nb;
84 + /* cached state from typec controller */
86 + enum typec_orientation orientation;
87 + struct typec_altmode alt;
93 +static const unsigned int typec_extcon_cable[] = {
100 + EXTCON_CHG_USB_CDP,
101 + EXTCON_CHG_USB_DCP,
102 + EXTCON_CHG_USB_ACA,
107 +static void typec_extcon_set_cable(struct typec_extcon *tce, int id, bool on,
108 + union extcon_property_value prop_ss,
109 + union extcon_property_value prop_or)
111 + union extcon_property_value cur_ss, cur_or;
112 + bool prop_diff = false;
115 + ret = extcon_get_property(tce->extcon, id,
116 + EXTCON_PROP_USB_SS, &cur_ss);
117 + if (ret || cur_ss.intval != prop_ss.intval)
120 + ret = extcon_get_property(tce->extcon, id,
121 + EXTCON_PROP_USB_TYPEC_POLARITY, &cur_or);
122 + if (ret || cur_or.intval != prop_or.intval)
125 + if (!on && extcon_get_state(tce->extcon, id)) {
126 + extcon_set_state_sync(tce->extcon, id, false);
127 + } else if (on && (!extcon_get_state(tce->extcon, id) || prop_diff)) {
128 + extcon_set_state(tce->extcon, id, true);
129 + extcon_set_property(tce->extcon, id,
130 + EXTCON_PROP_USB_SS, prop_ss);
131 + extcon_set_property(tce->extcon, id,
132 + EXTCON_PROP_USB_TYPEC_POLARITY, prop_or);
133 + extcon_sync(tce->extcon, id);
137 +static int typec_extcon_sync_extcon(struct typec_extcon *tce)
139 + union extcon_property_value prop_ss, prop_or;
140 + bool has_dp = false;
142 + mutex_lock(&tce->lock);
144 + /* connector is disconnected */
145 + if (tce->orientation == TYPEC_ORIENTATION_NONE) {
146 + typec_extcon_set_cable(tce, EXTCON_USB, false, prop_ss, prop_or);
147 + typec_extcon_set_cable(tce, EXTCON_USB_HOST, false, prop_ss, prop_or);
148 + typec_extcon_set_cable(tce, EXTCON_DISP_DP, false, prop_ss, prop_or);
153 + prop_or.intval = tce->orientation == TYPEC_ORIENTATION_NORMAL ? 0 : 1;
154 + prop_ss.intval = 0;
156 + if (tce->has_alt && tce->alt.svid == USB_TYPEC_DP_SID) {
157 + switch (tce->mode) {
158 + case TYPEC_STATE_SAFE:
160 + case TYPEC_DP_STATE_C:
161 + case TYPEC_DP_STATE_E:
164 + case TYPEC_DP_STATE_D:
167 + case TYPEC_STATE_USB:
168 + prop_ss.intval = 1;
171 + dev_err(tce->dev, "unhandled mux mode=%lu\n", tce->mode);
176 + typec_extcon_set_cable(tce, EXTCON_USB,
177 + tce->role == USB_ROLE_DEVICE, prop_ss, prop_or);
178 + typec_extcon_set_cable(tce, EXTCON_USB_HOST,
179 + tce->role == USB_ROLE_HOST, prop_ss, prop_or);
181 + typec_extcon_set_cable(tce, EXTCON_DISP_DP, has_dp, prop_ss, prop_or);
184 + mutex_unlock(&tce->lock);
188 +static int typec_extcon_sw_set(struct typec_switch_dev *sw,
189 + enum typec_orientation orientation)
191 + struct typec_extcon *tce = typec_switch_get_drvdata(sw);
193 + dev_dbg(tce->dev, "SW SET: orientation=%d\n", orientation);
195 + mutex_lock(&tce->lock);
196 + tce->orientation = orientation;
197 + mutex_unlock(&tce->lock);
199 + typec_extcon_sync_extcon(tce);
204 +static int typec_extcon_mux_set(struct typec_mux_dev *mux,
205 + struct typec_mux_state *state)
207 + struct typec_extcon *tce = typec_mux_get_drvdata(mux);
208 + struct typec_altmode *alt = state->alt;
210 + dev_dbg(tce->dev, "MUX SET: state->mode=%lu\n", state->mode);
212 + dev_dbg(tce->dev, " ...alt: svid=%04hx mode=%d vdo=%08x active=%u\n",
213 + alt->svid, alt->mode, alt->vdo, alt->active);
215 + mutex_lock(&tce->lock);
216 + tce->mode = state->mode;
217 + tce->has_alt = alt != NULL;
220 + mutex_unlock(&tce->lock);
222 + typec_extcon_sync_extcon(tce);
227 +static int typec_extcon_usb_set_role(struct usb_role_switch *sw,
228 + enum usb_role role)
230 + struct typec_extcon *tce = usb_role_switch_get_drvdata(sw);
232 + dev_dbg(tce->dev, "ROLE SET: role=%d\n", role);
234 + mutex_lock(&tce->lock);
236 + mutex_unlock(&tce->lock);
238 + typec_extcon_sync_extcon(tce);
243 +static int typec_extcon_notifier(struct notifier_block *nb,
244 + unsigned long action, void *data)
246 + struct typec_extcon *tce = container_of(nb, struct typec_extcon, extcon_nb);
248 + bool sdp = extcon_get_state(tce->extcon, EXTCON_CHG_USB_SDP);
249 + bool cdp = extcon_get_state(tce->extcon, EXTCON_CHG_USB_CDP);
250 + bool dcp = extcon_get_state(tce->extcon, EXTCON_CHG_USB_DCP);
251 + bool usb = extcon_get_state(tce->extcon, EXTCON_USB);
252 + bool usb_host = extcon_get_state(tce->extcon, EXTCON_USB_HOST);
253 + bool dp = extcon_get_state(tce->extcon, EXTCON_DISP_DP);
255 + dev_info(tce->dev, "extcon changed sdp=%d cdp=%d dcp=%d usb=%d usb_host=%d dp=%d\n",
256 + sdp, cdp, dcp, usb, usb_host, dp);
261 +static int typec_extcon_probe(struct platform_device *pdev)
263 + struct typec_switch_desc sw_desc = { };
264 + struct typec_mux_desc mux_desc = { };
265 + struct usb_role_switch_desc role_desc = { };
266 + struct device *dev = &pdev->dev;
267 + struct typec_extcon *tce;
270 + tce = devm_kzalloc(dev, sizeof(*tce), GFP_KERNEL);
274 + tce->dev = &pdev->dev;
275 + mutex_init(&tce->lock);
276 + tce->mode = TYPEC_STATE_SAFE;
278 + sw_desc.drvdata = tce;
279 + sw_desc.fwnode = dev->fwnode;
280 + sw_desc.set = typec_extcon_sw_set;
282 + tce->sw = typec_switch_register(dev, &sw_desc);
283 + if (IS_ERR(tce->sw))
284 + return dev_err_probe(dev, PTR_ERR(tce->sw),
285 + "Error registering typec switch\n");
287 + mux_desc.drvdata = tce;
288 + mux_desc.fwnode = dev->fwnode;
289 + mux_desc.set = typec_extcon_mux_set;
291 + tce->mux = typec_mux_register(dev, &mux_desc);
292 + if (IS_ERR(tce->mux)) {
293 + ret = dev_err_probe(dev, PTR_ERR(tce->mux),
294 + "Error registering typec mux\n");
298 + role_desc.driver_data = tce;
299 + role_desc.fwnode = dev->fwnode;
300 + role_desc.name = fwnode_get_name(dev->fwnode);
301 + role_desc.set = typec_extcon_usb_set_role;
303 + tce->role_sw = usb_role_switch_register(dev, &role_desc);
304 + if (IS_ERR(tce->role_sw)) {
305 + ret = dev_err_probe(dev, PTR_ERR(tce->role_sw),
306 + "Error registering USB role switch\n");
310 + tce->extcon = devm_extcon_dev_allocate(dev, typec_extcon_cable);
311 + if (IS_ERR(tce->extcon)) {
312 + ret = PTR_ERR(tce->extcon);
316 + ret = devm_extcon_dev_register(dev, tce->extcon);
318 + ret = dev_err_probe(dev, ret, "failed to register extcon device\n");
322 + extcon_set_property_capability(tce->extcon, EXTCON_USB,
323 + EXTCON_PROP_USB_SS);
324 + extcon_set_property_capability(tce->extcon, EXTCON_USB,
325 + EXTCON_PROP_USB_TYPEC_POLARITY);
326 + extcon_set_property_capability(tce->extcon, EXTCON_USB_HOST,
327 + EXTCON_PROP_USB_SS);
328 + extcon_set_property_capability(tce->extcon, EXTCON_USB_HOST,
329 + EXTCON_PROP_USB_TYPEC_POLARITY);
330 + extcon_set_property_capability(tce->extcon, EXTCON_DISP_DP,
331 + EXTCON_PROP_USB_SS);
332 + extcon_set_property_capability(tce->extcon, EXTCON_DISP_DP,
333 + EXTCON_PROP_USB_TYPEC_POLARITY);
335 + tce->extcon_nb.notifier_call = typec_extcon_notifier;
336 + ret = devm_extcon_register_notifier_all(dev, tce->extcon, &tce->extcon_nb);
338 + dev_err_probe(dev, ret, "Failed to register extcon notifier\n");
345 + usb_role_switch_unregister(tce->role_sw);
347 + typec_mux_unregister(tce->mux);
349 + typec_switch_unregister(tce->sw);
353 +static int typec_extcon_remove(struct platform_device *pdev)
355 + struct typec_extcon *tce = platform_get_drvdata(pdev);
357 + usb_role_switch_unregister(tce->role_sw);
358 + typec_mux_unregister(tce->mux);
359 + typec_switch_unregister(tce->sw);
364 +static struct of_device_id typec_extcon_of_match_table[] = {
365 + { .compatible = "linux,typec-extcon-bridge" },
368 +MODULE_DEVICE_TABLE(of, typec_extcon_of_match_table);
370 +static struct platform_driver typec_extcon_driver = {
372 + .name = "typec-extcon",
373 + .of_match_table = typec_extcon_of_match_table,
375 + .probe = typec_extcon_probe,
376 + .remove = typec_extcon_remove,
379 +module_platform_driver(typec_extcon_driver);
381 +MODULE_LICENSE("GPL");
382 +MODULE_AUTHOR("Ondrej Jirman <megi@xff.cz>");
383 +MODULE_DESCRIPTION("typec -> extcon bridge driver");
387 From 509e948e64f676d1347dbcbd077f1c4ce34f0f25 Mon Sep 17 00:00:00 2001
389 Date: Thu, 9 Feb 2023 20:33:50 +0100
390 Subject: usb: typec: typec-extcon: Allow to force reset on each mux change
392 This may help with some Alt-DP USB adapters.
394 Signed-off-by: Ondrej Jirman <megi@xff.cz>
396 drivers/usb/typec/typec-extcon.c | 15 +++++++++++++++
397 1 file changed, 15 insertions(+)
399 diff --git a/drivers/usb/typec/typec-extcon.c b/drivers/usb/typec/typec-extcon.c
400 index 12be8f203f76..d5a7f40f0921 100644
401 --- a/drivers/usb/typec/typec-extcon.c
402 +++ b/drivers/usb/typec/typec-extcon.c
404 #include <linux/usb/typec_mux.h>
405 #include <linux/extcon-provider.h>
407 +static bool reset_on_mux;
408 +module_param(reset_on_mux, bool, S_IRUGO | S_IWUSR);
409 +MODULE_PARM_DESC(reset_on_mux, "Set DP=0 on each type-c mux change");
411 struct typec_extcon {
414 @@ -162,6 +166,17 @@ static int typec_extcon_mux_set(struct typec_mux_dev *mux,
415 dev_dbg(tce->dev, " ...alt: svid=%04hx mode=%d vdo=%08x active=%u\n",
416 alt->svid, alt->mode, alt->vdo, alt->active);
418 + mutex_lock(&tce->lock);
419 + if (reset_on_mux && alt != NULL && tce->has_alt) {
420 + tce->mode = state->mode;
421 + tce->has_alt = false;
422 + mutex_unlock(&tce->lock);
424 + typec_extcon_sync_extcon(tce);
426 + mutex_unlock(&tce->lock);
429 mutex_lock(&tce->lock);
430 tce->mode = state->mode;
431 tce->has_alt = alt != NULL;
435 From c132654f50ab4726cdf0e116236c33c6038da08a Mon Sep 17 00:00:00 2001
437 Date: Wed, 2 Dec 2020 12:09:45 +0100
438 Subject: arm64: dts: rk3399-pinebook-pro: Improve Type-C support on Pinebook
441 This is using the same extcon bridge developed by me for Pinephone Pro.
443 Signed-off-by: Ondrej Jirman <megi@xff.cz>
445 .../boot/dts/rockchip/rk3399-pinebook-pro.dts | 57 +++++++++++++++++++---
446 1 file changed, 50 insertions(+), 7 deletions(-)
448 diff --git a/arch/arm64/boot/dts/rockchip/rk3399-pinebook-pro.dts b/arch/arm64/boot/dts/rockchip/rk3399-pinebook-pro.dts
449 index 15e90f836745..0d0310c4a82f 100644
450 --- a/arch/arm64/boot/dts/rockchip/rk3399-pinebook-pro.dts
451 +++ b/arch/arm64/boot/dts/rockchip/rk3399-pinebook-pro.dts
454 /* Regulators supplied by vcc5v0_usb */
455 /* Type C port power supply regulator */
456 - vbus_5vout: vbus_typec: vbus-5vout {
457 + vbus_5vout: vbus-5vout {
458 compatible = "regulator-fixed";
460 gpio = <&gpio1 RK_PA3 GPIO_ACTIVE_HIGH>;
462 pinctrl-names = "default";
463 pinctrl-0 = <&dc_det_pin>;
466 + typec_extcon_bridge: typec-extcon {
467 + compatible = "linux,typec-extcon-bridge";
469 + orientation-switch;
471 + svid = /bits/ 16 <0xff01>;
477 cpu-supply = <&vdd_cpu_l>;
482 + extcon = <&typec_extcon_bridge>;
483 + phys = <&tcphy0_dp>;
488 pinctrl-names = "default";
490 interrupts = <RK_PA2 IRQ_TYPE_LEVEL_LOW>;
491 pinctrl-names = "default";
492 pinctrl-0 = <&fusb0_int_pin>;
493 - vbus-supply = <&vbus_typec>;
494 + vbus-supply = <&vbus_5vout>;
495 + usb-role-switch = <&typec_extcon_bridge>;
496 + extcon = <&typec_extcon_bridge>;
499 compatible = "usb-c-connector";
500 @@ -700,10 +716,19 @@
501 op-sink-microwatt = <1000000>;
504 - <PDO_FIXED(5000, 2500, PDO_FIXED_USB_COMM)>;
505 + <PDO_FIXED(5000, 2500, PDO_FIXED_USB_COMM | PDO_FIXED_DUAL_ROLE | PDO_FIXED_DATA_SWAP)>;
507 - <PDO_FIXED(5000, 1400, PDO_FIXED_USB_COMM)>;
508 + <PDO_FIXED(5000, 1400, PDO_FIXED_USB_COMM | PDO_FIXED_DUAL_ROLE | PDO_FIXED_DATA_SWAP)>;
509 try-power-role = "sink";
510 + mode-switch = <&typec_extcon_bridge>;
511 + orientation-switch = <&typec_extcon_bridge>;
521 #address-cells = <1>;
526 + extcon = <&typec_extcon_bridge>;
530 @@ -1004,13 +1030,21 @@
534 + extcon = <&typec_extcon_bridge>;
537 u2phy0_otg: otg-port {
539 + * Type-C port on the left side of the chasis.
544 u2phy0_host: host-port {
545 - phy-supply = <&vcc5v0_otg>;
547 + * USB 2.0 host port for the keyboard (internally connected).
549 + phy-supply = <&vcc5v0_usb>;
553 @@ -1025,11 +1059,18 @@
556 u2phy1_otg: otg-port {
558 + * USB 3.0 A port on the left side of the chasis.
563 u2phy1_host: host-port {
564 - phy-supply = <&vcc5v0_otg>;
566 + * To the HUB that has USB camera and USB 2.0 port on the right
567 + * side of the chasis.
569 + phy-supply = <&vcc5v0_usb>;
573 @@ -1080,7 +1121,9 @@
579 + extcon = <&typec_extcon_bridge>;
580 + snps,usb3-phy-reset-quirk;