Fix USB hotplug on type-C connectors of Apple Silicon hardware. The USB
authorkettenis <kettenis@openbsd.org>
Mon, 12 Dec 2022 19:18:25 +0000 (19:18 +0000)
committerkettenis <kettenis@openbsd.org>
Mon, 12 Dec 2022 19:18:25 +0000 (19:18 +0000)
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
sys/dev/fdt/files.fdt
sys/dev/fdt/tipd.c [new file with mode: 0644]
sys/dev/fdt/xhci_fdt.c
sys/dev/ofw/ofw_misc.h
sys/dev/usb/xhci.c
sys/dev/usb/xhcivar.h

index 4413363..d6608a8 100644 (file)
@@ -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
index 93c8f7d..222b059 100644 (file)
@@ -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 (file)
index 0000000..6f7a3cb
--- /dev/null
@@ -0,0 +1,224 @@
+/*     $OpenBSD: tipd.c,v 1.1 2022/12/12 19:18:25 kettenis Exp $       */
+/*
+ * Copyright (c) 2022 Mark Kettenis <kettenis@openbsd.org>
+ *
+ * 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 <sys/param.h>
+#include <sys/systm.h>
+#include <sys/device.h>
+#include <sys/malloc.h>
+
+#include <machine/intr.h>
+#include <machine/fdt.h>
+
+#include <dev/ofw/openfirm.h>
+#include <dev/ofw/ofw_misc.h>
+#include <dev/ofw/fdt.h>
+
+#include <dev/i2c/i2cvar.h>
+
+#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, &reg, 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, &reg, 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, &reg, sizeof(reg), buf, sizeof(buf), 0);
+       iic_release_bus(sc->sc_tag, 0);
+
+       return error;
+}
index a0c224e..9cdb5f1 100644 (file)
@@ -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 <kettenis@openbsd.org>
  *
@@ -19,6 +19,7 @@
 #include <sys/systm.h>
 #include <sys/device.h>
 #include <sys/malloc.h>
+#include <sys/task.h>
 
 #include <machine/bus.h>
 #include <machine/fdt.h>
@@ -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;
index 3af9d1d..efb0d3a 100644 (file)
@@ -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 {
index 4bec4e0..05f5479 100644 (file)
@@ -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)
index be48486..d1a4a83 100644 (file)
@@ -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);