summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Kettenis <kettenis@cvs.openbsd.org>2022-12-12 19:18:26 +0000
committerMark Kettenis <kettenis@cvs.openbsd.org>2022-12-12 19:18:26 +0000
commit96d93e17c775223a5b55a9e251591f088028d1d1 (patch)
treecfa3ef6d6192a239d9351c64f80f151709f4cf6b
parenta5fd3451148b084e4123ec12aa8b80746daf5383 (diff)
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@
-rw-r--r--sys/arch/arm64/conf/GENERIC3
-rw-r--r--sys/dev/fdt/files.fdt7
-rw-r--r--sys/dev/fdt/tipd.c224
-rw-r--r--sys/dev/fdt/xhci_fdt.c44
-rw-r--r--sys/dev/ofw/ofw_misc.h9
-rw-r--r--sys/dev/usb/xhci.c25
-rw-r--r--sys/dev/usb/xhcivar.h3
7 files changed, 299 insertions, 16 deletions
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 <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;
+}
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 <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;
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);