From ade86d6ed42aae06300c8c2256689504dd5bed38 Mon Sep 17 00:00:00 2001 From: kettenis Date: Mon, 12 Dec 2022 19:18:25 +0000 Subject: [PATCH] Fix USB hotplug on type-C connectors of Apple Silicon hardware. The USB controller on these machines does not see connection events. Instead we need to rely on the USB PD controllers to notify us of a new connection and reset the USB controller. This diff implements this by adding a new tipd(4) driver and infrastructure to notify xhci(4) of new connections. ok patrick@ --- sys/arch/arm64/conf/GENERIC | 3 +- sys/dev/fdt/files.fdt | 7 +- sys/dev/fdt/tipd.c | 224 ++++++++++++++++++++++++++++++++++++ sys/dev/fdt/xhci_fdt.c | 44 ++++++- sys/dev/ofw/ofw_misc.h | 9 +- sys/dev/usb/xhci.c | 25 ++-- sys/dev/usb/xhcivar.h | 3 +- 7 files changed, 299 insertions(+), 16 deletions(-) create mode 100644 sys/dev/fdt/tipd.c diff --git a/sys/arch/arm64/conf/GENERIC b/sys/arch/arm64/conf/GENERIC index 441336332b0..d6608a85bdd 100644 --- a/sys/arch/arm64/conf/GENERIC +++ b/sys/arch/arm64/conf/GENERIC @@ -1,4 +1,4 @@ -# $OpenBSD: GENERIC,v 1.249 2022/11/26 09:05:32 kettenis Exp $ +# $OpenBSD: GENERIC,v 1.250 2022/12/12 19:18:25 kettenis Exp $ # # GENERIC machine description file # @@ -551,6 +551,7 @@ rkpmic* at iic? # RK808 PMIC sypwr* at iic? # SY8106A regulator tascodec* at iic? # TAS2770 audio codec tcpci* at iic? # USB Type-C controller +tipd* at iic? # TPS6598x Type-C controller pijuice* at iic? # PiJuice HAT # GPIO "pin bus" drivers diff --git a/sys/dev/fdt/files.fdt b/sys/dev/fdt/files.fdt index 93c8f7de922..222b059391d 100644 --- a/sys/dev/fdt/files.fdt +++ b/sys/dev/fdt/files.fdt @@ -1,4 +1,4 @@ -# $OpenBSD: files.fdt,v 1.172 2022/11/23 23:43:08 kettenis Exp $ +# $OpenBSD: files.fdt,v 1.173 2022/12/12 19:18:25 kettenis Exp $ # # Config file and device description for machine-independent FDT code. # Included by ports that need it. @@ -677,3 +677,8 @@ file dev/fdt/qcpwm.c qcpwm device qcrtc attach qcrtc at spmi file dev/fdt/qcrtc.c qcrtc + +# TI TPS6598x Type-C controller +device tipd +attach tipd at i2c +file dev/fdt/tipd.c tipd diff --git a/sys/dev/fdt/tipd.c b/sys/dev/fdt/tipd.c new file mode 100644 index 00000000000..6f7a3cb97f5 --- /dev/null +++ b/sys/dev/fdt/tipd.c @@ -0,0 +1,224 @@ +/* $OpenBSD: tipd.c,v 1.1 2022/12/12 19:18:25 kettenis Exp $ */ +/* + * Copyright (c) 2022 Mark Kettenis + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include + +#define TPS_INT_EVENT_1 0x14 +#define TPS_INT_EVENT_2 0x15 +#define TPS_INT_MASK_1 0x16 +#define TPS_INT_MASK_2 0x17 +#define TPS_INT_CLEAR_1 0x18 +#define TPS_INT_CLEAR_2 0x19 +#define TPS_STATUS 0x1a +#define TPS_STATUS_PLUG_PRESENT (1 << 0) + +/* + * Interrupt bits on the CD321x controllers used by Apple differ from + * those used by the standard TPS6598x controllers. + */ +#define CD_INT_PLUG_EVENT (1 << 1) + +struct tipd_softc { + struct device sc_dev; + i2c_tag_t sc_tag; + i2c_addr_t sc_addr; + + void *sc_ih; + + struct device_ports sc_ports; +}; + +int tipd_match(struct device *, void *, void *); +void tipd_attach(struct device *, struct device *, void *); + +const struct cfattach tipd_ca = { + sizeof(struct tipd_softc), tipd_match, tipd_attach +}; + +struct cfdriver tipd_cd = { + NULL, "tipd", DV_DULL +}; + +int tipd_intr(void *); + +int tipd_read_4(struct tipd_softc *, uint8_t, uint32_t *); +int tipd_read_8(struct tipd_softc *, uint8_t, uint64_t *); +int tipd_write_8(struct tipd_softc *, uint8_t, uint64_t); + +int +tipd_match(struct device *parent, void *match, void *aux) +{ + struct i2c_attach_args *ia = aux; + + return iic_is_compatible(ia, "apple,cd321x"); +} + +void +tipd_attach(struct device *parent, struct device *self, void *aux) +{ + struct tipd_softc *sc = (struct tipd_softc *)self; + struct i2c_attach_args *ia = aux; + int node = *(int *)ia->ia_cookie; + + sc->sc_tag = ia->ia_tag; + sc->sc_addr = ia->ia_addr; + + sc->sc_ih = fdt_intr_establish(node, IPL_BIO, tipd_intr, + sc, sc->sc_dev.dv_xname); + if (sc->sc_ih == NULL) { + printf(": can't establish interrupt\n"); + return; + } + + printf("\n"); + + tipd_write_8(sc, TPS_INT_MASK_1, CD_INT_PLUG_EVENT); + + node = OF_getnodebyname(node, "connector"); + if (node) { + sc->sc_ports.dp_node = node; + device_ports_register(&sc->sc_ports, -1); + } +} + +void +tipd_connect(struct tipd_softc *sc) +{ + struct endpoint *ep, *rep; + struct usb_controller_port *port; + + ep = endpoint_byreg(&sc->sc_ports, 0, -1); + if (ep == NULL) + return; + rep = endpoint_remote(ep); + if (rep == NULL || rep->ep_type != EP_USB_CONTROLLER_PORT) + return; + port = endpoint_get_cookie(rep); + if (port && port->up_connect) + port->up_connect(port->up_cookie); +} + +void +tipd_disconnect(struct tipd_softc *sc) +{ + struct endpoint *ep, *rep; + struct usb_controller_port *port; + + ep = endpoint_byreg(&sc->sc_ports, 0, -1); + if (ep == NULL) + return; + rep = endpoint_remote(ep); + if (rep == NULL || rep->ep_type != EP_USB_CONTROLLER_PORT) + return; + port = endpoint_get_cookie(rep); + if (port && port->up_disconnect) + port->up_disconnect(port->up_cookie); +} + +int +tipd_intr(void *arg) +{ + struct tipd_softc *sc = arg; + uint64_t event; + uint32_t status; + int error; + + error = tipd_read_8(sc, TPS_INT_EVENT_1, &event); + if (error) + return 0; + + if (event == 0) + return 0; + + if (event & CD_INT_PLUG_EVENT) { + error = tipd_read_4(sc, TPS_STATUS, &status); + if (error) + goto fail; + + if (status & TPS_STATUS_PLUG_PRESENT) + tipd_connect(sc); + else + tipd_disconnect(sc); + } + +fail: + tipd_write_8(sc, TPS_INT_CLEAR_1, event); + return 1; +} + +int +tipd_read_4(struct tipd_softc *sc, uint8_t reg, uint32_t *val) +{ + uint8_t buf[5]; + int error; + + iic_acquire_bus(sc->sc_tag, 0); + error = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, + sc->sc_addr, ®, sizeof(reg), buf, sizeof(buf), 0); + iic_release_bus(sc->sc_tag, 0); + + if (error == 0) + *val = lemtoh32(&buf[1]); + + return error; +} + +int +tipd_read_8(struct tipd_softc *sc, uint8_t reg, uint64_t *val) +{ + uint8_t buf[9]; + int error; + + iic_acquire_bus(sc->sc_tag, 0); + error = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, + sc->sc_addr, ®, sizeof(reg), buf, sizeof(buf), 0); + iic_release_bus(sc->sc_tag, 0); + + if (error == 0) + *val = lemtoh64(&buf[1]); + + return error; +} + +int +tipd_write_8(struct tipd_softc *sc, uint8_t reg, uint64_t val) +{ + uint8_t buf[9]; + int error; + + buf[0] = 8; + htolem64(&buf[1], val); + + iic_acquire_bus(sc->sc_tag, 0); + error = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, + sc->sc_addr, ®, sizeof(reg), buf, sizeof(buf), 0); + iic_release_bus(sc->sc_tag, 0); + + return error; +} diff --git a/sys/dev/fdt/xhci_fdt.c b/sys/dev/fdt/xhci_fdt.c index a0c224e10ca..9cdb5f1eeae 100644 --- a/sys/dev/fdt/xhci_fdt.c +++ b/sys/dev/fdt/xhci_fdt.c @@ -1,4 +1,4 @@ -/* $OpenBSD: xhci_fdt.c,v 1.19 2022/06/06 09:46:07 kettenis Exp $ */ +/* $OpenBSD: xhci_fdt.c,v 1.20 2022/12/12 19:18:25 kettenis Exp $ */ /* * Copyright (c) 2017 Mark Kettenis * @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -47,6 +48,10 @@ struct xhci_fdt_softc { bus_addr_t sc_otg_base; bus_size_t sc_otg_size; bus_space_handle_t sc_otg_ioh; + + struct device_ports sc_ports; + struct usb_controller_port sc_usb_controller_port; + struct task sc_snps_connect_task; }; int xhci_fdt_match(struct device *, void *, void *); @@ -222,6 +227,28 @@ xhci_cdns_init(struct xhci_fdt_softc *sc) #define USB3_GUSB2PHYCFG0_SUSPENDUSB20 (1 << 6) #define USB3_GUSB2PHYCFG0_PHYIF (1 << 3) +void +xhci_snps_do_connect(void *arg) +{ + struct xhci_fdt_softc *sc = arg; + + xhci_reinit(&sc->sc); +} + +void +xhci_snps_connect(void *cookie) +{ + struct xhci_fdt_softc *sc = cookie; + + task_add(systq, &sc->sc_snps_connect_task); +} + +void * +xhci_snps_ep_get_cookie(void *cookie, struct endpoint *ep) +{ + return cookie; +} + int xhci_snps_init(struct xhci_fdt_softc *sc) { @@ -229,6 +256,21 @@ xhci_snps_init(struct xhci_fdt_softc *sc) int node = sc->sc_node; uint32_t reg; + /* + * On Apple hardware we need to reset the controller when we + * see a new connection. + */ + if (OF_is_compatible(node, "apple,dwc3")) { + sc->sc_usb_controller_port.up_cookie = sc; + sc->sc_usb_controller_port.up_connect = xhci_snps_connect; + task_set(&sc->sc_snps_connect_task, xhci_snps_do_connect, sc); + + sc->sc_ports.dp_node = node; + sc->sc_ports.dp_cookie = &sc->sc_usb_controller_port; + sc->sc_ports.dp_ep_get_cookie = xhci_snps_ep_get_cookie; + device_ports_register(&sc->sc_ports, EP_USB_CONTROLLER_PORT); + } + /* We don't support device mode, so always force host mode. */ reg = bus_space_read_4(sc->sc.iot, sc->sc.ioh, USB3_GCTL); reg &= ~USB3_GCTL_PRTCAPDIR_MASK; diff --git a/sys/dev/ofw/ofw_misc.h b/sys/dev/ofw/ofw_misc.h index 3af9d1d5d8b..efb0d3ad58f 100644 --- a/sys/dev/ofw/ofw_misc.h +++ b/sys/dev/ofw/ofw_misc.h @@ -1,4 +1,4 @@ -/* $OpenBSD: ofw_misc.h,v 1.26 2022/11/09 19:18:11 kettenis Exp $ */ +/* $OpenBSD: ofw_misc.h,v 1.27 2022/12/12 19:18:25 kettenis Exp $ */ /* * Copyright (c) 2017-2021 Mark Kettenis * @@ -166,6 +166,13 @@ enum endpoint_type { EP_DRM_ENCODER, /* struct drm_encoder */ EP_DRM_PANEL, /* struct drm_panel */ EP_DAI_DEVICE, /* struct dai_device */ + EP_USB_CONTROLLER_PORT, /* struct usb_controller_port */ +}; + +struct usb_controller_port { + void *up_cookie; + void (*up_connect)(void *); + void (*up_disconnect)(void *); }; struct endpoint { diff --git a/sys/dev/usb/xhci.c b/sys/dev/usb/xhci.c index 4bec4e0169c..05f5479aa6f 100644 --- a/sys/dev/usb/xhci.c +++ b/sys/dev/usb/xhci.c @@ -1,4 +1,4 @@ -/* $OpenBSD: xhci.c,v 1.126 2022/07/15 07:52:06 kettenis Exp $ */ +/* $OpenBSD: xhci.c,v 1.127 2022/12/12 19:18:25 kettenis Exp $ */ /* * Copyright (c) 2014-2015 Martin Pieuchot @@ -528,16 +528,7 @@ xhci_activate(struct device *self, int act) switch (act) { case DVACT_RESUME: sc->sc_bus.use_polling++; - - xhci_reset(sc); - xhci_ring_reset(sc, &sc->sc_cmd_ring); - xhci_ring_reset(sc, &sc->sc_evt_ring); - - /* Renesas controllers, at least, need more time to resume. */ - usb_delay_ms(&sc->sc_bus, USB_RESUME_WAIT); - - xhci_config(sc); - + xhci_reinit(sc); sc->sc_bus.use_polling--; rv = config_activate_children(self, act); break; @@ -587,6 +578,18 @@ xhci_reset(struct xhci_softc *sc) return (0); } +void +xhci_reinit(struct xhci_softc *sc) +{ + xhci_reset(sc); + xhci_ring_reset(sc, &sc->sc_cmd_ring); + xhci_ring_reset(sc, &sc->sc_evt_ring); + + /* Renesas controllers, at least, need more time to resume. */ + usb_delay_ms(&sc->sc_bus, USB_RESUME_WAIT); + + xhci_config(sc); +} int xhci_intr(void *v) diff --git a/sys/dev/usb/xhcivar.h b/sys/dev/usb/xhcivar.h index be484869797..d1a4a831389 100644 --- a/sys/dev/usb/xhcivar.h +++ b/sys/dev/usb/xhcivar.h @@ -1,4 +1,4 @@ -/* $OpenBSD: xhcivar.h,v 1.13 2022/07/15 13:08:23 tb Exp $ */ +/* $OpenBSD: xhcivar.h,v 1.14 2022/12/12 19:18:25 kettenis Exp $ */ /* * Copyright (c) 2014 Martin Pieuchot @@ -124,6 +124,7 @@ struct xhci_softc { int xhci_init(struct xhci_softc *); void xhci_config(struct xhci_softc *); +void xhci_reinit(struct xhci_softc *); int xhci_intr(void *); int xhci_detach(struct device *, int); int xhci_activate(struct device *, int); -- 2.20.1