summaryrefslogtreecommitdiff
path: root/sys/dev/fdt
diff options
context:
space:
mode:
authorPatrick Wildt <patrick@cvs.openbsd.org>2020-04-27 21:30:53 +0000
committerPatrick Wildt <patrick@cvs.openbsd.org>2020-04-27 21:30:53 +0000
commitdd746ac6a023b01fcc53eb906fb92fce6e72feaa (patch)
tree41aa63334d0190afe743b797721891866bf53891 /sys/dev/fdt
parent23a167f191c3d7653bbc9d673addd864c1733d90 (diff)
Add tcpci(4) to support TCPCI-compliant USB Type-C port controllers.
A Type-C controller has multiple tasks. Even though the orientation of the plug doesn't matter for the user, it matters for the hardware. To be able to know how to route the SuperSpeed pins you need to know which way the plug is connected. Also you need to know if you're a sink/source or device/host. To get the first connection, you toggle between the modes until you find a connection. In case you see that a sink is connected, you can turn on USB Vbus to power the sink. This driver explicitly does not implement USB's Type-C state machine, but if we get more and more of these controllers it might be worth doing. Also there's no support for Power Delivery messages yet. "go for it" kettenis@
Diffstat (limited to 'sys/dev/fdt')
-rw-r--r--sys/dev/fdt/files.fdt6
-rw-r--r--sys/dev/fdt/tcpci.c604
2 files changed, 609 insertions, 1 deletions
diff --git a/sys/dev/fdt/files.fdt b/sys/dev/fdt/files.fdt
index 6273a59ab7b..c74fb555c31 100644
--- a/sys/dev/fdt/files.fdt
+++ b/sys/dev/fdt/files.fdt
@@ -1,4 +1,4 @@
-# $OpenBSD: files.fdt,v 1.130 2020/04/27 12:15:30 kettenis Exp $
+# $OpenBSD: files.fdt,v 1.131 2020/04/27 21:30:52 patrick Exp $
#
# Config file and device description for machine-independent FDT code.
# Included by ports that need it.
@@ -496,3 +496,7 @@ file dev/fdt/sfp.c sfp
device bdpmic
attach bdpmic at i2c
file dev/fdt/bd718x7.c bdpmic
+
+device tcpci
+attach tcpci at i2c
+file dev/fdt/tcpci.c tcpci
diff --git a/sys/dev/fdt/tcpci.c b/sys/dev/fdt/tcpci.c
new file mode 100644
index 00000000000..46cf1ae91f8
--- /dev/null
+++ b/sys/dev/fdt/tcpci.c
@@ -0,0 +1,604 @@
+/* $OpenBSD: tcpci.c,v 1.1 2020/04/27 21:30:52 patrick Exp $ */
+/*
+ * Copyright (c) 2018 Patrick Wildt <patrick@blueri.se>
+ *
+ * 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/kernel.h>
+#include <sys/device.h>
+#include <sys/malloc.h>
+#include <sys/stdint.h>
+#include <sys/task.h>
+
+#include <machine/bus.h>
+#include <machine/fdt.h>
+
+#include <dev/i2c/i2cvar.h>
+#include <dev/ofw/openfirm.h>
+#include <dev/ofw/ofw_gpio.h>
+#include <dev/ofw/ofw_misc.h>
+#include <dev/ofw/ofw_pinctrl.h>
+
+/* #define TCPCI_DEBUG */
+
+#ifdef TCPCI_DEBUG
+#define DPRINTF(x) printf x
+#else
+#define DPRINTF(x)
+#endif
+
+#define TCPC_VENDOR_ID 0x00
+#define TCPC_PRODUCT_ID 0x02
+#define TCPC_BCD_DEV 0x04
+#define TCPC_TC_REV 0x06
+#define TCPC_PD_REV 0x08
+#define TCPC_PD_INT_REV 0x0a
+#define TCPC_ALERT 0x10
+#define TCPC_ALERT_CC_STATUS (1 << 0)
+#define TCPC_ALERT_POWER_STATUS (1 << 1)
+#define TCPC_ALERT_RX_STATUS (1 << 2)
+#define TCPC_ALERT_RX_HARD_RST (1 << 3)
+#define TCPC_ALERT_TX_FAILED (1 << 4)
+#define TCPC_ALERT_TX_DISCARDED (1 << 5)
+#define TCPC_ALERT_TX_SUCCESS (1 << 6)
+#define TCPC_ALERT_V_ALARM_HI (1 << 7)
+#define TCPC_ALERT_V_ALARM_LO (1 << 8)
+#define TCPC_ALERT_FAULT (1 << 9)
+#define TCPC_ALERT_RX_BUF_OVF (1 << 10)
+#define TCPC_ALERT_VBUS_DISCNCT (1 << 11)
+#define TCPC_ALERT_MASK 0x12
+#define TCPC_POWER_STATUS_MASK 0x14
+#define TCPC_POWER_STATUS_VBUS_PRES (1 << 2)
+#define TCPC_FAULT_STATUS_MASK 0x15
+#define TCPC_CONFIG_STD_OUTPUT 0x18
+#define TCPC_TCPC_CTRL 0x19
+#define TCPC_TCPC_CTRL_ORIENTATION (1 << 0)
+#define TCPC_TCPC_CTRL_BIST_MODE (1 << 1)
+#define TCPC_ROLE_CTRL 0x1a
+#define TCPC_ROLE_CTRL_CC1_SHIFT 0
+#define TCPC_ROLE_CTRL_CC2_SHIFT 2
+#define TCPC_ROLE_CTRL_CC_RA 0x0
+#define TCPC_ROLE_CTRL_CC_RP 0x1
+#define TCPC_ROLE_CTRL_CC_RD 0x2
+#define TCPC_ROLE_CTRL_CC_OPEN 0x3
+#define TCPC_ROLE_CTRL_CC_MASK 0x3
+#define TCPC_ROLE_CTRL_RP_VAL_MASK (0x3 << 4)
+#define TCPC_ROLE_CTRL_RP_VAL_DEF (0x0 << 4)
+#define TCPC_ROLE_CTRL_RP_VAL_1_5 (0x1 << 4)
+#define TCPC_ROLE_CTRL_RP_VAL_3_0 (0x2 << 4)
+#define TCPC_ROLE_CTRL_DRP (1 << 6)
+#define TCPC_FAULT_CTRL 0x1b
+#define TCPC_POWER_CTRL 0x1c
+#define TCPC_POWER_CTRL_VCONN_ENABLE (1 << 0)
+#define TCPC_POWER_CTRL_FORCEDISCH (1 << 2)
+#define TCPC_POWER_CTRL_DIS_VOL_ALARM (1 << 5)
+#define TCPC_CC_STATUS 0x1d
+#define TCPC_CC_STATUS_CC1_SHIFT 0
+#define TCPC_CC_STATUS_CC2_SHIFT 2
+#define TCPC_CC_STATUS_CC_MASK 0x3
+#define TCPC_CC_STATUS_TERM (1 << 4)
+#define TCPC_CC_STATUS_TOGGLING (1 << 5)
+#define TCPC_POWER_STATUS 0x1e
+#define TCPC_FAULT_STATUS 0x1f
+#define TCPC_FAULT_STATUS_CLEAR (1 << 7)
+#define TCPC_COMMAND 0x23
+#define TCPC_COMMAND_WAKE_I2C 0x11
+#define TCPC_COMMAND_DISABLE_VBUS_DETECT 0x22
+#define TCPC_COMMAND_ENABLE_VBUS_DETECT 0x33
+#define TCPC_COMMAND_DISABLE_SINK_VBUS 0x44
+#define TCPC_COMMAND_SINK_VBUS 0x55
+#define TCPC_COMMAND_DISABLE_SRC_VBUS 0x66
+#define TCPC_COMMAND_SRC_VBUS_DEFAULT 0x77
+#define TCPC_COMMAND_SRC_VBUS_HIGH 0x88
+#define TCPC_COMMAND_LOOK4CONNECTION 0x99
+#define TCPC_COMMAND_RXONEMORE 0xAA
+#define TCPC_COMMAND_I2C_IDLE 0xFF
+#define TCPC_DEV_CAP_1 0x24
+#define TCPC_DEV_CAP_2 0x26
+#define TCPC_STD_INPUT_CAP 0x28
+#define TCPC_STD_OUTPUT_CAP 0x29
+#define TCPC_MSG_HDR_INFO 0x2e
+#define TCPC_MSG_HDR_INFO_PWR_ROLE (1 << 0)
+#define TCPC_MSG_HDR_INFO_PD_REV10 (0 << 1)
+#define TCPC_MSG_HDR_INFO_PD_REV20 (1 << 1)
+#define TCPC_MSG_HDR_INFO_DATA_ROLE (1 << 3)
+#define TCPC_RX_DETECT 0x2f
+#define TCPC_RX_DETECT_SOP (1 << 0)
+#define TCPC_RX_DETECT_HARD_RESET (1 << 5)
+#define TCPC_RX_BYTE_CNT 0x30
+#define TCPC_RX_BUF_FRAME_TYPE 0x31
+#define TCPC_RX_HDR 0x32
+#define TCPC_RX_DATA 0x34 /* through 0x4f */
+#define TCPC_TRANSMIT 0x50
+#define TCPC_TX_BYTE_CNT 0x51
+#define TCPC_TX_HDR 0x52
+#define TCPC_TX_DATA 0x54 /* through 0x6f */
+#define TCPC_VBUS_VOLTAGE 0x70
+#define TCPC_VBUS_SINK_DISCONNECT_THRESH 0x72
+#define TCPC_VBUS_STOP_DISCHARGE_THRESH 0x74
+#define TCPC_VBUS_VOLTAGE_ALARM_HI_CFG 0x76
+#define TCPC_VBUS_VOLTAGE_ALARM_LO_CFG 0x78
+
+enum typec_cc_status {
+ TYPEC_CC_OPEN,
+ TYPEC_CC_RA,
+ TYPEC_CC_RD,
+ TYPEC_CC_RP_DEF,
+ TYPEC_CC_RP_1_5,
+ TYPEC_CC_RP_3_0,
+};
+
+enum typec_data_role {
+ TYPEC_DEVICE,
+ TYPEC_HOST,
+};
+
+enum typec_power_role {
+ TYPEC_SINK,
+ TYPEC_SOURCE,
+};
+
+enum typec_polarity {
+ TYPEC_POLARITY_CC1,
+ TYPEC_POLARITY_CC2,
+};
+
+struct tcpci_softc {
+ struct device sc_dev;
+ i2c_tag_t sc_tag;
+ i2c_addr_t sc_addr;
+ int sc_node;
+ void *sc_ih;
+
+ struct task sc_task;
+
+ int sc_attached;
+ enum typec_data_role sc_try_data;
+ enum typec_power_role sc_try_power;
+
+ uint32_t *sc_ss_sel;
+ uint8_t sc_cc;
+ uint8_t sc_vbus_det;
+};
+
+int tcpci_match(struct device *, void *, void *);
+void tcpci_attach(struct device *, struct device *, void *);
+int tcpci_detach(struct device *, int);
+
+int tcpci_intr(void *);
+void tcpci_task(void *);
+void tcpci_cc_change(struct tcpci_softc *);
+void tcpci_power_change(struct tcpci_softc *);
+void tcpci_set_polarity(struct tcpci_softc *, int);
+void tcpci_set_vbus(struct tcpci_softc *, int, int);
+void tcpci_set_roles(struct tcpci_softc *, enum typec_data_role,
+ enum typec_power_role);
+
+void tcpci_write_reg16(struct tcpci_softc *, uint8_t, uint16_t);
+uint16_t tcpci_read_reg16(struct tcpci_softc *, uint8_t);
+void tcpci_write_reg8(struct tcpci_softc *, uint8_t, uint8_t);
+uint8_t tcpci_read_reg8(struct tcpci_softc *, uint8_t);
+
+struct cfattach tcpci_ca = {
+ sizeof(struct tcpci_softc),
+ tcpci_match,
+ tcpci_attach,
+ tcpci_detach,
+};
+
+struct cfdriver tcpci_cd = {
+ NULL, "tcpci", DV_DULL
+};
+
+int
+tcpci_match(struct device *parent, void *match, void *aux)
+{
+ struct i2c_attach_args *ia = aux;
+
+ if (strcmp(ia->ia_name, "nxp,ptn5110") == 0)
+ return 1;
+
+ return 0;
+}
+
+void
+tcpci_attach(struct device *parent, struct device *self, void *aux)
+{
+ struct tcpci_softc *sc = (struct tcpci_softc *)self;
+ struct i2c_attach_args *ia = aux;
+ int len;
+
+ sc->sc_tag = ia->ia_tag;
+ sc->sc_addr = ia->ia_addr;
+ sc->sc_node = *(int *)ia->ia_cookie;
+
+ /* Automatic DRP toggling should try first as ... */
+ sc->sc_try_data = TYPEC_HOST;
+ sc->sc_try_power = TYPEC_SOURCE;
+
+ pinctrl_byname(sc->sc_node, "default");
+
+ task_set(&sc->sc_task, tcpci_task, sc);
+ sc->sc_ih = arm_intr_establish_fdt(sc->sc_node, IPL_BIO,
+ tcpci_intr, sc, sc->sc_dev.dv_xname);
+ if (sc->sc_ih == NULL) {
+ printf(": unable to establish interrupt\n");
+ return;
+ }
+
+ len = OF_getproplen(sc->sc_node, "ss-sel-gpios");
+ if (len > 0) {
+ sc->sc_ss_sel = malloc(len, M_TEMP, M_WAITOK);
+ OF_getpropintarray(sc->sc_node, "ss-sel-gpios",
+ sc->sc_ss_sel, len);
+ gpio_controller_config_pin(sc->sc_ss_sel,
+ GPIO_CONFIG_OUTPUT);
+ gpio_controller_set_pin(sc->sc_ss_sel, 1);
+ }
+
+ tcpci_write_reg16(sc, TCPC_ALERT, 0xffff);
+ tcpci_write_reg8(sc, TCPC_FAULT_STATUS, 0x80);
+ tcpci_write_reg8(sc, TCPC_POWER_STATUS_MASK,
+ TCPC_POWER_STATUS_VBUS_PRES);
+ tcpci_write_reg8(sc, TCPC_POWER_CTRL, tcpci_read_reg8(sc,
+ TCPC_POWER_CTRL) & ~TCPC_POWER_CTRL_DIS_VOL_ALARM);
+ tcpci_write_reg16(sc, TCPC_ALERT_MASK,
+ TCPC_ALERT_TX_SUCCESS | TCPC_ALERT_TX_FAILED |
+ TCPC_ALERT_TX_DISCARDED | TCPC_ALERT_RX_STATUS |
+ TCPC_ALERT_RX_HARD_RST | TCPC_ALERT_CC_STATUS |
+ TCPC_ALERT_RX_BUF_OVF | TCPC_ALERT_FAULT |
+ TCPC_ALERT_V_ALARM_LO | TCPC_ALERT_POWER_STATUS);
+
+ if (sc->sc_try_data == TYPEC_HOST)
+ tcpci_write_reg8(sc, TCPC_ROLE_CTRL, TCPC_ROLE_CTRL_DRP | 0xa);
+ else
+ tcpci_write_reg8(sc, TCPC_ROLE_CTRL, TCPC_ROLE_CTRL_DRP | 0x5);
+ tcpci_write_reg8(sc, TCPC_COMMAND, TCPC_COMMAND_LOOK4CONNECTION);
+
+ printf("\n");
+}
+
+int
+tcpci_detach(struct device *self, int flags)
+{
+ return 0;
+}
+
+int
+tcpci_intr(void *args)
+{
+ struct tcpci_softc *sc = args;
+ fdt_intr_disable(sc->sc_ih);
+ task_add(systq, &sc->sc_task);
+ return 1;
+}
+
+void
+tcpci_task(void *args)
+{
+ struct tcpci_softc *sc = args;
+ uint16_t status;
+
+ status = tcpci_read_reg16(sc, TCPC_ALERT);
+ tcpci_write_reg16(sc, TCPC_ALERT, status);
+
+ DPRINTF(("%s: alert %x\n", __func__, status));
+
+ if (status & TCPC_ALERT_CC_STATUS)
+ tcpci_cc_change(sc);
+
+ if (status & TCPC_ALERT_POWER_STATUS)
+ tcpci_power_change(sc);
+
+ if (status & TCPC_ALERT_V_ALARM_LO) {
+ tcpci_write_reg8(sc, TCPC_VBUS_VOLTAGE_ALARM_LO_CFG, 0);
+ tcpci_write_reg8(sc, TCPC_POWER_CTRL, tcpci_read_reg8(sc,
+ TCPC_POWER_CTRL) & ~TCPC_POWER_CTRL_FORCEDISCH);
+ }
+
+ if (status & TCPC_ALERT_FAULT)
+ tcpci_write_reg8(sc, TCPC_FAULT_STATUS, tcpci_read_reg8(sc,
+ TCPC_FAULT_STATUS) | TCPC_FAULT_STATUS_CLEAR);
+
+ fdt_intr_enable(sc->sc_ih);
+}
+
+uint8_t
+tcpci_typec_to_rp(int typec)
+{
+ switch (typec) {
+ case TYPEC_CC_RP_DEF:
+ return TCPC_ROLE_CTRL_RP_VAL_DEF;
+ case TYPEC_CC_RP_1_5:
+ return TCPC_ROLE_CTRL_RP_VAL_1_5;
+ case TYPEC_CC_RP_3_0:
+ return TCPC_ROLE_CTRL_RP_VAL_3_0;
+ default:
+ panic("%s:%d", __func__, __LINE__);
+ }
+}
+
+int
+tcpci_cc_to_typec(int cc, int sink)
+{
+ if (sink) {
+ if (cc == 0x1)
+ return TYPEC_CC_RP_DEF;
+ if (cc == 0x2)
+ return TYPEC_CC_RP_1_5;
+ if (cc == 0x3)
+ return TYPEC_CC_RP_3_0;
+ } else {
+ if (cc == 0x1)
+ return TYPEC_CC_RA;
+ if (cc == 0x2)
+ return TYPEC_CC_RD;
+ }
+
+ return TYPEC_CC_OPEN;
+}
+
+int
+tcpci_cc_is_sink(int cc1, int cc2)
+{
+ if ((cc1 == TYPEC_CC_RP_DEF || cc1 == TYPEC_CC_RP_1_5 ||
+ cc1 == TYPEC_CC_RP_3_0) && cc2 == TYPEC_CC_OPEN)
+ return 1;
+ if ((cc2 == TYPEC_CC_RP_DEF || cc2 == TYPEC_CC_RP_1_5 ||
+ cc2 == TYPEC_CC_RP_3_0) && cc1 == TYPEC_CC_OPEN)
+ return 1;
+ return 0;
+}
+
+int
+tcpci_cc_is_source(int cc1, int cc2)
+{
+ if (cc1 == TYPEC_CC_RD && cc2 != TYPEC_CC_RD)
+ return 1;
+ if (cc2 == TYPEC_CC_RD && cc1 != TYPEC_CC_RD)
+ return 1;
+ return 0;
+}
+
+int
+tcpci_cc_is_audio(int cc1, int cc2)
+{
+ if (cc1 == TYPEC_CC_RA && cc2 == TYPEC_CC_RA)
+ return 1;
+ return 0;
+}
+
+int
+tcpci_cc_is_audio_detached(int cc1, int cc2)
+{
+ if (cc1 == TYPEC_CC_RA && cc2 == TYPEC_CC_OPEN)
+ return 1;
+ if (cc2 == TYPEC_CC_RA && cc1 == TYPEC_CC_OPEN)
+ return 1;
+ return 0;
+}
+
+void
+tcpci_cc_change(struct tcpci_softc *sc)
+{
+ uint8_t cc, cc1, cc2;
+
+ cc = tcpci_read_reg8(sc, TCPC_CC_STATUS);
+ if (sc->sc_cc == cc)
+ return;
+
+ cc1 = (cc >> TCPC_ROLE_CTRL_CC1_SHIFT) & TCPC_ROLE_CTRL_CC_MASK;
+ cc1 = tcpci_cc_to_typec(cc1, cc & TCPC_CC_STATUS_TERM);
+ cc2 = (cc >> TCPC_ROLE_CTRL_CC2_SHIFT) & TCPC_ROLE_CTRL_CC_MASK;
+ cc2 = tcpci_cc_to_typec(cc2, cc & TCPC_CC_STATUS_TERM);
+
+ if (cc1 == TYPEC_CC_OPEN && cc2 == TYPEC_CC_OPEN) {
+ /* No CC, wait for new connection. */
+ DPRINTF(("%s: disconnected\n", __func__));
+ tcpci_write_reg8(sc, TCPC_RX_DETECT, 0);
+ tcpci_set_vbus(sc, 0, 0);
+ tcpci_write_reg8(sc, TCPC_POWER_CTRL, tcpci_read_reg8(sc,
+ TCPC_POWER_CTRL) & ~TCPC_POWER_CTRL_VCONN_ENABLE);
+ tcpci_set_polarity(sc, TYPEC_POLARITY_CC1);
+ if (sc->sc_try_data == TYPEC_HOST) {
+ tcpci_write_reg8(sc, TCPC_ROLE_CTRL,
+ TCPC_ROLE_CTRL_DRP | 0xa);
+ } else {
+ tcpci_write_reg8(sc, TCPC_ROLE_CTRL,
+ TCPC_ROLE_CTRL_DRP | 0x5);
+ }
+ tcpci_write_reg8(sc, TCPC_COMMAND,
+ TCPC_COMMAND_LOOK4CONNECTION);
+ sc->sc_attached = 0;
+ } else if (tcpci_cc_is_source(cc1, cc2)) {
+ /* Host */
+ DPRINTF(("%s: attached as source\n", __func__));
+ if (cc1 == TYPEC_CC_RD)
+ tcpci_set_polarity(sc, TYPEC_POLARITY_CC1);
+ else
+ tcpci_set_polarity(sc, TYPEC_POLARITY_CC2);
+ tcpci_set_roles(sc, TYPEC_HOST, TYPEC_SOURCE);
+ tcpci_write_reg8(sc, TCPC_RX_DETECT,
+ TCPC_RX_DETECT_SOP | TCPC_RX_DETECT_HARD_RESET);
+ if ((cc1 == TYPEC_CC_RD && cc2 == TYPEC_CC_RA) ||
+ (cc2 == TYPEC_CC_RD && cc1 == TYPEC_CC_RA))
+ tcpci_write_reg8(sc, TCPC_POWER_CTRL, tcpci_read_reg8(sc,
+ TCPC_POWER_CTRL) | TCPC_POWER_CTRL_VCONN_ENABLE);
+ tcpci_set_vbus(sc, 1, 0);
+ sc->sc_attached = 1;
+ } else if (tcpci_cc_is_sink(cc1, cc2)) {
+ /* Device */
+ DPRINTF(("%s: attached as sink\n", __func__));
+ if (cc1 != TYPEC_CC_OPEN) {
+ tcpci_set_polarity(sc, TYPEC_POLARITY_CC1);
+ tcpci_write_reg8(sc, TCPC_ROLE_CTRL,
+ TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC1_SHIFT |
+ TCPC_ROLE_CTRL_CC_OPEN << TCPC_ROLE_CTRL_CC2_SHIFT);
+ } else {
+ tcpci_set_polarity(sc, TYPEC_POLARITY_CC2);
+ tcpci_write_reg8(sc, TCPC_ROLE_CTRL,
+ TCPC_ROLE_CTRL_CC_OPEN << TCPC_ROLE_CTRL_CC1_SHIFT |
+ TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC2_SHIFT);
+ }
+ tcpci_set_roles(sc, TYPEC_DEVICE, TYPEC_SINK);
+ tcpci_set_vbus(sc, 0, 0);
+ sc->sc_attached = 1;
+ } else if (tcpci_cc_is_audio_detached(cc1, cc2)) {
+ /* Audio Detached */
+ DPRINTF(("%s: audio detached\n", __func__));
+ } else {
+ panic("%s: unknown combination cc %x", __func__, cc);
+ }
+
+ sc->sc_cc = cc;
+}
+
+void
+tcpci_power_change(struct tcpci_softc *sc)
+{
+ uint8_t power;
+
+ if (tcpci_read_reg8(sc, TCPC_POWER_STATUS_MASK) == 0xff)
+ DPRINTF(("%s: power reset\n", __func__));
+
+ power = tcpci_read_reg8(sc, TCPC_POWER_STATUS);
+ power &= TCPC_POWER_STATUS_VBUS_PRES;
+ if (sc->sc_vbus_det == power)
+ return;
+
+ DPRINTF(("%s: power %d\n", __func__, power));
+ sc->sc_vbus_det = power;
+}
+
+void
+tcpci_set_roles(struct tcpci_softc *sc, enum typec_data_role data,
+ enum typec_power_role power)
+{
+ uint8_t reg;
+
+ reg = TCPC_MSG_HDR_INFO_PD_REV20;
+ if (power == TYPEC_SOURCE)
+ reg |= TCPC_MSG_HDR_INFO_PWR_ROLE;
+ if (data == TYPEC_HOST)
+ reg |= TCPC_MSG_HDR_INFO_DATA_ROLE;
+
+ tcpci_write_reg8(sc, TCPC_MSG_HDR_INFO, reg);
+
+ if (data == TYPEC_HOST)
+ printf("%s: connected in host mode\n",
+ sc->sc_dev.dv_xname);
+ else
+ printf("%s: connected in device mode\n",
+ sc->sc_dev.dv_xname);
+}
+
+void
+tcpci_set_polarity(struct tcpci_softc *sc, int cc)
+{
+ if (cc == TYPEC_POLARITY_CC1) {
+ tcpci_write_reg8(sc, TCPC_TCPC_CTRL, 0);
+ if (sc->sc_ss_sel)
+ gpio_controller_set_pin(sc->sc_ss_sel, 1);
+ }
+ if (cc == TYPEC_POLARITY_CC2) {
+ tcpci_write_reg8(sc, TCPC_TCPC_CTRL,
+ TCPC_TCPC_CTRL_ORIENTATION);
+ if (sc->sc_ss_sel)
+ gpio_controller_set_pin(sc->sc_ss_sel, 0);
+ }
+}
+
+void
+tcpci_set_vbus(struct tcpci_softc *sc, int source, int sink)
+{
+ if (!source)
+ tcpci_write_reg8(sc, TCPC_COMMAND,
+ TCPC_COMMAND_DISABLE_SRC_VBUS);
+
+ if (!sink)
+ tcpci_write_reg8(sc, TCPC_COMMAND,
+ TCPC_COMMAND_DISABLE_SINK_VBUS);
+
+ if (!source && !sink) {
+ tcpci_write_reg8(sc, TCPC_VBUS_VOLTAGE_ALARM_LO_CFG, 0x1c);
+ tcpci_write_reg8(sc, TCPC_POWER_CTRL, tcpci_read_reg8(sc,
+ TCPC_POWER_CTRL) | TCPC_POWER_CTRL_FORCEDISCH);
+ }
+
+ if (source)
+ tcpci_write_reg8(sc, TCPC_COMMAND,
+ TCPC_COMMAND_SRC_VBUS_DEFAULT);
+
+ if (sink)
+ tcpci_write_reg8(sc, TCPC_COMMAND,
+ TCPC_COMMAND_SINK_VBUS);
+}
+
+uint8_t
+tcpci_read_reg8(struct tcpci_softc *sc, uint8_t reg)
+{
+ uint8_t val = 0;
+
+ iic_acquire_bus(sc->sc_tag, 0);
+ if (iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP,
+ sc->sc_addr, &reg, sizeof(reg), &val, sizeof(val), 0)) {
+ printf("%s: cannot read register 0x%x\n",
+ sc->sc_dev.dv_xname, reg);
+ }
+ iic_release_bus(sc->sc_tag, 0);
+
+ return val;
+}
+
+void
+tcpci_write_reg8(struct tcpci_softc *sc, uint8_t reg, uint8_t val)
+{
+ iic_acquire_bus(sc->sc_tag, 0);
+ if (iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP,
+ sc->sc_addr, &reg, sizeof(reg), &val, sizeof(val), 0)) {
+ printf("%s: cannot write register 0x%x\n",
+ sc->sc_dev.dv_xname, reg);
+ }
+ iic_release_bus(sc->sc_tag, 0);
+}
+
+uint16_t
+tcpci_read_reg16(struct tcpci_softc *sc, uint8_t reg)
+{
+ uint16_t val = 0;
+
+ iic_acquire_bus(sc->sc_tag, 0);
+ if (iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP,
+ sc->sc_addr, &reg, sizeof(reg), &val, sizeof(val), 0)) {
+ printf("%s: cannot read register 0x%x\n",
+ sc->sc_dev.dv_xname, reg);
+ }
+ iic_release_bus(sc->sc_tag, 0);
+
+ return val;
+}
+
+void
+tcpci_write_reg16(struct tcpci_softc *sc, uint8_t reg, uint16_t val)
+{
+ iic_acquire_bus(sc->sc_tag, 0);
+ if (iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP,
+ sc->sc_addr, &reg, sizeof(reg), &val, sizeof(val), 0)) {
+ printf("%s: cannot write register 0x%x\n",
+ sc->sc_dev.dv_xname, reg);
+ }
+ iic_release_bus(sc->sc_tag, 0);
+}