Hi,
This is the fourth (and hopefully last RFC) of my proposal for more
complete alternate mode support. In this version the main difference
compared to the previous versions is the displayport alt mode driver.
For those who have no idea what is this about, the idea is to add a
bus type for the USB-C alternate modes. All alternate modes, for
example DisplayPort, require specific communication, so we will need
separate driver for every alternate mode. For more details please read
the cover letter of the previous version here:
https://lkml.org/lkml/2018/5/11/323
It took some time for me to get permission to publish the displayport
alt mode driver. Sorry for the delay. I was actually hoping that I
could include changes also to the drm subsystem already now, but it
ended up being more complicated than I first though, and I quite
simply don't have time for that, so let's just start with these.
Heikki Krogerus (8):
usb: pd: include kernel.h
usb: typec: helper for checking cable plug orientation
usb: typec: mux: Get the mux identifier from function parameter
usb: typec: Register a device for every mode
usb: typec: Bus type for alternate modes
usb: typec: Add driver for DisplayPort alternate mode
usb: typec: pi3usb30532: Start using generic state values
usb: typec: tcpm: Support for Alternate Modes
Documentation/ABI/obsolete/sysfs-class-typec | 48 ++
Documentation/ABI/testing/sysfs-bus-typec | 51 ++
Documentation/ABI/testing/sysfs-class-typec | 62 +-
.../testing/sysfs-driver-typec-displayport | 49 ++
Documentation/driver-api/usb/typec_bus.rst | 136 +++++
MAINTAINERS | 13 +-
drivers/usb/typec/Kconfig | 2 +
drivers/usb/typec/Makefile | 3 +-
drivers/usb/typec/altmodes/Kconfig | 14 +
drivers/usb/typec/altmodes/Makefile | 2 +
drivers/usb/typec/altmodes/displayport.c | 543 ++++++++++++++++++
drivers/usb/typec/bus.c | 401 +++++++++++++
drivers/usb/typec/bus.h | 38 ++
drivers/usb/typec/class.c | 485 ++++++++++------
drivers/usb/typec/mux.c | 6 +-
drivers/usb/typec/mux/pi3usb30532.c | 15 +-
drivers/usb/typec/tcpm.c | 184 ++++--
include/linux/mod_devicetable.h | 15 +
include/linux/usb/pd.h | 1 +
include/linux/usb/tcpm.h | 9 -
include/linux/usb/typec.h | 52 +-
include/linux/usb/typec_altmode.h | 160 ++++++
include/linux/usb/typec_dp.h | 70 +++
include/linux/usb/typec_mux.h | 2 +-
scripts/mod/devicetable-offsets.c | 4 +
scripts/mod/file2alias.c | 13 +
26 files changed, 2053 insertions(+), 325 deletions(-)
create mode 100644 Documentation/ABI/obsolete/sysfs-class-typec
create mode 100644 Documentation/ABI/testing/sysfs-bus-typec
create mode 100644 Documentation/ABI/testing/sysfs-driver-typec-displayport
create mode 100644 Documentation/driver-api/usb/typec_bus.rst
create mode 100644 drivers/usb/typec/altmodes/Kconfig
create mode 100644 drivers/usb/typec/altmodes/Makefile
create mode 100644 drivers/usb/typec/altmodes/displayport.c
create mode 100644 drivers/usb/typec/bus.c
create mode 100644 drivers/usb/typec/bus.h
create mode 100644 include/linux/usb/typec_altmode.h
create mode 100644 include/linux/usb/typec_dp.h
--
2.17.1
DisplayPort USB Type-C Alt Mode allows DisplayPort displays
and adapters to be attached to the USB Type-C ports on the
system.
Signed-off-by: Heikki Krogerus <[email protected]>
---
.../testing/sysfs-driver-typec-displayport | 49 ++
drivers/usb/typec/Kconfig | 2 +
drivers/usb/typec/Makefile | 1 +
drivers/usb/typec/altmodes/Kconfig | 14 +
drivers/usb/typec/altmodes/Makefile | 2 +
drivers/usb/typec/altmodes/displayport.c | 543 ++++++++++++++++++
include/linux/usb/typec_dp.h | 70 +++
7 files changed, 681 insertions(+)
create mode 100644 Documentation/ABI/testing/sysfs-driver-typec-displayport
create mode 100644 drivers/usb/typec/altmodes/Kconfig
create mode 100644 drivers/usb/typec/altmodes/Makefile
create mode 100644 drivers/usb/typec/altmodes/displayport.c
create mode 100644 include/linux/usb/typec_dp.h
diff --git a/Documentation/ABI/testing/sysfs-driver-typec-displayport b/Documentation/ABI/testing/sysfs-driver-typec-displayport
new file mode 100644
index 000000000000..66d72185cfd5
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-driver-typec-displayport
@@ -0,0 +1,49 @@
+What: /sys/bus/typec/devices/.../displayport/configuration
+Date: July 2018
+Contact: Heikki Krogerus <[email protected]>
+Description:
+ Shows the current DisplayPort configuration for the connector.
+ Valid values are USB, source and sink. Source means DisplayPort
+ source, and sink means DisplayPort sink.
+
+ All supported configurations are listed as space separated list
+ with the active one wrapped in square brackets.
+
+ Source example:
+
+ USB [source] sink
+
+ The configuration can be changed by writing to the file
+
+ Note. USB configuration does not equal to Exit Mode. It is
+ separate configuration defined in VESA DisplayPort Alt Mode on
+ USB Type-C Standard. Functionally it equals to the situation
+ where the mode has been exited (to exit the mode, see
+ Documentation/ABI/testing/sysfs-bus-typec, and use file
+ /sys/bus/typec/devices/.../active).
+
+What: /sys/bus/typec/devices/.../displayport/pin_assignment
+Date: July 2018
+Contact: Heikki Krogerus <[email protected]>
+Description:
+ VESA DisplayPort Alt Mode on USB Type-C Standard defines six
+ different pin assignments for USB Type-C connector that are
+ labeled A, B, C, D, E, and F. The supported pin assignments are
+ listed as space separated list with the active one wrapped in
+ square brackets.
+
+ Example:
+
+ C [D]
+
+ Pin assignment can be changed by writing to the file. It is
+ possible to set pin assignment before configuration has been
+ set, but the assignment will not be active before the
+ connector is actually configured.
+
+ Note. As of VESA DisplayPort Alt Mode on USB Type-C Standard
+ version 1.0b, pin assignments A, B, and F are deprecated. Only
+ pin assignments D can now carry simultaneously one channel of
+ USB SuperSpeed protocol. From user perspective pin assignments C
+ and E are equal, where all channels on the connector are used
+ for carrying DisplayPort protocol (allowing higher resolutions).
diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig
index 2c8eab11a493..652d49ede8b8 100644
--- a/drivers/usb/typec/Kconfig
+++ b/drivers/usb/typec/Kconfig
@@ -88,4 +88,6 @@ config TYPEC_TPS6598X
source "drivers/usb/typec/mux/Kconfig"
+source "drivers/usb/typec/altmodes/Kconfig"
+
endif # TYPEC
diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile
index 5466c62c8e97..65651ec65d19 100644
--- a/drivers/usb/typec/Makefile
+++ b/drivers/usb/typec/Makefile
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_TYPEC) += typec.o
typec-y := class.o mux.o bus.o
+obj-$(CONFIG_TYPEC) += altmodes/
obj-$(CONFIG_TYPEC_TCPM) += tcpm.o
obj-y += fusb302/
obj-$(CONFIG_TYPEC_WCOVE) += typec_wcove.o
diff --git a/drivers/usb/typec/altmodes/Kconfig b/drivers/usb/typec/altmodes/Kconfig
new file mode 100644
index 000000000000..efef2a64bc51
--- /dev/null
+++ b/drivers/usb/typec/altmodes/Kconfig
@@ -0,0 +1,14 @@
+
+menu "USB Type-C Alternate Mode drivers"
+
+config TYPEC_DP_ALTMODE
+ tristate "DisplayPort Alternate Mode driver"
+ help
+ DisplayPort USB Type-C Alternate Mode allows DisplayPort
+ displays and adapters to be attached to the USB Type-C
+ connectors on the system.
+
+ To compile this driver as a module, choose M here: the
+ module will be called typec_displayport.
+
+endmenu
diff --git a/drivers/usb/typec/altmodes/Makefile b/drivers/usb/typec/altmodes/Makefile
new file mode 100644
index 000000000000..5caf094ef71a
--- /dev/null
+++ b/drivers/usb/typec/altmodes/Makefile
@@ -0,0 +1,2 @@
+obj-$(CONFIG_TYPEC_DP_ALTMODE) += typec_displayport.o
+typec_displayport-y := displayport.o
diff --git a/drivers/usb/typec/altmodes/displayport.c b/drivers/usb/typec/altmodes/displayport.c
new file mode 100644
index 000000000000..a5054d86a4d9
--- /dev/null
+++ b/drivers/usb/typec/altmodes/displayport.c
@@ -0,0 +1,543 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ * USB Typec-C DisplayPort Alternate Mode driver
+ *
+ * Copyright (C) 2018 Intel Corporation
+ * Author: Heikki Krogerus <[email protected]>
+ *
+ * DisplayPort is trademark of VESA (http://www.vesa.org)
+ */
+
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/module.h>
+#include <linux/usb/pd_vdo.h>
+#include <linux/usb/typec_dp.h>
+
+#define DP_HEADER(cmd) (VDO(USB_TYPEC_DP_SID, 1, cmd) | \
+ VDO_OPOS(USB_TYPEC_DP_MODE))
+
+/* DisplayPort alt mode specific commands */
+#define DP_CMD_STATUS_UPDATE VDO_CMD_VENDOR(0)
+#define DP_CMD_CONFIGURE VDO_CMD_VENDOR(1)
+
+enum {
+ DP_CONF_USB,
+ DP_CONF_DFP_D,
+ DP_CONF_UFP_D,
+ DP_CONF_DUAL_D,
+};
+
+/* DisplayPort Capabilities VDO bits */
+#define DP_CAP_CAPABILITY(_cap_) ((_cap_) & 3)
+#define DP_CAP_UFP_D 1
+#define DP_CAP_DFP_D 2
+#define DP_CAP_DFP_D_AND_UFP_D 3
+#define DP_CAP_DP_SIGNALING BIT(2) /* Always set */
+#define DP_CAP_GEN2 BIT(3) /* Reserved after v1.0b */
+#define DP_CAP_RECEPTACLE BIT(6)
+#define DP_CAP_USB BIT(7)
+#define DP_CAP_DFP_D_PIN_ASSIGN(_cap_) (((_cap_) & GENMASK(15, 8)) >> 8)
+#define DP_CAP_UFP_D_PIN_ASSIGN(_cap_) (((_cap_) & GENMASK(23, 16)) >> 16)
+
+enum {
+ DP_PIN_ASSIGN_A, /* Not supported after v1.0b */
+ DP_PIN_ASSIGN_B, /* Not supported after v1.0b */
+ DP_PIN_ASSIGN_C,
+ DP_PIN_ASSIGN_D,
+ DP_PIN_ASSIGN_E,
+ DP_PIN_ASSIGN_F, /* Not supported after v1.0b */
+};
+
+/* Helper for setting/getting the pin assignement value to the configuration */
+#define DP_CONF_SET_PIN_ASSIGN(_a_) ((_a_) << 8)
+#define DP_CONF_GET_PIN_ASSIGN(_conf_) (((_conf_) & GENMASK(15, 8)) >> 8)
+
+/* Pin assignments that use USB3.1 Gen2 signaling to carry DP protocol */
+#define DP_PIN_ASSIGN_GEN2_BR_MASK (BIT(DP_PIN_ASSIGN_A) | \
+ BIT(DP_PIN_ASSIGN_B))
+
+/* Pin assignments that use DP v1.3 signaling to carry DP protocol */
+#define DP_PIN_ASSIGN_DP_BR_MASK (BIT(DP_PIN_ASSIGN_C) | \
+ BIT(DP_PIN_ASSIGN_D) | \
+ BIT(DP_PIN_ASSIGN_E) | \
+ BIT(DP_PIN_ASSIGN_F))
+
+/* DP only pin assignments */
+#define DP_PIN_ASSIGN_DP_ONLY_MASK (BIT(DP_PIN_ASSIGN_A) | \
+ BIT(DP_PIN_ASSIGN_C) | \
+ BIT(DP_PIN_ASSIGN_E))
+
+/* Pin assignments where one channel is for USB */
+#define DP_PIN_ASSIGN_MULTI_FUNC_MASK (BIT(DP_PIN_ASSIGN_B) | \
+ BIT(DP_PIN_ASSIGN_D) | \
+ BIT(DP_PIN_ASSIGN_F))
+
+enum dp_state {
+ DP_STATE_NONE,
+ DP_STATE_ENTER,
+ DP_STATE_UPDATE,
+ DP_STATE_CONFIGURE,
+ DP_STATE_EXIT,
+};
+
+struct dp_altmode {
+ struct typec_displayport_data data;
+
+ enum dp_state state;
+
+ struct mutex lock;
+ struct work_struct work;
+ struct typec_altmode *alt;
+ const struct typec_altmode *port;
+};
+
+static int dp_altmode_configure(struct dp_altmode *dp, u8 con)
+{
+ u32 conf = DP_CONF_SIGNALING_DP; /* Only DP signaling supported */
+ u8 pin_assign = 0;
+
+ switch (con) {
+ case DP_STATUS_CON_DISABLED:
+ dp->data.conf = 0;
+ return 0;
+ case DP_STATUS_CON_DFP_D:
+ conf |= DP_CONF_UFP_U_AS_DFP_D;
+ pin_assign = DP_CAP_UFP_D_PIN_ASSIGN(dp->alt->vdo) &
+ DP_CAP_DFP_D_PIN_ASSIGN(dp->port->vdo);
+ break;
+ case DP_STATUS_CON_UFP_D:
+ case DP_STATUS_CON_BOTH: /* NOTE: First acting as DP source */
+ conf |= DP_CONF_UFP_U_AS_UFP_D;
+ pin_assign = DP_CAP_DFP_D_PIN_ASSIGN(dp->alt->vdo) &
+ DP_CAP_UFP_D_PIN_ASSIGN(dp->port->vdo);
+ break;
+ default:
+ break;
+ }
+
+ /* Determining the initial pin assignment. */
+ if (!DP_CONF_GET_PIN_ASSIGN(dp->data.conf)) {
+ /* Is USB together with DP preferred */
+ if (dp->data.status & DP_STATUS_PREFER_MULTI_FUNC &&
+ pin_assign & DP_PIN_ASSIGN_MULTI_FUNC_MASK)
+ pin_assign &= DP_PIN_ASSIGN_MULTI_FUNC_MASK;
+ else
+ pin_assign &= DP_PIN_ASSIGN_DP_ONLY_MASK;
+
+ if (!pin_assign)
+ return -EINVAL;
+
+ conf |= DP_CONF_SET_PIN_ASSIGN(pin_assign);
+ }
+
+ dp->data.conf |= conf;
+
+ return 0;
+}
+
+static int dp_altmode_status_update(struct dp_altmode *dp)
+{
+ bool configured = !!DP_CONF_GET_PIN_ASSIGN(dp->data.conf);
+ u8 con = DP_STATUS_CONNECTION(dp->data.status);
+ int ret = 0;
+
+ if (configured && (dp->data.status & DP_STATUS_SWITCH_TO_USB)) {
+ dp->data.conf = 0;
+ dp->state = DP_STATE_CONFIGURE;
+ } else if (dp->data.status & DP_STATUS_EXIT_DP_MODE) {
+ dp->state = DP_STATE_EXIT;
+ } else if (!(con & DP_CONF_CURRENTLY(dp->data.conf))) {
+ ret = dp_altmode_configure(dp, con);
+ if (!ret)
+ dp->state = DP_STATE_CONFIGURE;
+ }
+
+ return ret;
+}
+
+static int dp_altmode_configured(struct dp_altmode *dp)
+{
+ u8 state;
+ int ret;
+
+ sysfs_notify(&dp->alt->dev.kobj, "displayport", "configuration");
+
+ if (!dp->data.conf)
+ return typec_altmode_notify(dp->alt, TYPEC_STATE_USB,
+ &dp->data);
+
+ state = get_count_order(DP_CONF_GET_PIN_ASSIGN(dp->data.conf));
+ ret = typec_altmode_notify(dp->alt, TYPEC_MODAL_STATE(state),
+ &dp->data);
+ if (ret)
+ return ret;
+
+ sysfs_notify(&dp->alt->dev.kobj, "displayport", "pin_assignment");
+
+ return 0;
+}
+
+static void dp_altmode_work(struct work_struct *work)
+{
+ struct dp_altmode *dp = container_of(work, struct dp_altmode, work);
+ u32 header = 0;
+ u32 vdo;
+ int ret;
+
+ mutex_lock(&dp->lock);
+
+ switch (dp->state) {
+ case DP_STATE_ENTER:
+ ret = typec_altmode_enter(dp->alt);
+ if (ret)
+ dev_err(&dp->alt->dev, "failed to enter mode\n");
+ break;
+ case DP_STATE_UPDATE:
+ header = DP_HEADER(DP_CMD_STATUS_UPDATE);
+ vdo = 1;
+ break;
+ case DP_STATE_CONFIGURE:
+ ret = typec_altmode_notify(dp->alt, TYPEC_STATE_SAFE,
+ &dp->data);
+ if (ret) {
+ dev_err(&dp->alt->dev,
+ "unable to put to connector to safe mode\n");
+ break;
+ }
+ header = DP_HEADER(DP_CMD_CONFIGURE);
+ vdo = dp->data.conf;
+ break;
+ case DP_STATE_EXIT:
+ if (typec_altmode_exit(dp->alt))
+ dev_err(&dp->alt->dev, "Exit Mode Failed!\n");
+ break;
+ default:
+ break;
+ }
+
+ if (header) {
+ if (typec_altmode_vdm(dp->alt, header, &vdo, 2))
+ dev_err(&dp->alt->dev, "unable to send VDM\n");
+ }
+
+ mutex_unlock(&dp->lock);
+}
+
+static void dp_altmode_attention(struct typec_altmode *alt, const u32 vdo)
+{
+ struct dp_altmode *dp = typec_altmode_get_drvdata(alt);
+ u8 state;
+
+ mutex_lock(&dp->lock);
+
+ dp->state = DP_STATE_NONE;
+ dp->data.status = vdo;
+ dp_altmode_status_update(dp);
+
+ if (dp->state == DP_STATE_NONE) {
+ state = get_count_order(DP_CONF_GET_PIN_ASSIGN(dp->data.conf));
+ if (typec_altmode_notify(dp->alt, TYPEC_MODAL_STATE(state),
+ &dp->data))
+ dev_err(&alt->dev, "%s: notification failed\n",
+ __func__);
+ } else {
+ schedule_work(&dp->work);
+ }
+
+ mutex_unlock(&dp->lock);
+}
+
+static int dp_altmode_vdm(struct typec_altmode *alt,
+ const u32 hdr, const u32 *vdo, int count)
+{
+ struct dp_altmode *dp = typec_altmode_get_drvdata(alt);
+ int cmd_type = PD_VDO_CMDT(hdr);
+ int cmd = PD_VDO_CMD(hdr);
+ int ret = 0;
+
+ mutex_lock(&dp->lock);
+
+ dp->state = DP_STATE_NONE;
+
+ switch (cmd_type) {
+ case CMDT_RSP_ACK:
+ switch (cmd) {
+ case CMD_ENTER_MODE:
+ dp->state = DP_STATE_UPDATE;
+ break;
+ case CMD_EXIT_MODE:
+ dp->data.status = 0;
+ dp->data.conf = 0;
+ break;
+ case DP_CMD_STATUS_UPDATE:
+ dp->data.status = *vdo;
+ ret = dp_altmode_status_update(dp);
+ break;
+ case DP_CMD_CONFIGURE:
+ ret = dp_altmode_configured(dp);
+ break;
+ default:
+ break;
+ }
+ break;
+ case CMDT_RSP_NAK:
+ switch (cmd) {
+ case DP_CMD_CONFIGURE:
+ dp->data.conf = 0;
+ ret = dp_altmode_configured(dp);
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (dp->state != DP_STATE_NONE)
+ schedule_work(&dp->work);
+
+ mutex_unlock(&dp->lock);
+ return ret;
+}
+
+static int dp_altmode_activate(struct typec_altmode *alt, int activate)
+{
+ return activate ? typec_altmode_enter(alt) : typec_altmode_exit(alt);
+}
+
+static const struct typec_altmode_ops dp_altmode_ops = {
+ .attention = dp_altmode_attention,
+ .vdm = dp_altmode_vdm,
+ .activate = dp_altmode_activate,
+};
+
+static const char * const configurations[] = {
+ [DP_CONF_USB] = "USB",
+ [DP_CONF_DFP_D] = "source",
+ [DP_CONF_UFP_D] = "sink",
+};
+
+static ssize_t
+configuration_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct dp_altmode *dp = dev_get_drvdata(dev);
+ int conf;
+ int ret;
+
+ conf = sysfs_match_string(configurations, buf);
+ if (conf < 0)
+ return conf;
+
+ mutex_lock(&dp->lock);
+
+ ret = dp_altmode_configure(dp, conf);
+ if (!ret && dp->alt->active) {
+ dp->state = DP_STATE_CONFIGURE;
+ schedule_work(&dp->work);
+ }
+
+ mutex_unlock(&dp->lock);
+
+ return ret ? ret : size;
+}
+
+static ssize_t configuration_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct dp_altmode *dp = dev_get_drvdata(dev);
+ int len;
+ u8 cap;
+ u8 cur;
+ int i;
+
+ mutex_lock(&dp->lock);
+
+ cap = DP_CAP_CAPABILITY(dp->alt->vdo);
+ cur = DP_CONF_CURRENTLY(dp->data.conf);
+
+ len = sprintf(buf, "%s ", cur ? "USB" : "[USB]");
+
+ for (i = 1; i < ARRAY_SIZE(configurations); i++) {
+ if (i == cur)
+ len += sprintf(buf + len, "[%s] ", configurations[i]);
+ else if ((i == DP_CONF_DFP_D && cap & DP_CAP_DFP_D) ||
+ (i == DP_CONF_UFP_D && cap & DP_CAP_UFP_D))
+ len += sprintf(buf + len, "%s ", configurations[i]);
+ }
+
+ mutex_unlock(&dp->lock);
+
+ buf[len - 1] = '\n';
+ return len;
+}
+static DEVICE_ATTR_RW(configuration);
+
+static const char * const pin_assignments[] = {
+ [DP_PIN_ASSIGN_A] = "A",
+ [DP_PIN_ASSIGN_B] = "B",
+ [DP_PIN_ASSIGN_C] = "C",
+ [DP_PIN_ASSIGN_D] = "D",
+ [DP_PIN_ASSIGN_E] = "E",
+ [DP_PIN_ASSIGN_F] = "F",
+};
+
+static ssize_t
+pin_assignment_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct dp_altmode *dp = dev_get_drvdata(dev);
+ u8 assignments;
+ u32 conf;
+ int ret;
+
+ ret = sysfs_match_string(pin_assignments, buf);
+ if (ret < 0)
+ return ret;
+
+ conf = DP_CONF_SET_PIN_ASSIGN(BIT(ret));
+ ret = 0;
+
+ mutex_lock(&dp->lock);
+
+ if (conf & dp->data.conf)
+ goto out_unlock;
+
+ if (DP_CONF_CURRENTLY(dp->data.conf) == DP_CONF_DFP_D)
+ assignments = DP_CAP_UFP_D_PIN_ASSIGN(dp->alt->vdo);
+ else
+ assignments = DP_CAP_DFP_D_PIN_ASSIGN(dp->alt->vdo);
+
+ if (!(DP_CONF_GET_PIN_ASSIGN(conf) & assignments)) {
+ ret = -EINVAL;
+ goto out_unlock;
+ }
+
+ /* Only send Configure command if a configuration has been set */
+ if (dp->alt->active && DP_CONF_CURRENTLY(dp->data.conf)) {
+ dp->state = DP_STATE_CONFIGURE;
+ schedule_work(&dp->work);
+ }
+
+ dp->data.conf &= ~DP_CONF_PIN_ASSIGNEMENT_MASK;
+ dp->data.conf |= conf;
+
+out_unlock:
+ mutex_unlock(&dp->lock);
+
+ return ret ? ret : size;
+}
+
+static ssize_t pin_assignment_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct dp_altmode *dp = dev_get_drvdata(dev);
+ u8 assignments;
+ int len = 0;
+ u8 cur;
+ int i;
+
+ mutex_lock(&dp->lock);
+
+ cur = get_count_order(DP_CONF_GET_PIN_ASSIGN(dp->data.conf));
+
+ if (DP_CONF_CURRENTLY(dp->data.conf) == DP_CONF_DFP_D)
+ assignments = DP_CAP_UFP_D_PIN_ASSIGN(dp->alt->vdo);
+ else
+ assignments = DP_CAP_DFP_D_PIN_ASSIGN(dp->alt->vdo);
+
+ for (i = 0; assignments; assignments >>= 1, i++) {
+ if (assignments & 1) {
+ if (i == cur)
+ len += sprintf(buf + len, "[%s] ",
+ pin_assignments[i]);
+ else
+ len += sprintf(buf + len, "%s ",
+ pin_assignments[i]);
+ }
+ }
+
+ mutex_unlock(&dp->lock);
+
+ buf[len - 1] = '\n';
+ return len;
+}
+static DEVICE_ATTR_RW(pin_assignment);
+
+static struct attribute *dp_altmode_attrs[] = {
+ &dev_attr_configuration.attr,
+ &dev_attr_pin_assignment.attr,
+ NULL
+};
+
+static const struct attribute_group dp_altmode_group = {
+ .name = "displayport",
+ .attrs = dp_altmode_attrs,
+};
+
+static int dp_altmode_probe(struct typec_altmode *alt)
+{
+ const struct typec_altmode *port = typec_altmode_get_partner(alt);
+ struct dp_altmode *dp;
+ int ret;
+
+ /* FIXME: Port can only be DFP_U. */
+
+ /* Make sure we have compatiple pin configurations */
+ if (!(DP_CAP_DFP_D_PIN_ASSIGN(port->vdo) &
+ DP_CAP_UFP_D_PIN_ASSIGN(alt->vdo)) &&
+ !(DP_CAP_UFP_D_PIN_ASSIGN(port->vdo) &
+ DP_CAP_DFP_D_PIN_ASSIGN(alt->vdo)))
+ return -ENODEV;
+
+ ret = sysfs_create_group(&alt->dev.kobj, &dp_altmode_group);
+ if (ret)
+ return ret;
+
+ dp = devm_kzalloc(&alt->dev, sizeof(*dp), GFP_KERNEL);
+ if (!dp)
+ return -ENOMEM;
+
+ INIT_WORK(&dp->work, dp_altmode_work);
+ mutex_init(&dp->lock);
+ dp->port = port;
+ dp->alt = alt;
+
+ alt->desc = "DisplayPort";
+ alt->ops = &dp_altmode_ops;
+
+ typec_altmode_set_drvdata(alt, dp);
+
+ dp->state = DP_STATE_ENTER;
+ schedule_work(&dp->work);
+
+ return 0;
+}
+
+static void dp_altmode_remove(struct typec_altmode *alt)
+{
+ sysfs_remove_group(&alt->dev.kobj, &dp_altmode_group);
+}
+
+static const struct typec_device_id dp_typec_id[] = {
+ { USB_TYPEC_DP_SID, USB_TYPEC_DP_MODE },
+ { },
+};
+MODULE_DEVICE_TABLE(typec, dp_typec_id);
+
+static struct typec_altmode_driver dp_altmode_driver = {
+ .id_table = dp_typec_id,
+ .probe = dp_altmode_probe,
+ .remove = dp_altmode_remove,
+ .driver = {
+ .name = "typec_displayport",
+ .owner = THIS_MODULE,
+ },
+};
+module_typec_altmode_driver(dp_altmode_driver);
+
+MODULE_AUTHOR("Heikki Krogerus <[email protected]>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("DisplayPort Alternate Mode");
diff --git a/include/linux/usb/typec_dp.h b/include/linux/usb/typec_dp.h
new file mode 100644
index 000000000000..5ca989076d7c
--- /dev/null
+++ b/include/linux/usb/typec_dp.h
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __USB_TYPEC_DP_H
+#define __USB_TYPEC_DP_H
+
+#include <linux/usb/typec_altmode.h>
+
+#define USB_TYPEC_DP_SID 0xff01
+#define USB_TYPEC_DP_MODE 1
+
+/*
+ * Connector states matching the pin assignments in DisplayPort Alt Mode
+ * Specification.
+ *
+ * These values are meant primarily to be used by the mux drivers, but they are
+ * also used as the "value" part in the alternate mode notification chain, so
+ * receivers of those notifications will always see them.
+ *
+ * Note. DisplayPort USB Type-C Alt Mode Specification version 1.0b deprecated
+ * pin assignments A, B and F, but they are still defined here for legacy
+ * purposes.
+ */
+enum {
+ TYPEC_DP_STATE_A = TYPEC_STATE_MODAL, /* Not supported after v1.0b */
+ TYPEC_DP_STATE_B, /* Not supported after v1.0b */
+ TYPEC_DP_STATE_C,
+ TYPEC_DP_STATE_D,
+ TYPEC_DP_STATE_E,
+ TYPEC_DP_STATE_F, /* Not supported after v1.0b */
+};
+
+/*
+ * struct typec_displayport_data - DisplayPort Alt Mode specific data
+ * @status: Status Update command VDO content
+ * @conf: Configure command VDO content
+ *
+ * This structure is delivered as the data part with the notifications. It
+ * contains the VDOs from the two DisplayPort Type-C alternate mode specific
+ * commands: Status Update and Configure.
+ *
+ * @status will show for example the status of the HPD signal.
+ */
+struct typec_displayport_data {
+ u32 status;
+ u32 conf;
+};
+
+/* DisplayPort Status Update VDO bits */
+#define DP_STATUS_CONNECTION(_status_) ((_status_) & 3)
+#define DP_STATUS_CON_DISABLED 0
+#define DP_STATUS_CON_DFP_D 1
+#define DP_STATUS_CON_UFP_D 2
+#define DP_STATUS_CON_BOTH 3
+#define DP_STATUS_POWER_LOW BIT(2)
+#define DP_STATUS_ENABLED BIT(3)
+#define DP_STATUS_PREFER_MULTI_FUNC BIT(4)
+#define DP_STATUS_SWITCH_TO_USB BIT(5)
+#define DP_STATUS_EXIT_DP_MODE BIT(6)
+#define DP_STATUS_HPD_STATE BIT(7) /* 0 = HPD_Low, 1 = HPD_High */
+#define DP_STATUS_IRQ_HPD BIT(8)
+
+/* DisplayPort Configurations VDO bits */
+#define DP_CONF_CURRENTLY(_conf_) ((_conf_) & 3)
+#define DP_CONF_UFP_U_AS_DFP_D BIT(0)
+#define DP_CONF_UFP_U_AS_UFP_D BIT(1)
+#define DP_CONF_SIGNALING_DP BIT(2)
+#define DP_CONF_SIGNALING_GEN_2 BIT(3) /* Reserved after v1.0b */
+#define DP_CONF_PIN_ASSIGNEMENT_SHIFT 8
+#define DP_CONF_PIN_ASSIGNEMENT_MASK GENMASK(15, 8)
+
+#endif /* __USB_TYPEC_DP_H */
--
2.17.1
Introducing a simple bus for the alternate modes. Bus allows
binding drivers to the discovered alternate modes the
partners support.
Signed-off-by: Heikki Krogerus <[email protected]>
---
Documentation/ABI/obsolete/sysfs-class-typec | 48 +++
Documentation/ABI/testing/sysfs-bus-typec | 51 +++
Documentation/ABI/testing/sysfs-class-typec | 62 +--
Documentation/driver-api/usb/typec_bus.rst | 136 +++++++
MAINTAINERS | 13 +-
drivers/usb/typec/Makefile | 2 +-
drivers/usb/typec/bus.c | 401 +++++++++++++++++++
drivers/usb/typec/bus.h | 38 ++
drivers/usb/typec/class.c | 366 +++++++++++++----
include/linux/mod_devicetable.h | 15 +
include/linux/usb/typec.h | 14 +-
include/linux/usb/typec_altmode.h | 160 ++++++++
scripts/mod/devicetable-offsets.c | 4 +
scripts/mod/file2alias.c | 13 +
14 files changed, 1175 insertions(+), 148 deletions(-)
create mode 100644 Documentation/ABI/obsolete/sysfs-class-typec
create mode 100644 Documentation/ABI/testing/sysfs-bus-typec
create mode 100644 Documentation/driver-api/usb/typec_bus.rst
create mode 100644 drivers/usb/typec/bus.c
create mode 100644 drivers/usb/typec/bus.h
create mode 100644 include/linux/usb/typec_altmode.h
diff --git a/Documentation/ABI/obsolete/sysfs-class-typec b/Documentation/ABI/obsolete/sysfs-class-typec
new file mode 100644
index 000000000000..32623514ee87
--- /dev/null
+++ b/Documentation/ABI/obsolete/sysfs-class-typec
@@ -0,0 +1,48 @@
+These files are deprecated and will be removed. The same files are available
+under /sys/bus/typec (see Documentation/ABI/testing/sysfs-bus-typec).
+
+What: /sys/class/typec/<port|partner|cable>/<dev>/svid
+Date: April 2017
+Contact: Heikki Krogerus <[email protected]>
+Description:
+ The SVID (Standard or Vendor ID) assigned by USB-IF for this
+ alternate mode.
+
+What: /sys/class/typec/<port|partner|cable>/<dev>/mode<index>/
+Date: April 2017
+Contact: Heikki Krogerus <[email protected]>
+Description:
+ Every supported mode will have its own directory. The name of
+ a mode will be "mode<index>" (for example mode1), where <index>
+ is the actual index to the mode VDO returned by Discover Modes
+ USB power delivery command.
+
+What: /sys/class/typec/<port|partner|cable>/<dev>/mode<index>/description
+Date: April 2017
+Contact: Heikki Krogerus <[email protected]>
+Description:
+ Shows description of the mode. The description is optional for
+ the drivers, just like with the Billboard Devices.
+
+What: /sys/class/typec/<port|partner|cable>/<dev>/mode<index>/vdo
+Date: April 2017
+Contact: Heikki Krogerus <[email protected]>
+Description:
+ Shows the VDO in hexadecimal returned by Discover Modes command
+ for this mode.
+
+What: /sys/class/typec/<port|partner|cable>/<dev>/mode<index>/active
+Date: April 2017
+Contact: Heikki Krogerus <[email protected]>
+Description:
+ Shows if the mode is active or not. The attribute can be used
+ for entering/exiting the mode with partners and cable plugs, and
+ with the port alternate modes it can be used for disabling
+ support for specific alternate modes. Entering/exiting modes is
+ supported as synchronous operation so write(2) to the attribute
+ does not return until the enter/exit mode operation has
+ finished. The attribute is notified when the mode is
+ entered/exited so poll(2) on the attribute wakes up.
+ Entering/exiting a mode will also generate uevent KOBJ_CHANGE.
+
+ Valid values: yes, no
diff --git a/Documentation/ABI/testing/sysfs-bus-typec b/Documentation/ABI/testing/sysfs-bus-typec
new file mode 100644
index 000000000000..205d9c91e2e1
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-bus-typec
@@ -0,0 +1,51 @@
+What: /sys/bus/typec/devices/.../active
+Date: July 2018
+Contact: Heikki Krogerus <[email protected]>
+Description:
+ Shows if the mode is active or not. The attribute can be used
+ for entering/exiting the mode. Entering/exiting modes is
+ supported as synchronous operation so write(2) to the attribute
+ does not return until the enter/exit mode operation has
+ finished. The attribute is notified when the mode is
+ entered/exited so poll(2) on the attribute wakes up.
+ Entering/exiting a mode will also generate uevent KOBJ_CHANGE.
+
+ Valid values are boolean.
+
+What: /sys/bus/typec/devices/.../description
+Date: July 2018
+Contact: Heikki Krogerus <[email protected]>
+Description:
+ Shows description of the mode. The description is optional for
+ the drivers, just like with the Billboard Devices.
+
+What: /sys/bus/typec/devices/.../mode
+Date: July 2018
+Contact: Heikki Krogerus <[email protected]>
+Description:
+ The index number of the mode returned by Discover Modes USB
+ Power Delivery command. Depending on the alternate mode, the
+ mode index may be significant.
+
+ With some alternate modes (SVIDs), the mode index is assigned
+ for specific functionality in the specification for that
+ alternate mode.
+
+ With other alternate modes, the mode index values are not
+ assigned, and can not be therefore used for identification. When
+ the mode index is not assigned, identifying the alternate mode
+ must be done with either mode VDO or the description.
+
+What: /sys/bus/typec/devices/.../svid
+Date: July 2018
+Contact: Heikki Krogerus <[email protected]>
+Description:
+ The Standard or Vendor ID (SVID) assigned by USB-IF for this
+ alternate mode.
+
+What: /sys/bus/typec/devices/.../vdo
+Date: July 2018
+Contact: Heikki Krogerus <[email protected]>
+Description:
+ Shows the VDO in hexadecimal returned by Discover Modes command
+ for this mode.
diff --git a/Documentation/ABI/testing/sysfs-class-typec b/Documentation/ABI/testing/sysfs-class-typec
index 5be552e255e9..d7647b258c3c 100644
--- a/Documentation/ABI/testing/sysfs-class-typec
+++ b/Documentation/ABI/testing/sysfs-class-typec
@@ -222,70 +222,12 @@ Description:
available. The value can be polled.
-Alternate Mode devices.
+USB Type-C port alternate mode devices.
-The alternate modes will have Standard or Vendor ID (SVID) assigned by USB-IF.
-The ports, partners and cable plugs can have alternate modes. A supported SVID
-will consist of a set of modes. Every SVID a port/partner/plug supports will
-have a device created for it, and every supported mode for a supported SVID will
-have its own directory under that device. Below <dev> refers to the device for
-the alternate mode.
-
-What: /sys/class/typec/<port|partner|cable>/<dev>/svid
-Date: April 2017
-Contact: Heikki Krogerus <[email protected]>
-Description:
- The SVID (Standard or Vendor ID) assigned by USB-IF for this
- alternate mode.
-
-What: /sys/class/typec/<port|partner|cable>/<dev>/mode<index>/
-Date: April 2017
-Contact: Heikki Krogerus <[email protected]>
-Description:
- Every supported mode will have its own directory. The name of
- a mode will be "mode<index>" (for example mode1), where <index>
- is the actual index to the mode VDO returned by Discover Modes
- USB power delivery command.
-
-What: /sys/class/typec/<port|partner|cable>/<dev>/mode<index>/description
-Date: April 2017
-Contact: Heikki Krogerus <[email protected]>
-Description:
- Shows description of the mode. The description is optional for
- the drivers, just like with the Billboard Devices.
-
-What: /sys/class/typec/<port|partner|cable>/<dev>/mode<index>/vdo
-Date: April 2017
-Contact: Heikki Krogerus <[email protected]>
-Description:
- Shows the VDO in hexadecimal returned by Discover Modes command
- for this mode.
-
-What: /sys/class/typec/<port|partner|cable>/<dev>/mode<index>/active
-Date: April 2017
-Contact: Heikki Krogerus <[email protected]>
-Description:
- Shows if the mode is active or not. The attribute can be used
- for entering/exiting the mode with partners and cable plugs, and
- with the port alternate modes it can be used for disabling
- support for specific alternate modes. Entering/exiting modes is
- supported as synchronous operation so write(2) to the attribute
- does not return until the enter/exit mode operation has
- finished. The attribute is notified when the mode is
- entered/exited so poll(2) on the attribute wakes up.
- Entering/exiting a mode will also generate uevent KOBJ_CHANGE.
-
- Valid values: yes, no
-
-What: /sys/class/typec/<port>/<dev>/mode<index>/supported_roles
+What: /sys/class/typec/<port>/<alt mode>/supported_roles
Date: April 2017
Contact: Heikki Krogerus <[email protected]>
Description:
Space separated list of the supported roles.
- This attribute is available for the devices describing the
- alternate modes a port supports, and it will not be exposed with
- the devices presenting the alternate modes the partners or cable
- plugs support.
-
Valid values: source, sink
diff --git a/Documentation/driver-api/usb/typec_bus.rst b/Documentation/driver-api/usb/typec_bus.rst
new file mode 100644
index 000000000000..55d2204aea29
--- /dev/null
+++ b/Documentation/driver-api/usb/typec_bus.rst
@@ -0,0 +1,136 @@
+
+API for USB Type-C Alternate Mode drivers
+=========================================
+
+Introduction
+------------
+
+Alternate modes require communication with the partner using Vendor Defined
+Messages (VDM) as defined in USB Type-C and USB Power Delivery Specifications.
+The communication is SVID (Standard or Vendor ID) specific, i.e. specific for
+every alternate mode, so every alternate mode will need a custom driver.
+
+USB Type-C bus allows binding a driver to the discovered partner alternate
+modes by using the SVID and the mode number.
+
+USB Type-C Connector Class provides a device for every alternate mode a port
+supports, and separate device for every alternate mode the partner supports.
+The drivers for the alternate modes are bound to the partner alternate mode
+devices, and the port alternate mode devices must be handled by the port
+drivers.
+
+When a new partner alternate mode device is registered, it is linked to the
+alternate mode device of the port that the partner is attached to, that has
+matching SVID and mode. Communication between the port driver and alternate mode
+driver will happen using the same API.
+
+The port alternate mode devices are used as a proxy between the partner and the
+alternate mode drivers, so the port drivers are only expected to pass the SVID
+specific commands from the alternate mode drivers to the partner, and from the
+partners to the alternate mode drivers. No direct SVID specific communication is
+needed from the port drivers, but the port drivers need to provide the operation
+callbacks for the port alternate mode devices, just like the alternate mode
+drivers need to provide them for the partner alternate mode devices.
+
+Usage:
+------
+
+General
+~~~~~~~
+
+By default, the alternate mode drivers are responsible for entering the mode.
+It is also possible to leave the decision about entering the mode to the user
+space (See Documentation/ABI/testing/sysfs-class-typec). Port drivers should not
+enter any modes on their own.
+
+``->vdm`` is the most important callback in the vector. It will be used to
+deliver all the SVID specific commands from the partner to the alternate mode
+driver, and vice versa in case of port drivers. The drivers send the SVID
+specific commands to each other using :c:func:`typec_altmode_vmd()`.
+
+If the communication with the partner using the SVID specific commands results
+in need to reconfigure the pins on the connector, the alternate mode driver
+needs to notify the bus using :c:func:`typec_altmode_notify()`. The driver
+passes the negotiated SVID specific pin configuration value to the function as
+parameter. The bus driver will then configure the mux behind the connector using
+that value as the state value for the mux, and also call blocking notification
+chain to notify the external drivers about the state of the connector that need
+to know it.
+
+NOTE: The SVID specific pin configuration values must always start from
+``TYPEC_STATE_MODAL``. USB Type-C specification defines two default states for
+the connector: ``TYPEC_STATE_USB`` and ``TYPEC_STATE_SAFE``. These values are
+reserved by the bus as the first possible values for the state. When the
+alternate mode is entered, the bus will put the connector into
+``TYPEC_STATE_SAFE`` before sending Enter or Exit Mode command as defined in USB
+Type-C Specification, and also put the connector back to ``TYPEC_STATE_USB``
+after the mode has been exited.
+
+An example of working definitions for SVID specific pin configurations would
+look like this:
+
+enum {
+ ALTMODEX_CONF_A = TYPEC_STATE_MODAL,
+ ALTMODEX_CONF_B,
+ ...
+};
+
+Helper macro ``TYPEC_MODAL_STATE()`` can also be used:
+
+#define ALTMODEX_CONF_A = TYPEC_MODAL_STATE(0);
+#define ALTMODEX_CONF_B = TYPEC_MODAL_STATE(1);
+
+Notification chain
+~~~~~~~~~~~~~~~~~~
+
+The drivers for the components that the alternate modes are designed for need to
+get details regarding the results of the negotiation with the partner, and the
+pin configuration of the connector. In case of DisplayPort alternate mode for
+example, the GPU drivers will need to know those details. In case of
+Thunderbolt alternate mode, the thunderbolt drivers will need to know them, and
+so on.
+
+The notification chain is designed for this purpose. The drivers can register
+notifiers with :c:func:`typec_altmode_register_notifier()`.
+
+Cable plug alternate modes
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The alternate mode drivers are not bound to cable plug alternate mode devices,
+only to the partner alternate mode devices. If the alternate mode supports, or
+requires, a cable that responds to SOP Prime, and optionally SOP Double Prime
+messages, the driver for that alternate mode must request handle to the cable
+plug alternate modes using :c:func:`typec_altmode_get_plug()`, and take over
+their control.
+
+Driver API
+----------
+
+Alternate mode driver registering/unregistering
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. kernel-doc:: drivers/usb/typec/bus.c
+ :functions: typec_altmode_register_driver typec_altmode_unregister_driver
+
+Alternate mode driver operations
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. kernel-doc:: drivers/usb/typec/bus.c
+ :functions: typec_altmode_enter typec_altmode_exit typec_altmode_attention typec_altmode_vdm typec_altmode_notify
+
+API for the port drivers
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. kernel-doc:: drivers/usb/typec/bus.c
+ :functions: typec_match_altmode
+
+Cable Plug operations
+~~~~~~~~~~~~~~~~~~~~~
+
+.. kernel-doc:: drivers/usb/typec/bus.c
+ :functions: typec_altmode_get_plug typec_altmode_put_plug
+
+Notifications
+~~~~~~~~~~~~~
+.. kernel-doc:: drivers/usb/typec/class.c
+ :functions: typec_altmode_register_notifier typec_altmode_unregister_notifier
diff --git a/MAINTAINERS b/MAINTAINERS
index f39a8de1bbd7..6b85813d6bb3 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14922,15 +14922,24 @@ L: [email protected]
S: Maintained
F: drivers/usb/typec/mux/pi3usb30532.c
-USB TYPEC SUBSYSTEM
+USB TYPEC CLASS
M: Heikki Krogerus <[email protected]>
L: [email protected]
S: Maintained
F: Documentation/ABI/testing/sysfs-class-typec
-F: Documentation/usb/typec.rst
+F: Documentation/driver-api/usb/typec.rst
F: drivers/usb/typec/
F: include/linux/usb/typec.h
+USB TYPEC BUS FOR ALTERNATE MODES
+M: Heikki Krogerus <[email protected]>
+L: [email protected]
+S: Maintained
+F: Documentation/ABI/testing/sysfs-bus-typec
+F: Documentation/driver-api/usb/typec_bus.rst
+F: drivers/usb/typec/altmodes/
+F: include/linux/usb/typec_altmode.h
+
USB UHCI DRIVER
M: Alan Stern <[email protected]>
L: [email protected]
diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile
index 1f599a6c30cc..5466c62c8e97 100644
--- a/drivers/usb/typec/Makefile
+++ b/drivers/usb/typec/Makefile
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_TYPEC) += typec.o
-typec-y := class.o mux.o
+typec-y := class.o mux.o bus.o
obj-$(CONFIG_TYPEC_TCPM) += tcpm.o
obj-y += fusb302/
obj-$(CONFIG_TYPEC_WCOVE) += typec_wcove.o
diff --git a/drivers/usb/typec/bus.c b/drivers/usb/typec/bus.c
new file mode 100644
index 000000000000..51b2484e55aa
--- /dev/null
+++ b/drivers/usb/typec/bus.c
@@ -0,0 +1,401 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ * Bus for USB Type-C Alternate Modes
+ *
+ * Copyright (C) 2018 Intel Corporation
+ * Author: Heikki Krogerus <[email protected]>
+ */
+
+#include <linux/usb/pd_vdo.h>
+
+#include "bus.h"
+
+static inline int typec_altmode_set_mux(struct altmode *alt, u8 state)
+{
+ return alt->mux ? alt->mux->set(alt->mux, state) : 0;
+}
+
+static int typec_altmode_set_state(struct typec_altmode *adev, int state)
+{
+ bool is_port = is_typec_port(adev->dev.parent);
+ struct altmode *port_altmode;
+ int ret;
+
+ port_altmode = is_port ? to_altmode(adev) : to_altmode(adev)->partner;
+
+ ret = typec_altmode_set_mux(port_altmode, state);
+ if (ret)
+ return ret;
+
+ blocking_notifier_call_chain(&port_altmode->nh, state, NULL);
+
+ return 0;
+}
+
+/* -------------------------------------------------------------------------- */
+/* Common API */
+
+/**
+ * typec_altmode_notify - Communication between the OS and alternate mode driver
+ * @adev: Handle to the alternate mode
+ * @conf: Alternate mode specific configuration value
+ * @data: Alternate mode specific data
+ *
+ * The primary purpose for this function is to allow the alternate mode drivers
+ * to tell which pin configuration has been negotiated with the partner. That
+ * information will then be used for example to configure the muxes.
+ * Communication to the other direction is also possible, and low level device
+ * drivers can also send notifications to the alternate mode drivers. The actual
+ * communication will be specific for every SVID.
+ */
+int typec_altmode_notify(struct typec_altmode *adev,
+ unsigned long conf, void *data)
+{
+ bool is_port = is_typec_port(adev->dev.parent);
+ struct altmode *altmode;
+ struct altmode *partner;
+ int ret;
+
+ if (!adev)
+ return 0;
+
+ altmode = to_altmode(adev);
+
+ if (!altmode->partner)
+ return -ENODEV;
+
+ partner = altmode->partner;
+
+ ret = typec_altmode_set_mux(is_port ? altmode : partner, (u8)conf);
+ if (ret)
+ return ret;
+
+ blocking_notifier_call_chain(is_port ? &altmode->nh : &partner->nh,
+ conf, data);
+
+ if (partner->adev.ops && partner->adev.ops->notify)
+ return partner->adev.ops->notify(&partner->adev, conf, data);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(typec_altmode_notify);
+
+/**
+ * typec_altmode_enter - Enter Mode
+ * @adev: The alternate mode
+ *
+ * The alternate mode drivers use this function to enter mode. The port drivers
+ * use this to inform the alternate mode drivers that the partner has initiated
+ * Enter Mode command.
+ */
+int typec_altmode_enter(struct typec_altmode *adev)
+{
+ struct altmode *partner = to_altmode(adev)->partner;
+ struct typec_altmode *pdev = &partner->adev;
+ int ret;
+
+ if (!adev || adev->active)
+ return 0;
+
+ /* Moving to USB Safe State */
+ ret = typec_altmode_set_state(adev, TYPEC_STATE_SAFE);
+ if (ret)
+ return ret;
+
+ /* Enter Mode */
+ if (pdev->ops && pdev->ops->enter)
+ pdev->ops->enter(pdev);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(typec_altmode_enter);
+
+/**
+ * typec_altmode_exit - Exit Mode
+ * @adev: The alternate mode
+ *
+ * The partner of @adev has initiated Exit Mode command.
+ */
+int typec_altmode_exit(struct typec_altmode *adev)
+{
+ struct altmode *partner = to_altmode(adev)->partner;
+ struct typec_altmode *pdev = &partner->adev;
+ int ret;
+
+ if (!adev || !adev->active)
+ return 0;
+
+ /* Moving to USB Safe State */
+ ret = typec_altmode_set_state(adev, TYPEC_STATE_SAFE);
+ if (ret)
+ return ret;
+
+ /* Exit Mode command */
+ if (pdev->ops && pdev->ops->exit)
+ pdev->ops->exit(pdev);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(typec_altmode_exit);
+
+/**
+ * typec_altmode_attention - Attention command
+ * @adev: The alternate mode
+ * @vdo: VDO for the Attention command
+ *
+ * Notifies the partner of @adev about Attention command.
+ */
+void typec_altmode_attention(struct typec_altmode *adev, u32 vdo)
+{
+ struct typec_altmode *pdev = &to_altmode(adev)->partner->adev;
+
+ if (pdev->ops && pdev->ops->attention)
+ pdev->ops->attention(pdev, vdo);
+}
+EXPORT_SYMBOL_GPL(typec_altmode_attention);
+
+/**
+ * typec_altmode_vdm - Send Vendor Defined Messages (VDM) to the partner
+ * @adev: Alternate mode handle
+ * @header: VDM Header
+ * @vdo: Array of Vendor Defined Data Objects
+ * @count: Number of Data Objects
+ *
+ * The alternate mode drivers use this function for SVID specific communication
+ * with the partner. The port drivers use it to deliver the Structured VDMs
+ * received from the partners to the alternate mode drivers.
+ */
+int typec_altmode_vdm(struct typec_altmode *adev,
+ const u32 header, const u32 *vdo, int count)
+{
+ struct typec_altmode *pdev;
+ struct altmode *altmode;
+
+ if (!adev)
+ return 0;
+
+ altmode = to_altmode(adev);
+
+ if (!altmode->partner)
+ return -ENODEV;
+
+ pdev = &altmode->partner->adev;
+
+ if (pdev->ops && pdev->ops->vdm)
+ return pdev->ops->vdm(pdev, header, vdo, count);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(typec_altmode_vdm);
+
+const struct typec_altmode *
+typec_altmode_get_partner(struct typec_altmode *adev)
+{
+ return &to_altmode(adev)->partner->adev;
+}
+EXPORT_SYMBOL_GPL(typec_altmode_get_partner);
+
+/* -------------------------------------------------------------------------- */
+/* API for the alternate mode drivers */
+
+/**
+ * typec_altmode_get_plug - Find cable plug alternate mode
+ * @adev: Handle to partner alternate mode
+ * @index: Cable plug index
+ *
+ * Increment reference count for cable plug alternate mode device. Returns
+ * handle to the cable plug alternate mode, or NULL if none is found.
+ */
+struct typec_altmode *typec_altmode_get_plug(struct typec_altmode *adev,
+ enum typec_plug_index index)
+{
+ struct altmode *port = to_altmode(adev)->partner;
+
+ if (port->plug[index]) {
+ get_device(&port->plug[index]->adev.dev);
+ return &port->plug[index]->adev;
+ }
+
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(typec_altmode_get_plug);
+
+/**
+ * typec_altmode_put_plug - Decrement cable plug alternate mode reference count
+ * @plug: Handle to the cable plug alternate mode
+ */
+void typec_altmode_put_plug(struct typec_altmode *plug)
+{
+ if (plug)
+ put_device(&plug->dev);
+}
+EXPORT_SYMBOL_GPL(typec_altmode_put_plug);
+
+int __typec_altmode_register_driver(struct typec_altmode_driver *drv,
+ struct module *module)
+{
+ if (!drv->probe)
+ return -EINVAL;
+
+ drv->driver.owner = module;
+ drv->driver.bus = &typec_bus;
+
+ return driver_register(&drv->driver);
+}
+EXPORT_SYMBOL_GPL(__typec_altmode_register_driver);
+
+void typec_altmode_unregister_driver(struct typec_altmode_driver *drv)
+{
+ driver_unregister(&drv->driver);
+}
+EXPORT_SYMBOL_GPL(typec_altmode_unregister_driver);
+
+/* -------------------------------------------------------------------------- */
+/* API for the port drivers */
+
+/**
+ * typec_match_altmode - Match SVID to an array of alternate modes
+ * @altmodes: Array of alternate modes
+ * @n: Number of elements in the array, or -1 for NULL termiated arrays
+ * @svid: Standard or Vendor ID to match with
+ *
+ * Return pointer to an alternate mode with SVID mathing @svid, or NULL when no
+ * match is found.
+ */
+struct typec_altmode *typec_match_altmode(struct typec_altmode **altmodes,
+ size_t n, u16 svid, u8 mode)
+{
+ int i;
+
+ for (i = 0; i < n; i++) {
+ if (!altmodes[i])
+ break;
+ if (altmodes[i]->svid == svid && altmodes[i]->mode == mode)
+ return altmodes[i];
+ }
+
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(typec_match_altmode);
+
+/* -------------------------------------------------------------------------- */
+
+static ssize_t
+description_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct typec_altmode *alt = to_typec_altmode(dev);
+
+ return sprintf(buf, "%s\n", alt->desc ? alt->desc : "");
+}
+static DEVICE_ATTR_RO(description);
+
+static struct attribute *typec_attrs[] = {
+ &dev_attr_description.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(typec);
+
+static int typec_match(struct device *dev, struct device_driver *driver)
+{
+ struct typec_altmode_driver *drv = to_altmode_driver(driver);
+ struct typec_altmode *altmode = to_typec_altmode(dev);
+ const struct typec_device_id *id;
+
+ for (id = drv->id_table; id->svid; id++)
+ if (id->svid == altmode->svid &&
+ (id->mode == TYPEC_ANY_MODE || id->mode == altmode->mode))
+ return 1;
+ return 0;
+}
+
+static int typec_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+ struct typec_altmode *altmode = to_typec_altmode(dev);
+
+ if (add_uevent_var(env, "SVID=%04X", altmode->svid))
+ return -ENOMEM;
+
+ if (add_uevent_var(env, "MODE=%u", altmode->mode))
+ return -ENOMEM;
+
+ return add_uevent_var(env, "MODALIAS=typec:id%04Xm%02X",
+ altmode->svid, altmode->mode);
+}
+
+static int typec_altmode_create_links(struct altmode *alt)
+{
+ struct device *port_dev = &alt->partner->adev.dev;
+ struct device *dev = &alt->adev.dev;
+ int err;
+
+ err = sysfs_create_link(&dev->kobj, &port_dev->kobj, "port");
+ if (err)
+ return err;
+
+ err = sysfs_create_link(&port_dev->kobj, &dev->kobj, "partner");
+ if (err)
+ sysfs_remove_link(&dev->kobj, "port");
+
+ return err;
+}
+
+static void typec_altmode_remove_links(struct altmode *alt)
+{
+ sysfs_remove_link(&alt->partner->adev.dev.kobj, "partner");
+ sysfs_remove_link(&alt->adev.dev.kobj, "port");
+}
+
+static int typec_probe(struct device *dev)
+{
+ struct typec_altmode_driver *drv = to_altmode_driver(dev->driver);
+ struct typec_altmode *adev = to_typec_altmode(dev);
+ struct altmode *altmode = to_altmode(adev);
+ int ret;
+
+ /* Fail if the port does not support the alternate mode */
+ if (!altmode->partner)
+ return -ENODEV;
+
+ ret = typec_altmode_create_links(altmode);
+ if (ret) {
+ dev_warn(dev, "failed to create symlinks\n");
+ return ret;
+ }
+
+ ret = drv->probe(adev);
+ if (ret)
+ typec_altmode_remove_links(altmode);
+
+ return ret;
+}
+
+static int typec_remove(struct device *dev)
+{
+ struct typec_altmode_driver *drv = to_altmode_driver(dev->driver);
+ struct typec_altmode *adev = to_typec_altmode(dev);
+ struct altmode *altmode = to_altmode(adev);
+
+ typec_altmode_remove_links(altmode);
+
+ if (drv->remove)
+ drv->remove(to_typec_altmode(dev));
+
+ if (adev->active) {
+ WARN_ON(typec_altmode_set_state(adev, TYPEC_STATE_SAFE));
+ typec_altmode_update_active(adev, false);
+ }
+
+ adev->desc = NULL;
+ adev->ops = NULL;
+
+ return 0;
+}
+
+struct bus_type typec_bus = {
+ .name = "typec",
+ .dev_groups = typec_groups,
+ .match = typec_match,
+ .uevent = typec_uevent,
+ .probe = typec_probe,
+ .remove = typec_remove,
+};
diff --git a/drivers/usb/typec/bus.h b/drivers/usb/typec/bus.h
new file mode 100644
index 000000000000..62aaf8b56bde
--- /dev/null
+++ b/drivers/usb/typec/bus.h
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef __USB_TYPEC_ALTMODE_H__
+#define __USB_TYPEC_ALTMODE_H__
+
+#include <linux/usb/typec_altmode.h>
+#include <linux/usb/typec_mux.h>
+
+struct bus_type;
+
+struct altmode {
+ unsigned int id;
+ struct typec_altmode adev;
+ struct typec_mux *mux;
+
+ enum typec_port_data roles;
+
+ struct attribute *attrs[5];
+ char group_name[6];
+ struct attribute_group group;
+ const struct attribute_group *groups[2];
+
+ struct altmode *partner;
+ struct altmode *plug[2];
+
+ struct blocking_notifier_head nh;
+};
+
+#define to_altmode(d) container_of(d, struct altmode, adev)
+
+extern struct bus_type typec_bus;
+extern const struct device_type typec_altmode_dev_type;
+extern const struct device_type typec_port_dev_type;
+
+#define is_typec_altmode(_dev_) (_dev_->type == &typec_altmode_dev_type)
+#define is_typec_port(_dev_) (_dev_->type == &typec_port_dev_type)
+
+#endif /* __USB_TYPEC_ALTMODE_H__ */
diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c
index dd8c48b629d6..abbd33939109 100644
--- a/drivers/usb/typec/class.c
+++ b/drivers/usb/typec/class.c
@@ -10,28 +10,13 @@
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/slab.h>
-#include <linux/usb/typec.h>
-#include <linux/usb/typec_mux.h>
-struct typec_altmode {
- struct device dev;
- u16 svid;
- u8 mode;
-
- u32 vdo;
- char *desc;
- enum typec_port_type roles;
- unsigned int active:1;
-
- struct attribute *attrs[5];
- char group_name[6];
- struct attribute_group group;
- const struct attribute_group *groups[2];
-};
+#include "bus.h"
struct typec_plug {
struct device dev;
enum typec_plug_index index;
+ struct ida mode_ids;
};
struct typec_cable {
@@ -46,11 +31,13 @@ struct typec_partner {
unsigned int usb_pd:1;
struct usb_pd_identity *identity;
enum typec_accessory accessory;
+ struct ida mode_ids;
};
struct typec_port {
unsigned int id;
struct device dev;
+ struct ida mode_ids;
int prefer_role;
enum typec_data_role data_role;
@@ -71,17 +58,14 @@ struct typec_port {
#define to_typec_plug(_dev_) container_of(_dev_, struct typec_plug, dev)
#define to_typec_cable(_dev_) container_of(_dev_, struct typec_cable, dev)
#define to_typec_partner(_dev_) container_of(_dev_, struct typec_partner, dev)
-#define to_altmode(_dev_) container_of(_dev_, struct typec_altmode, dev)
static const struct device_type typec_partner_dev_type;
static const struct device_type typec_cable_dev_type;
static const struct device_type typec_plug_dev_type;
-static const struct device_type typec_port_dev_type;
#define is_typec_partner(_dev_) (_dev_->type == &typec_partner_dev_type)
#define is_typec_cable(_dev_) (_dev_->type == &typec_cable_dev_type)
#define is_typec_plug(_dev_) (_dev_->type == &typec_plug_dev_type)
-#define is_typec_port(_dev_) (_dev_->type == &typec_port_dev_type)
static DEFINE_IDA(typec_index_ida);
static struct class *typec_class;
@@ -163,25 +147,148 @@ static void typec_report_identity(struct device *dev)
/* ------------------------------------------------------------------------- */
/* Alternate Modes */
+static int altmode_match(struct device *dev, void *data)
+{
+ struct typec_altmode *adev = to_typec_altmode(dev);
+ struct typec_device_id *id = data;
+
+ if (!is_typec_altmode(dev))
+ return 0;
+
+ return ((adev->svid == id->svid) && (adev->mode == id->mode));
+}
+
+static void typec_altmode_set_partner(struct altmode *altmode)
+{
+ struct typec_altmode *adev = &altmode->adev;
+ struct typec_device_id id = { adev->svid, adev->mode, };
+ struct typec_port *port = typec_altmode2port(adev);
+ struct altmode *partner;
+ struct device *dev;
+
+ dev = device_find_child(&port->dev, &id, altmode_match);
+ if (!dev)
+ return;
+
+ /* Bind the port alt mode to the partner/plug alt mode. */
+ partner = to_altmode(to_typec_altmode(dev));
+ altmode->partner = partner;
+
+ /* Bind the partner/plug alt mode to the port alt mode. */
+ if (is_typec_plug(adev->dev.parent)) {
+ struct typec_plug *plug = to_typec_plug(adev->dev.parent);
+
+ partner->plug[plug->index] = altmode;
+ } else {
+ partner->partner = altmode;
+ }
+}
+
+static void typec_altmode_put_partner(struct altmode *altmode)
+{
+ struct altmode *partner = altmode->partner;
+ struct typec_altmode *adev;
+
+ if (!partner)
+ return;
+
+ adev = &partner->adev;
+
+ if (is_typec_plug(adev->dev.parent)) {
+ struct typec_plug *plug = to_typec_plug(adev->dev.parent);
+
+ partner->plug[plug->index] = NULL;
+ } else {
+ partner->partner = NULL;
+ }
+ put_device(&adev->dev);
+}
+
+static int __typec_port_match(struct device *dev, const void *name)
+{
+ return !strcmp((const char *)name, dev_name(dev));
+}
+
+static void *typec_port_match(struct device_connection *con, int ep, void *data)
+{
+ return class_find_device(typec_class, NULL, con->endpoint[ep],
+ __typec_port_match);
+}
+
+struct typec_altmode *
+typec_altmode_register_notifier(struct device *dev, u16 svid, u8 mode,
+ struct notifier_block *nb)
+{
+ struct typec_device_id id = { svid, mode, };
+ struct device *altmode_dev;
+ struct device *port_dev;
+ struct altmode *altmode;
+ int ret;
+
+ /* Find the port linked to the caller */
+ port_dev = device_connection_find_match(dev, NULL, NULL,
+ typec_port_match);
+ if (IS_ERR_OR_NULL(port_dev))
+ return port_dev ? ERR_CAST(port_dev) : ERR_PTR(-ENODEV);
+
+ /* Find the altmode with matching svid */
+ altmode_dev = device_find_child(port_dev, &id, altmode_match);
+
+ put_device(port_dev);
+
+ if (!altmode_dev)
+ return ERR_PTR(-ENODEV);
+
+ altmode = to_altmode(to_typec_altmode(altmode_dev));
+
+ /* Register notifier */
+ ret = blocking_notifier_chain_register(&altmode->nh, nb);
+ if (ret) {
+ put_device(altmode_dev);
+ return ERR_PTR(ret);
+ }
+
+ return &altmode->adev;
+}
+EXPORT_SYMBOL_GPL(typec_altmode_register_notifier);
+
+void typec_altmode_unregister_notifier(struct typec_altmode *adev,
+ struct notifier_block *nb)
+{
+ struct altmode *altmode = to_altmode(adev);
+
+ blocking_notifier_chain_unregister(&altmode->nh, nb);
+ put_device(&adev->dev);
+}
+EXPORT_SYMBOL_GPL(typec_altmode_unregister_notifier);
+
/**
* typec_altmode_update_active - Report Enter/Exit mode
- * @alt: Handle to the alternate mode
+ * @adev: Handle to the alternate mode
* @active: True when the mode has been entered
*
* If a partner or cable plug executes Enter/Exit Mode command successfully, the
* drivers use this routine to report the updated state of the mode.
*/
-void typec_altmode_update_active(struct typec_altmode *alt, bool active)
+void typec_altmode_update_active(struct typec_altmode *adev, bool active)
{
char dir[6];
- if (alt->active == active)
+ if (adev->active == active)
return;
- alt->active = active;
- snprintf(dir, sizeof(dir), "mode%d", alt->mode);
- sysfs_notify(&alt->dev.kobj, dir, "active");
- kobject_uevent(&alt->dev.kobj, KOBJ_CHANGE);
+ if (!is_typec_port(adev->dev.parent)) {
+ if (!active)
+ module_put(adev->dev.driver->owner);
+ else
+ WARN_ON(!try_module_get(adev->dev.driver->owner));
+ }
+
+ adev->active = active;
+ snprintf(dir, sizeof(dir), "mode%d", adev->mode);
+ sysfs_notify(&adev->dev.kobj, dir, "active");
+ sysfs_notify(&adev->dev.kobj, NULL, "active");
+ kobject_uevent(&adev->dev.kobj, KOBJ_CHANGE);
}
EXPORT_SYMBOL_GPL(typec_altmode_update_active);
@@ -208,7 +315,7 @@ EXPORT_SYMBOL_GPL(typec_altmode2port);
static ssize_t
vdo_show(struct device *dev, struct device_attribute *attr, char *buf)
{
- struct typec_altmode *alt = to_altmode(dev);
+ struct typec_altmode *alt = to_typec_altmode(dev);
return sprintf(buf, "0x%08x\n", alt->vdo);
}
@@ -217,7 +324,7 @@ static DEVICE_ATTR_RO(vdo);
static ssize_t
description_show(struct device *dev, struct device_attribute *attr, char *buf)
{
- struct typec_altmode *alt = to_altmode(dev);
+ struct typec_altmode *alt = to_typec_altmode(dev);
return sprintf(buf, "%s\n", alt->desc ? alt->desc : "");
}
@@ -226,7 +333,7 @@ static DEVICE_ATTR_RO(description);
static ssize_t
active_show(struct device *dev, struct device_attribute *attr, char *buf)
{
- struct typec_altmode *alt = to_altmode(dev);
+ struct typec_altmode *alt = to_typec_altmode(dev);
return sprintf(buf, "%s\n", alt->active ? "yes" : "no");
}
@@ -234,21 +341,37 @@ active_show(struct device *dev, struct device_attribute *attr, char *buf)
static ssize_t active_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t size)
{
- struct typec_altmode *alt = to_altmode(dev);
- struct typec_port *port = typec_altmode2port(alt);
- bool activate;
+ struct typec_altmode *adev = to_typec_altmode(dev);
+ struct altmode *altmode = to_altmode(adev);
+ bool enter;
int ret;
- if (!port->cap->activate_mode)
- return -EOPNOTSUPP;
-
- ret = kstrtobool(buf, &activate);
+ ret = kstrtobool(buf, &enter);
if (ret)
return ret;
- ret = port->cap->activate_mode(port->cap, alt->mode, activate);
- if (ret)
- return ret;
+ if (adev->active == enter)
+ return size;
+
+ if (is_typec_port(adev->dev.parent)) {
+ typec_altmode_update_active(adev, enter);
+
+ /* Make sure that the partner exits the mode before disabling */
+ if (altmode->partner && !enter && altmode->partner->adev.active)
+ typec_altmode_exit(&altmode->partner->adev);
+ } else if (altmode->partner) {
+ if (enter && !altmode->partner->adev.active) {
+ dev_warn(dev, "port has the mode disabled\n");
+ return -EPERM;
+ }
+ }
+
+ /* Note: If there is no driver, the mode will not be entered */
+ if (adev->ops && adev->ops->activate) {
+ ret = adev->ops->activate(adev, enter);
+ if (ret)
+ return ret;
+ }
return size;
}
@@ -258,7 +381,7 @@ static ssize_t
supported_roles_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
- struct typec_altmode *alt = to_altmode(dev);
+ struct altmode *alt = to_altmode(to_typec_altmode(dev));
ssize_t ret;
switch (alt->roles) {
@@ -277,29 +400,72 @@ supported_roles_show(struct device *dev, struct device_attribute *attr,
}
static DEVICE_ATTR_RO(supported_roles);
-static void typec_altmode_release(struct device *dev)
+static ssize_t
+mode_show(struct device *dev, struct device_attribute *attr, char *buf)
{
- struct typec_altmode *alt = to_altmode(dev);
+ struct typec_altmode *adev = to_typec_altmode(dev);
- kfree(alt);
+ return sprintf(buf, "%u\n", adev->mode);
}
+static DEVICE_ATTR_RO(mode);
-static ssize_t svid_show(struct device *dev, struct device_attribute *attr,
- char *buf)
+static ssize_t
+svid_show(struct device *dev, struct device_attribute *attr, char *buf)
{
- struct typec_altmode *alt = to_altmode(dev);
+ struct typec_altmode *adev = to_typec_altmode(dev);
- return sprintf(buf, "%04x\n", alt->svid);
+ return sprintf(buf, "%04x\n", adev->svid);
}
static DEVICE_ATTR_RO(svid);
static struct attribute *typec_altmode_attrs[] = {
+ &dev_attr_active.attr,
+ &dev_attr_mode.attr,
&dev_attr_svid.attr,
+ &dev_attr_vdo.attr,
NULL
};
ATTRIBUTE_GROUPS(typec_altmode);
-static const struct device_type typec_altmode_dev_type = {
+static int altmode_id_get(struct device *dev)
+{
+ struct ida *ids;
+
+ if (is_typec_partner(dev))
+ ids = &to_typec_partner(dev)->mode_ids;
+ else if (is_typec_plug(dev))
+ ids = &to_typec_plug(dev)->mode_ids;
+ else
+ ids = &to_typec_port(dev)->mode_ids;
+
+ return ida_simple_get(ids, 0, 0, GFP_KERNEL);
+}
+
+static void altmode_id_remove(struct device *dev, int id)
+{
+ struct ida *ids;
+
+ if (is_typec_partner(dev))
+ ids = &to_typec_partner(dev)->mode_ids;
+ else if (is_typec_plug(dev))
+ ids = &to_typec_plug(dev)->mode_ids;
+ else
+ ids = &to_typec_port(dev)->mode_ids;
+
+ ida_simple_remove(ids, id);
+}
+
+static void typec_altmode_release(struct device *dev)
+{
+ struct altmode *alt = to_altmode(to_typec_altmode(dev));
+
+ typec_altmode_put_partner(alt);
+
+ altmode_id_remove(alt->adev.dev.parent, alt->id);
+ kfree(alt);
+}
+
+const struct device_type typec_altmode_dev_type = {
.name = "typec_alternate_mode",
.groups = typec_altmode_groups,
.release = typec_altmode_release,
@@ -309,58 +475,74 @@ static struct typec_altmode *
typec_register_altmode(struct device *parent,
const struct typec_altmode_desc *desc)
{
- struct typec_altmode *alt;
+ unsigned int id = altmode_id_get(parent);
+ bool is_port = is_typec_port(parent);
+ struct altmode *alt;
int ret;
alt = kzalloc(sizeof(*alt), GFP_KERNEL);
if (!alt)
return ERR_PTR(-ENOMEM);
- alt->svid = desc->svid;
- alt->mode = desc->mode;
- alt->vdo = desc->vdo;
+ alt->adev.svid = desc->svid;
+ alt->adev.mode = desc->mode;
+ alt->adev.vdo = desc->vdo;
alt->roles = desc->roles;
+ alt->id = id;
alt->attrs[0] = &dev_attr_vdo.attr;
alt->attrs[1] = &dev_attr_description.attr;
alt->attrs[2] = &dev_attr_active.attr;
- if (is_typec_port(parent))
+ if (is_port) {
alt->attrs[3] = &dev_attr_supported_roles.attr;
+ alt->adev.active = true; /* Enabled by default */
+ }
sprintf(alt->group_name, "mode%d", desc->mode);
alt->group.name = alt->group_name;
alt->group.attrs = alt->attrs;
alt->groups[0] = &alt->group;
- alt->dev.parent = parent;
- alt->dev.groups = alt->groups;
- alt->dev.type = &typec_altmode_dev_type;
- dev_set_name(&alt->dev, "%s-%04x:%u", dev_name(parent),
- alt->svid, alt->mode);
+ alt->adev.dev.parent = parent;
+ alt->adev.dev.groups = alt->groups;
+ alt->adev.dev.type = &typec_altmode_dev_type;
+ dev_set_name(&alt->adev.dev, "%s.%u", dev_name(parent), id);
+
+ /* Link partners and plugs with the ports */
+ if (is_port)
+ BLOCKING_INIT_NOTIFIER_HEAD(&alt->nh);
+ else
+ typec_altmode_set_partner(alt);
+
+ /* The partners are bind to drivers */
+ if (is_typec_partner(parent))
+ alt->adev.dev.bus = &typec_bus;
- ret = device_register(&alt->dev);
+ ret = device_register(&alt->adev.dev);
if (ret) {
dev_err(parent, "failed to register alternate mode (%d)\n",
ret);
- put_device(&alt->dev);
+ put_device(&alt->adev.dev);
return ERR_PTR(ret);
}
- return alt;
+ return &alt->adev;
}
/**
* typec_unregister_altmode - Unregister Alternate Mode
- * @alt: The alternate mode to be unregistered
+ * @adev: The alternate mode to be unregistered
*
* Unregister device created with typec_partner_register_altmode(),
* typec_plug_register_altmode() or typec_port_register_altmode().
*/
-void typec_unregister_altmode(struct typec_altmode *alt)
+void typec_unregister_altmode(struct typec_altmode *adev)
{
- if (!IS_ERR_OR_NULL(alt))
- device_unregister(&alt->dev);
+ if (IS_ERR_OR_NULL(adev))
+ return;
+ typec_mux_put(to_altmode(adev)->mux);
+ device_unregister(&adev->dev);
}
EXPORT_SYMBOL_GPL(typec_unregister_altmode);
@@ -398,6 +580,7 @@ static void typec_partner_release(struct device *dev)
{
struct typec_partner *partner = to_typec_partner(dev);
+ ida_destroy(&partner->mode_ids);
kfree(partner);
}
@@ -463,6 +646,7 @@ struct typec_partner *typec_register_partner(struct typec_port *port,
if (!partner)
return ERR_PTR(-ENOMEM);
+ ida_init(&partner->mode_ids);
partner->usb_pd = desc->usb_pd;
partner->accessory = desc->accessory;
@@ -511,6 +695,7 @@ static void typec_plug_release(struct device *dev)
{
struct typec_plug *plug = to_typec_plug(dev);
+ ida_destroy(&plug->mode_ids);
kfree(plug);
}
@@ -563,6 +748,7 @@ struct typec_plug *typec_register_plug(struct typec_cable *cable,
sprintf(name, "plug%d", desc->index);
+ ida_init(&plug->mode_ids);
plug->index = desc->index;
plug->dev.class = typec_class;
plug->dev.parent = &cable->dev;
@@ -1077,12 +1263,13 @@ static void typec_release(struct device *dev)
struct typec_port *port = to_typec_port(dev);
ida_simple_remove(&typec_index_ida, port->id);
+ ida_destroy(&port->mode_ids);
typec_switch_put(port->sw);
typec_mux_put(port->mux);
kfree(port);
}
-static const struct device_type typec_port_dev_type = {
+const struct device_type typec_port_dev_type = {
.name = "typec_port",
.groups = typec_groups,
.uevent = typec_uevent,
@@ -1228,11 +1415,11 @@ enum typec_orientation typec_get_orientation(struct typec_port *port)
/**
* typec_set_mode - Set mode of operation for USB Type-C connector
- * @port: USB Type-C port for the connector
- * @mode: Operation mode for the connector
+ * @port: USB Type-C connector
+ * @mode: Accessory Mode, USB Operation or Safe State
*
- * Set mode @mode for @port. This function will configure the muxes needed to
- * enter @mode.
+ * Configure @port for Accessory Mode @mode. This function will configure the
+ * muxes needed for @mode.
*/
int typec_set_mode(struct typec_port *port, int mode)
{
@@ -1246,6 +1433,7 @@ EXPORT_SYMBOL_GPL(typec_set_mode);
* typec_port_register_altmode - Register USB Type-C Port Alternate Mode
* @port: USB Type-C Port that supports the alternate mode
* @desc: Description of the alternate mode
+ * @drvdata: Private pointer to driver specific info
*
* This routine is used to register an alternate mode that @port is capable of
* supporting.
@@ -1256,7 +1444,23 @@ struct typec_altmode *
typec_port_register_altmode(struct typec_port *port,
const struct typec_altmode_desc *desc)
{
- return typec_register_altmode(&port->dev, desc);
+ struct typec_altmode *adev;
+ struct typec_mux *mux;
+ char id[10];
+
+ sprintf(id, "id%04xm%02x", desc->svid, desc->mode);
+
+ mux = typec_mux_get(port->dev.parent, id);
+ if (IS_ERR(mux))
+ return ERR_CAST(mux);
+
+ adev = typec_register_altmode(&port->dev, desc);
+ if (IS_ERR(adev))
+ typec_mux_put(mux);
+ else
+ to_altmode(adev)->mux = mux;
+
+ return adev;
}
EXPORT_SYMBOL_GPL(typec_port_register_altmode);
@@ -1330,10 +1534,12 @@ struct typec_port *typec_register_port(struct device *parent,
break;
}
+ ida_init(&port->mode_ids);
+ mutex_init(&port->port_type_lock);
+
port->id = id;
port->cap = cap;
port->port_type = cap->type;
- mutex_init(&port->port_type_lock);
port->prefer_role = cap->prefer_role;
port->dev.class = typec_class;
@@ -1377,8 +1583,19 @@ EXPORT_SYMBOL_GPL(typec_unregister_port);
static int __init typec_init(void)
{
+ int ret;
+
+ ret = bus_register(&typec_bus);
+ if (ret)
+ return ret;
+
typec_class = class_create(THIS_MODULE, "typec");
- return PTR_ERR_OR_ZERO(typec_class);
+ if (IS_ERR(typec_class)) {
+ bus_unregister(&typec_bus);
+ return PTR_ERR(typec_class);
+ }
+
+ return 0;
}
subsys_initcall(typec_init);
@@ -1386,6 +1603,7 @@ static void __exit typec_exit(void)
{
class_destroy(typec_class);
ida_destroy(&typec_index_ida);
+ bus_unregister(&typec_bus);
}
module_exit(typec_exit);
diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h
index 96a71a648eed..1298a7daa57d 100644
--- a/include/linux/mod_devicetable.h
+++ b/include/linux/mod_devicetable.h
@@ -746,4 +746,19 @@ struct tb_service_id {
#define TBSVC_MATCH_PROTOCOL_VERSION 0x0004
#define TBSVC_MATCH_PROTOCOL_REVISION 0x0008
+/* USB Type-C Alternate Modes */
+
+#define TYPEC_ANY_MODE 0x7
+
+/**
+ * struct typec_device_id - USB Type-C alternate mode identifiers
+ * @svid: Standard or Vendor ID
+ * @mode: Mode index
+ */
+struct typec_device_id {
+ __u16 svid;
+ __u8 mode;
+ kernel_ulong_t driver_data;
+};
+
#endif /* LINUX_MOD_DEVICETABLE_H */
diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h
index 51c76591c506..dccf8270dada 100644
--- a/include/linux/usb/typec.h
+++ b/include/linux/usb/typec.h
@@ -5,21 +5,18 @@
#include <linux/types.h>
-/* XXX: Once we have a header for USB Power Delivery, this belongs there */
-#define ALTMODE_MAX_MODES 6
-
/* USB Type-C Specification releases */
#define USB_TYPEC_REV_1_0 0x100 /* 1.0 */
#define USB_TYPEC_REV_1_1 0x110 /* 1.1 */
#define USB_TYPEC_REV_1_2 0x120 /* 1.2 */
-struct typec_altmode;
struct typec_partner;
struct typec_cable;
struct typec_plug;
struct typec_port;
struct fwnode_handle;
+struct device;
enum typec_port_type {
TYPEC_PORT_SRC,
@@ -107,7 +104,7 @@ struct typec_altmode_desc {
u8 mode;
u32 vdo;
/* Only used with ports */
- enum typec_port_type roles;
+ enum typec_port_data roles;
};
struct typec_altmode
@@ -186,7 +183,6 @@ struct typec_partner_desc {
* @dr_set: Set Data Role
* @pr_set: Set Power Role
* @vconn_set: Set VCONN Role
- * @activate_mode: Enter/exit given Alternate Mode
* @port_type_set: Set port type
*
* Static capabilities of a single USB Type-C port.
@@ -212,12 +208,8 @@ struct typec_capability {
enum typec_role);
int (*vconn_set)(const struct typec_capability *,
enum typec_role);
-
- int (*activate_mode)(const struct typec_capability *,
- int mode, int activate);
int (*port_type_set)(const struct typec_capability *,
- enum typec_port_type);
-
+ enum typec_port_type);
};
/* Specific to try_role(). Indicates the user want's to clear the preference. */
diff --git a/include/linux/usb/typec_altmode.h b/include/linux/usb/typec_altmode.h
new file mode 100644
index 000000000000..6caa482ae118
--- /dev/null
+++ b/include/linux/usb/typec_altmode.h
@@ -0,0 +1,160 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef __USB_TYPEC_ALTMODE_H
+#define __USB_TYPEC_ALTMODE_H
+
+#include <linux/mod_devicetable.h>
+#include <linux/usb/typec.h>
+#include <linux/device.h>
+
+#define MODE_DISCOVERY_MAX 6
+
+struct typec_altmode_ops;
+
+/**
+ * struct typec_altmode - USB Type-C alternate mode device
+ * @dev: Driver model's view of this device
+ * @svid: Standard or Vendor ID (SVID) of the alternate mode
+ * @mode: Index of the Mode
+ * @vdo: VDO returned by Discover Modes USB PD command
+ * @active: Tells has the mode been entered or not
+ * @desc: Optional human readable description of the mode
+ * @ops: Operations vector from the driver
+ */
+struct typec_altmode {
+ struct device dev;
+ u16 svid;
+ int mode;
+ u32 vdo;
+ unsigned int active:1;
+
+ char *desc;
+ const struct typec_altmode_ops *ops;
+};
+
+#define to_typec_altmode(d) container_of(d, struct typec_altmode, dev)
+
+static inline void typec_altmode_set_drvdata(struct typec_altmode *altmode,
+ void *data)
+{
+ dev_set_drvdata(&altmode->dev, data);
+}
+
+static inline void *typec_altmode_get_drvdata(struct typec_altmode *altmode)
+{
+ return dev_get_drvdata(&altmode->dev);
+}
+
+/**
+ * struct typec_altmode_ops - Alternate mode specific operations vector
+ * @enter: Operations to be executed with Enter Mode Command
+ * @exit: Operations to be executed with Exit Mode Command
+ * @attention: Callback for Attention Command
+ * @vdm: Callback for SVID specific commands
+ * @notify: Communication channel for platform and the alternate mode
+ * @activate: User callback for Enter/Exit Mode
+ */
+struct typec_altmode_ops {
+ void (*enter)(struct typec_altmode *altmode);
+ void (*exit)(struct typec_altmode *altmode);
+ void (*attention)(struct typec_altmode *altmode, u32 vdo);
+ int (*vdm)(struct typec_altmode *altmode, const u32 hdr,
+ const u32 *vdo, int cnt);
+ int (*notify)(struct typec_altmode *altmode, unsigned long conf,
+ void *data);
+ int (*activate)(struct typec_altmode *altmode, int activate);
+};
+
+int typec_altmode_enter(struct typec_altmode *altmode);
+int typec_altmode_exit(struct typec_altmode *altmode);
+void typec_altmode_attention(struct typec_altmode *altmode, u32 vdo);
+int typec_altmode_vdm(struct typec_altmode *altmode,
+ const u32 header, const u32 *vdo, int count);
+int typec_altmode_notify(struct typec_altmode *altmode, unsigned long conf,
+ void *data);
+const struct typec_altmode *
+typec_altmode_get_partner(struct typec_altmode *altmode);
+
+/*
+ * These are the connector states (USB, Safe and Alt Mode) defined in USB Type-C
+ * Specification. SVID specific connector states are expected to follow and
+ * start from the value TYPEC_STATE_MODAL.
+ */
+enum {
+ TYPEC_STATE_SAFE, /* USB Safe State */
+ TYPEC_STATE_USB, /* USB Operation */
+ TYPEC_STATE_MODAL, /* Alternate Modes */
+};
+
+/*
+ * For the muxes there is no difference between Accessory Modes and Alternate
+ * Modes, so the Accessory Modes are supplied with specific modal state values
+ * here. Unlike with Alternate Modes, where the mux will be linked with the
+ * alternate mode device, the mux for Accessory Modes will be linked with the
+ * port device instead.
+ *
+ * Port drivers can use TYPEC_MODE_AUDIO and TYPEC_MODE_DEBUG as the mode
+ * value for typec_set_mode() when accessory modes are supported.
+ */
+enum {
+ TYPEC_MODE_AUDIO = TYPEC_STATE_MODAL, /* Audio Accessory */
+ TYPEC_MODE_DEBUG, /* Debug Accessory */
+};
+
+#define TYPEC_MODAL_STATE(_state_) ((_state_) + TYPEC_STATE_MODAL)
+
+struct typec_altmode *typec_altmode_get_plug(struct typec_altmode *altmode,
+ enum typec_plug_index index);
+void typec_altmode_put_plug(struct typec_altmode *plug);
+
+struct typec_altmode *typec_match_altmode(struct typec_altmode **altmodes,
+ size_t n, u16 svid, u8 mode);
+
+struct typec_altmode *
+typec_altmode_register_notifier(struct device *dev, u16 svid, u8 mode,
+ struct notifier_block *nb);
+
+void typec_altmode_unregister_notifier(struct typec_altmode *adev,
+ struct notifier_block *nb);
+
+/**
+ * typec_altmode_get_orientation - Get cable plug orientation
+ * altmode: Handle to the alternate mode
+ */
+static inline enum typec_orientation
+typec_altmode_get_orientation(struct typec_altmode *altmode)
+{
+ return typec_get_orientation(typec_altmode2port(altmode));
+}
+
+/**
+ * struct typec_altmode_driver - USB Type-C alternate mode device driver
+ * @id_table: Null terminated array of SVIDs
+ * @probe: Callback for device binding
+ * @remove: Callback for device unbinding
+ * @driver: Device driver model driver
+ *
+ * These drivers will be bind to the partner alternate mode devices. They will
+ * handle all SVID specific communication.
+ */
+struct typec_altmode_driver {
+ const struct typec_device_id *id_table;
+ int (*probe)(struct typec_altmode *altmode);
+ void (*remove)(struct typec_altmode *altmode);
+ struct device_driver driver;
+};
+
+#define to_altmode_driver(d) container_of(d, struct typec_altmode_driver, \
+ driver)
+
+#define typec_altmode_register_driver(drv) \
+ __typec_altmode_register_driver(drv, THIS_MODULE)
+int __typec_altmode_register_driver(struct typec_altmode_driver *drv,
+ struct module *module);
+void typec_altmode_unregister_driver(struct typec_altmode_driver *drv);
+
+#define module_typec_altmode_driver(__typec_altmode_driver) \
+ module_driver(__typec_altmode_driver, typec_altmode_register_driver, \
+ typec_altmode_unregister_driver)
+
+#endif /* __USB_TYPEC_ALTMODE_H */
diff --git a/scripts/mod/devicetable-offsets.c b/scripts/mod/devicetable-offsets.c
index 6667f7b491d6..293004499b4d 100644
--- a/scripts/mod/devicetable-offsets.c
+++ b/scripts/mod/devicetable-offsets.c
@@ -221,5 +221,9 @@ int main(void)
DEVID_FIELD(tb_service_id, protocol_version);
DEVID_FIELD(tb_service_id, protocol_revision);
+ DEVID(typec_device_id);
+ DEVID_FIELD(typec_device_id, svid);
+ DEVID_FIELD(typec_device_id, mode);
+
return 0;
}
diff --git a/scripts/mod/file2alias.c b/scripts/mod/file2alias.c
index 52fd54a8fe39..7be43697ff84 100644
--- a/scripts/mod/file2alias.c
+++ b/scripts/mod/file2alias.c
@@ -1352,6 +1352,19 @@ static int do_tbsvc_entry(const char *filename, void *symval, char *alias)
}
ADD_TO_DEVTABLE("tbsvc", tb_service_id, do_tbsvc_entry);
+/* Looks like: typec:idNmN */
+static int do_typec_entry(const char *filename, void *symval, char *alias)
+{
+ DEF_FIELD(symval, typec_device_id, svid);
+ DEF_FIELD(symval, typec_device_id, mode);
+
+ sprintf(alias, "typec:id%04X", svid);
+ ADD(alias, "m", mode != TYPEC_ANY_MODE, mode);
+
+ return 1;
+}
+ADD_TO_DEVTABLE("typec", typec_device_id, do_typec_entry);
+
/* Does namelen bytes of name exactly match the symbol? */
static bool sym_is(const char *name, unsigned namelen, const char *symbol)
{
--
2.17.1
Instead of the tcpm specific mux states, using the generic
USB type-c connector state values, and with DisplayPort
using connector states defined for the DisplayPort Alt Mode.
Signed-off-by: Heikki Krogerus <[email protected]>
---
drivers/usb/typec/mux/pi3usb30532.c | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)
diff --git a/drivers/usb/typec/mux/pi3usb30532.c b/drivers/usb/typec/mux/pi3usb30532.c
index b0e88db60ecf..bcea242852f2 100644
--- a/drivers/usb/typec/mux/pi3usb30532.c
+++ b/drivers/usb/typec/mux/pi3usb30532.c
@@ -9,7 +9,7 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
-#include <linux/usb/tcpm.h>
+#include <linux/usb/typec_dp.h>
#include <linux/usb/typec_mux.h>
#define PI3USB30532_CONF 0x00
@@ -83,21 +83,24 @@ static int pi3usb30532_mux_set(struct typec_mux *mux, int state)
new_conf = pi->conf;
switch (state) {
- case TYPEC_MUX_NONE:
+ case TYPEC_STATE_SAFE:
new_conf = PI3USB30532_CONF_OPEN;
break;
- case TYPEC_MUX_USB:
+ case TYPEC_STATE_USB:
new_conf = (new_conf & PI3USB30532_CONF_SWAP) |
- PI3USB30532_CONF_USB3;
+ PI3USB30532_CONF_USB3;
break;
- case TYPEC_MUX_DP:
+ case TYPEC_DP_STATE_C:
+ case TYPEC_DP_STATE_E:
new_conf = (new_conf & PI3USB30532_CONF_SWAP) |
PI3USB30532_CONF_4LANE_DP;
break;
- case TYPEC_MUX_DOCK:
+ case TYPEC_DP_STATE_D:
new_conf = (new_conf & PI3USB30532_CONF_SWAP) |
PI3USB30532_CONF_USB3_AND_2LANE_DP;
break;
+ default:
+ break;
}
ret = pi3usb30532_set_conf(pi, new_conf);
--
2.17.1
This adds more complete handling of VDMs and registration of
partner alternate modes, and introduces callbacks for
alternate mode operations.
Only DFP role is supported for now.
Signed-off-by: Heikki Krogerus <[email protected]>
---
drivers/usb/typec/tcpm.c | 161 +++++++++++++++++++++++++++++++--------
include/linux/usb/tcpm.h | 9 ---
2 files changed, 131 insertions(+), 39 deletions(-)
diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c
index 44ce3955cb59..e1b52b5e78b5 100644
--- a/drivers/usb/typec/tcpm.c
+++ b/drivers/usb/typec/tcpm.c
@@ -26,7 +26,7 @@
#include <linux/usb/pd_vdo.h>
#include <linux/usb/role.h>
#include <linux/usb/tcpm.h>
-#include <linux/usb/typec.h>
+#include <linux/usb/typec_altmode.h>
#include <linux/workqueue.h>
#define FOREACH_STATE(S) \
@@ -169,13 +169,14 @@ enum pd_msg_request {
/* Alternate mode support */
#define SVID_DISCOVERY_MAX 16
+#define ALTMODE_DISCOVERY_MAX (SVID_DISCOVERY_MAX * MODE_DISCOVERY_MAX)
struct pd_mode_data {
int svid_index; /* current SVID index */
int nsvids;
u16 svids[SVID_DISCOVERY_MAX];
int altmodes; /* number of alternate modes */
- struct typec_altmode_desc altmode_desc[SVID_DISCOVERY_MAX];
+ struct typec_altmode_desc altmode_desc[ALTMODE_DISCOVERY_MAX];
};
struct pd_pps_data {
@@ -310,8 +311,8 @@ struct tcpm_port {
/* Alternate mode data */
struct pd_mode_data mode_data;
- struct typec_altmode *partner_altmode[SVID_DISCOVERY_MAX * 6];
- struct typec_altmode *port_altmode[SVID_DISCOVERY_MAX * 6];
+ struct typec_altmode *partner_altmode[ALTMODE_DISCOVERY_MAX];
+ struct typec_altmode *port_altmode[ALTMODE_DISCOVERY_MAX];
/* Deadline in jiffies to exit src_try_wait state */
unsigned long max_wait;
@@ -640,14 +641,14 @@ void tcpm_pd_transmit_complete(struct tcpm_port *port,
}
EXPORT_SYMBOL_GPL(tcpm_pd_transmit_complete);
-static int tcpm_mux_set(struct tcpm_port *port, enum tcpc_mux_mode mode,
+static int tcpm_mux_set(struct tcpm_port *port, int state,
enum usb_role usb_role,
enum typec_orientation orientation)
{
int ret;
- tcpm_log(port, "Requesting mux mode %d, usb-role %d, orientation %d",
- mode, usb_role, orientation);
+ tcpm_log(port, "Requesting mux state %d, usb-role %d, orientation %d",
+ state, usb_role, orientation);
ret = typec_set_orientation(port->typec_port, orientation);
if (ret)
@@ -659,7 +660,7 @@ static int tcpm_mux_set(struct tcpm_port *port, enum tcpc_mux_mode mode,
return ret;
}
- return typec_set_mode(port->typec_port, mode);
+ return typec_set_mode(port->typec_port, state);
}
static int tcpm_set_polarity(struct tcpm_port *port,
@@ -786,7 +787,7 @@ static int tcpm_set_roles(struct tcpm_port *port, bool attached,
else
usb_role = USB_ROLE_DEVICE;
- ret = tcpm_mux_set(port, TYPEC_MUX_USB, usb_role, orientation);
+ ret = tcpm_mux_set(port, TYPEC_STATE_USB, usb_role, orientation);
if (ret < 0)
return ret;
@@ -1013,36 +1014,57 @@ static void svdm_consume_modes(struct tcpm_port *port, const __le32 *payload,
pmdata->altmodes, paltmode->svid,
paltmode->mode, paltmode->vdo);
- port->partner_altmode[pmdata->altmodes] =
- typec_partner_register_altmode(port->partner, paltmode);
- if (!port->partner_altmode[pmdata->altmodes]) {
- tcpm_log(port,
- "Failed to register modes for SVID 0x%04x",
- paltmode->svid);
- return;
- }
pmdata->altmodes++;
}
}
+static void tcpm_register_partner_altmodes(struct tcpm_port *port)
+{
+ struct pd_mode_data *modep = &port->mode_data;
+ struct typec_altmode *altmode;
+ int i;
+
+ for (i = 0; i < modep->altmodes; i++) {
+ altmode = typec_partner_register_altmode(port->partner,
+ &modep->altmode_desc[i]);
+ if (!altmode)
+ tcpm_log(port, "Failed to register partner SVID 0x%04x",
+ modep->altmode_desc[i].svid);
+ port->partner_altmode[i] = altmode;
+ }
+}
+
#define supports_modal(port) PD_IDH_MODAL_SUPP((port)->partner_ident.id_header)
static int tcpm_pd_svdm(struct tcpm_port *port, const __le32 *payload, int cnt,
u32 *response)
{
- u32 p0 = le32_to_cpu(payload[0]);
- int cmd_type = PD_VDO_CMDT(p0);
- int cmd = PD_VDO_CMD(p0);
+ struct typec_altmode *adev;
+ struct typec_altmode *pdev;
struct pd_mode_data *modep;
+ u32 p[PD_MAX_PAYLOAD];
int rlen = 0;
- u16 svid;
+ int cmd_type;
+ int cmd;
int i;
+ for (i = 0; i < cnt; i++)
+ p[i] = le32_to_cpu(payload[i]);
+
+ cmd_type = PD_VDO_CMDT(p[0]);
+ cmd = PD_VDO_CMD(p[0]);
+
tcpm_log(port, "Rx VDM cmd 0x%x type %d cmd %d len %d",
- p0, cmd_type, cmd, cnt);
+ p[0], cmd_type, cmd, cnt);
modep = &port->mode_data;
+ adev = typec_match_altmode(port->port_altmode, ALTMODE_DISCOVERY_MAX,
+ PD_VDO_VID(p[0]), PD_VDO_OPOS(p[0]));
+
+ pdev = typec_match_altmode(port->partner_altmode, ALTMODE_DISCOVERY_MAX,
+ PD_VDO_VID(p[0]), PD_VDO_OPOS(p[0]));
+
switch (cmd_type) {
case CMDT_INIT:
switch (cmd) {
@@ -1064,17 +1086,19 @@ static int tcpm_pd_svdm(struct tcpm_port *port, const __le32 *payload, int cnt,
case CMD_EXIT_MODE:
break;
case CMD_ATTENTION:
- break;
+ /* Attention command does not have response */
+ typec_altmode_attention(adev, p[1]);
+ return 0;
default:
break;
}
if (rlen >= 1) {
- response[0] = p0 | VDO_CMDT(CMDT_RSP_ACK);
+ response[0] = p[0] | VDO_CMDT(CMDT_RSP_ACK);
} else if (rlen == 0) {
- response[0] = p0 | VDO_CMDT(CMDT_RSP_NAK);
+ response[0] = p[0] | VDO_CMDT(CMDT_RSP_NAK);
rlen = 1;
} else {
- response[0] = p0 | VDO_CMDT(CMDT_RSP_BUSY);
+ response[0] = p[0] | VDO_CMDT(CMDT_RSP_BUSY);
rlen = 1;
}
break;
@@ -1107,14 +1131,39 @@ static int tcpm_pd_svdm(struct tcpm_port *port, const __le32 *payload, int cnt,
svdm_consume_modes(port, payload, cnt);
modep->svid_index++;
if (modep->svid_index < modep->nsvids) {
- svid = modep->svids[modep->svid_index];
+ u16 svid = modep->svids[modep->svid_index];
response[0] = VDO(svid, 1, CMD_DISCOVER_MODES);
rlen = 1;
} else {
- /* enter alternate mode if/when implemented */
+ tcpm_register_partner_altmodes(port);
}
break;
case CMD_ENTER_MODE:
+ typec_altmode_update_active(pdev, true);
+
+ if (typec_altmode_vdm(adev, p[0], &p[1], cnt)) {
+ response[0] = VDO(adev->svid, 1, CMD_EXIT_MODE);
+ response[0] |= VDO_OPOS(adev->mode);
+ return 1;
+ }
+ return 0;
+ case CMD_EXIT_MODE:
+ typec_altmode_update_active(pdev, false);
+
+ /* Back to USB Operation */
+ WARN_ON(typec_altmode_notify(adev, TYPEC_STATE_USB,
+ NULL));
+ break;
+ default:
+ break;
+ }
+ break;
+ case CMDT_RSP_NAK:
+ switch (cmd) {
+ case CMD_ENTER_MODE:
+ /* Back to USB Operation */
+ WARN_ON(typec_altmode_notify(adev, TYPEC_STATE_USB,
+ NULL));
break;
default:
break;
@@ -1124,6 +1173,9 @@ static int tcpm_pd_svdm(struct tcpm_port *port, const __le32 *payload, int cnt,
break;
}
+ /* Informing the alternate mode drivers about everything */
+ typec_altmode_vdm(adev, p[0], &p[1], cnt);
+
return rlen;
}
@@ -1407,6 +1459,53 @@ static int tcpm_validate_caps(struct tcpm_port *port, const u32 *pdo,
return 0;
}
+static void tcpm_altmode_enter(struct typec_altmode *altmode)
+{
+ struct tcpm_port *port = typec_altmode_get_drvdata(altmode);
+ u32 header;
+
+ mutex_lock(&port->lock);
+ header = VDO(altmode->svid, 1, CMD_ENTER_MODE);
+ header |= VDO_OPOS(altmode->mode);
+
+ tcpm_queue_vdm(port, header, NULL, 0);
+ mod_delayed_work(port->wq, &port->vdm_state_machine, 0);
+ mutex_unlock(&port->lock);
+}
+
+static void tcpm_altmode_exit(struct typec_altmode *altmode)
+{
+ struct tcpm_port *port = typec_altmode_get_drvdata(altmode);
+ u32 header;
+
+ mutex_lock(&port->lock);
+ header = VDO(altmode->svid, 1, CMD_EXIT_MODE);
+ header |= VDO_OPOS(altmode->mode);
+
+ tcpm_queue_vdm(port, header, NULL, 0);
+ mod_delayed_work(port->wq, &port->vdm_state_machine, 0);
+ mutex_unlock(&port->lock);
+}
+
+static int tcpm_altmode_vdm(struct typec_altmode *altmode,
+ u32 header, const u32 *data, int count)
+{
+ struct tcpm_port *port = typec_altmode_get_drvdata(altmode);
+
+ mutex_lock(&port->lock);
+ tcpm_queue_vdm(port, header, data, count - 1);
+ mod_delayed_work(port->wq, &port->vdm_state_machine, 0);
+ mutex_unlock(&port->lock);
+
+ return 0;
+}
+
+static const struct typec_altmode_ops tcpm_altmode_ops = {
+ .enter = tcpm_altmode_enter,
+ .exit = tcpm_altmode_exit,
+ .vdm = tcpm_altmode_vdm,
+};
+
/*
* PD (data, control) command handling functions
*/
@@ -2538,7 +2637,7 @@ static int tcpm_src_attach(struct tcpm_port *port)
out_disable_pd:
port->tcpc->set_pd_rx(port->tcpc, false);
out_disable_mux:
- tcpm_mux_set(port, TYPEC_MUX_NONE, USB_ROLE_NONE,
+ tcpm_mux_set(port, TYPEC_STATE_SAFE, USB_ROLE_NONE,
TYPEC_ORIENTATION_NONE);
return ret;
}
@@ -2584,7 +2683,7 @@ static void tcpm_reset_port(struct tcpm_port *port)
tcpm_init_vconn(port);
tcpm_set_current_limit(port, 0, 0);
tcpm_set_polarity(port, TYPEC_POLARITY_CC1);
- tcpm_mux_set(port, TYPEC_MUX_NONE, USB_ROLE_NONE,
+ tcpm_mux_set(port, TYPEC_STATE_SAFE, USB_ROLE_NONE,
TYPEC_ORIENTATION_NONE);
tcpm_set_attached_state(port, false);
port->try_src_count = 0;
@@ -4616,6 +4715,8 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
dev_name(dev), paltmode->svid);
break;
}
+ typec_altmode_set_drvdata(alt, port);
+ alt->ops = &tcpm_altmode_ops;
port->port_altmode[i] = alt;
i++;
paltmode++;
diff --git a/include/linux/usb/tcpm.h b/include/linux/usb/tcpm.h
index b231b9314240..8c3df3e11801 100644
--- a/include/linux/usb/tcpm.h
+++ b/include/linux/usb/tcpm.h
@@ -98,15 +98,6 @@ struct tcpc_config {
#define TCPC_MUX_DP_ENABLED BIT(1) /* DP enabled */
#define TCPC_MUX_POLARITY_INVERTED BIT(2) /* Polarity inverted */
-/* Mux modes, decoded to attributes */
-enum tcpc_mux_mode {
- TYPEC_MUX_NONE = 0, /* Open switch */
- TYPEC_MUX_USB = TCPC_MUX_USB_ENABLED, /* USB only */
- TYPEC_MUX_DP = TCPC_MUX_DP_ENABLED, /* DP only */
- TYPEC_MUX_DOCK = TCPC_MUX_USB_ENABLED | /* Both USB and DP */
- TCPC_MUX_DP_ENABLED,
-};
-
/**
* struct tcpc_dev - Port configuration and callback functions
* @config: Pointer to port configuration
--
2.17.1
Before a device was created for every discovered SVID, but
this will create a device for every discovered mode of every
SVID. The idea is to make it easier to create mode specific
drivers once a bus for the alternate mode is added.
Signed-off-by: Heikki Krogerus <[email protected]>
---
drivers/usb/typec/class.c | 172 ++++++++++++--------------------------
drivers/usb/typec/tcpm.c | 45 +++++-----
include/linux/usb/typec.h | 37 ++------
3 files changed, 83 insertions(+), 171 deletions(-)
diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c
index 49468f5329d6..dd8c48b629d6 100644
--- a/drivers/usb/typec/class.c
+++ b/drivers/usb/typec/class.c
@@ -13,31 +13,20 @@
#include <linux/usb/typec.h>
#include <linux/usb/typec_mux.h>
-struct typec_mode {
- int index;
+struct typec_altmode {
+ struct device dev;
+ u16 svid;
+ u8 mode;
+
u32 vdo;
char *desc;
enum typec_port_type roles;
-
- struct typec_altmode *alt_mode;
-
unsigned int active:1;
+ struct attribute *attrs[5];
char group_name[6];
struct attribute_group group;
- struct attribute *attrs[5];
- struct device_attribute vdo_attr;
- struct device_attribute desc_attr;
- struct device_attribute active_attr;
- struct device_attribute roles_attr;
-};
-
-struct typec_altmode {
- struct device dev;
- u16 svid;
- int n_modes;
- struct typec_mode modes[ALTMODE_MAX_MODES];
- const struct attribute_group *mode_groups[ALTMODE_MAX_MODES];
+ const struct attribute_group *groups[2];
};
struct typec_plug {
@@ -177,23 +166,20 @@ static void typec_report_identity(struct device *dev)
/**
* typec_altmode_update_active - Report Enter/Exit mode
* @alt: Handle to the alternate mode
- * @mode: Mode index
* @active: True when the mode has been entered
*
* If a partner or cable plug executes Enter/Exit Mode command successfully, the
* drivers use this routine to report the updated state of the mode.
*/
-void typec_altmode_update_active(struct typec_altmode *alt, int mode,
- bool active)
+void typec_altmode_update_active(struct typec_altmode *alt, bool active)
{
- struct typec_mode *m = &alt->modes[mode];
char dir[6];
- if (m->active == active)
+ if (alt->active == active)
return;
- m->active = active;
- snprintf(dir, sizeof(dir), "mode%d", mode);
+ alt->active = active;
+ snprintf(dir, sizeof(dir), "mode%d", alt->mode);
sysfs_notify(&alt->dev.kobj, dir, "active");
kobject_uevent(&alt->dev.kobj, KOBJ_CHANGE);
}
@@ -220,42 +206,36 @@ struct typec_port *typec_altmode2port(struct typec_altmode *alt)
EXPORT_SYMBOL_GPL(typec_altmode2port);
static ssize_t
-typec_altmode_vdo_show(struct device *dev, struct device_attribute *attr,
- char *buf)
+vdo_show(struct device *dev, struct device_attribute *attr, char *buf)
{
- struct typec_mode *mode = container_of(attr, struct typec_mode,
- vdo_attr);
+ struct typec_altmode *alt = to_altmode(dev);
- return sprintf(buf, "0x%08x\n", mode->vdo);
+ return sprintf(buf, "0x%08x\n", alt->vdo);
}
+static DEVICE_ATTR_RO(vdo);
static ssize_t
-typec_altmode_desc_show(struct device *dev, struct device_attribute *attr,
- char *buf)
+description_show(struct device *dev, struct device_attribute *attr, char *buf)
{
- struct typec_mode *mode = container_of(attr, struct typec_mode,
- desc_attr);
+ struct typec_altmode *alt = to_altmode(dev);
- return sprintf(buf, "%s\n", mode->desc ? mode->desc : "");
+ return sprintf(buf, "%s\n", alt->desc ? alt->desc : "");
}
+static DEVICE_ATTR_RO(description);
static ssize_t
-typec_altmode_active_show(struct device *dev, struct device_attribute *attr,
- char *buf)
+active_show(struct device *dev, struct device_attribute *attr, char *buf)
{
- struct typec_mode *mode = container_of(attr, struct typec_mode,
- active_attr);
+ struct typec_altmode *alt = to_altmode(dev);
- return sprintf(buf, "%s\n", mode->active ? "yes" : "no");
+ return sprintf(buf, "%s\n", alt->active ? "yes" : "no");
}
-static ssize_t
-typec_altmode_active_store(struct device *dev, struct device_attribute *attr,
- const char *buf, size_t size)
+static ssize_t active_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t size)
{
- struct typec_mode *mode = container_of(attr, struct typec_mode,
- active_attr);
- struct typec_port *port = typec_altmode2port(mode->alt_mode);
+ struct typec_altmode *alt = to_altmode(dev);
+ struct typec_port *port = typec_altmode2port(alt);
bool activate;
int ret;
@@ -266,22 +246,22 @@ typec_altmode_active_store(struct device *dev, struct device_attribute *attr,
if (ret)
return ret;
- ret = port->cap->activate_mode(port->cap, mode->index, activate);
+ ret = port->cap->activate_mode(port->cap, alt->mode, activate);
if (ret)
return ret;
return size;
}
+static DEVICE_ATTR_RW(active);
static ssize_t
-typec_altmode_roles_show(struct device *dev, struct device_attribute *attr,
- char *buf)
+supported_roles_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
{
- struct typec_mode *mode = container_of(attr, struct typec_mode,
- roles_attr);
+ struct typec_altmode *alt = to_altmode(dev);
ssize_t ret;
- switch (mode->roles) {
+ switch (alt->roles) {
case TYPEC_PORT_SRC:
ret = sprintf(buf, "source\n");
break;
@@ -295,61 +275,13 @@ typec_altmode_roles_show(struct device *dev, struct device_attribute *attr,
}
return ret;
}
+static DEVICE_ATTR_RO(supported_roles);
-static void typec_init_modes(struct typec_altmode *alt,
- const struct typec_mode_desc *desc, bool is_port)
+static void typec_altmode_release(struct device *dev)
{
- int i;
-
- for (i = 0; i < alt->n_modes; i++, desc++) {
- struct typec_mode *mode = &alt->modes[i];
-
- /* Not considering the human readable description critical */
- mode->desc = kstrdup(desc->desc, GFP_KERNEL);
- if (desc->desc && !mode->desc)
- dev_err(&alt->dev, "failed to copy mode%d desc\n", i);
-
- mode->alt_mode = alt;
- mode->vdo = desc->vdo;
- mode->roles = desc->roles;
- mode->index = desc->index;
- sprintf(mode->group_name, "mode%d", desc->index);
-
- sysfs_attr_init(&mode->vdo_attr.attr);
- mode->vdo_attr.attr.name = "vdo";
- mode->vdo_attr.attr.mode = 0444;
- mode->vdo_attr.show = typec_altmode_vdo_show;
-
- sysfs_attr_init(&mode->desc_attr.attr);
- mode->desc_attr.attr.name = "description";
- mode->desc_attr.attr.mode = 0444;
- mode->desc_attr.show = typec_altmode_desc_show;
-
- sysfs_attr_init(&mode->active_attr.attr);
- mode->active_attr.attr.name = "active";
- mode->active_attr.attr.mode = 0644;
- mode->active_attr.show = typec_altmode_active_show;
- mode->active_attr.store = typec_altmode_active_store;
-
- mode->attrs[0] = &mode->vdo_attr.attr;
- mode->attrs[1] = &mode->desc_attr.attr;
- mode->attrs[2] = &mode->active_attr.attr;
-
- /* With ports, list the roles that the mode is supported with */
- if (is_port) {
- sysfs_attr_init(&mode->roles_attr.attr);
- mode->roles_attr.attr.name = "supported_roles";
- mode->roles_attr.attr.mode = 0444;
- mode->roles_attr.show = typec_altmode_roles_show;
-
- mode->attrs[3] = &mode->roles_attr.attr;
- }
-
- mode->group.attrs = mode->attrs;
- mode->group.name = mode->group_name;
+ struct typec_altmode *alt = to_altmode(dev);
- alt->mode_groups[i] = &mode->group;
- }
+ kfree(alt);
}
static ssize_t svid_show(struct device *dev, struct device_attribute *attr,
@@ -367,16 +299,6 @@ static struct attribute *typec_altmode_attrs[] = {
};
ATTRIBUTE_GROUPS(typec_altmode);
-static void typec_altmode_release(struct device *dev)
-{
- struct typec_altmode *alt = to_altmode(dev);
- int i;
-
- for (i = 0; i < alt->n_modes; i++)
- kfree(alt->modes[i].desc);
- kfree(alt);
-}
-
static const struct device_type typec_altmode_dev_type = {
.name = "typec_alternate_mode",
.groups = typec_altmode_groups,
@@ -395,13 +317,27 @@ typec_register_altmode(struct device *parent,
return ERR_PTR(-ENOMEM);
alt->svid = desc->svid;
- alt->n_modes = desc->n_modes;
- typec_init_modes(alt, desc->modes, is_typec_port(parent));
+ alt->mode = desc->mode;
+ alt->vdo = desc->vdo;
+ alt->roles = desc->roles;
+
+ alt->attrs[0] = &dev_attr_vdo.attr;
+ alt->attrs[1] = &dev_attr_description.attr;
+ alt->attrs[2] = &dev_attr_active.attr;
+
+ if (is_typec_port(parent))
+ alt->attrs[3] = &dev_attr_supported_roles.attr;
+
+ sprintf(alt->group_name, "mode%d", desc->mode);
+ alt->group.name = alt->group_name;
+ alt->group.attrs = alt->attrs;
+ alt->groups[0] = &alt->group;
alt->dev.parent = parent;
- alt->dev.groups = alt->mode_groups;
+ alt->dev.groups = alt->groups;
alt->dev.type = &typec_altmode_dev_type;
- dev_set_name(&alt->dev, "svid-%04x", alt->svid);
+ dev_set_name(&alt->dev, "%s-%04x:%u", dev_name(parent),
+ alt->svid, alt->mode);
ret = device_register(&alt->dev);
if (ret) {
diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c
index 8a201dd53d36..44ce3955cb59 100644
--- a/drivers/usb/typec/tcpm.c
+++ b/drivers/usb/typec/tcpm.c
@@ -310,8 +310,8 @@ struct tcpm_port {
/* Alternate mode data */
struct pd_mode_data mode_data;
- struct typec_altmode *partner_altmode[SVID_DISCOVERY_MAX];
- struct typec_altmode *port_altmode[SVID_DISCOVERY_MAX];
+ struct typec_altmode *partner_altmode[SVID_DISCOVERY_MAX * 6];
+ struct typec_altmode *port_altmode[SVID_DISCOVERY_MAX * 6];
/* Deadline in jiffies to exit src_try_wait state */
unsigned long max_wait;
@@ -994,7 +994,6 @@ static void svdm_consume_modes(struct tcpm_port *port, const __le32 *payload,
{
struct pd_mode_data *pmdata = &port->mode_data;
struct typec_altmode_desc *paltmode;
- struct typec_mode_desc *pmode;
int i;
if (pmdata->altmodes >= ARRAY_SIZE(port->partner_altmode)) {
@@ -1002,32 +1001,28 @@ static void svdm_consume_modes(struct tcpm_port *port, const __le32 *payload,
return;
}
- paltmode = &pmdata->altmode_desc[pmdata->altmodes];
- memset(paltmode, 0, sizeof(*paltmode));
+ for (i = 1; i < cnt; i++) {
+ paltmode = &pmdata->altmode_desc[pmdata->altmodes];
+ memset(paltmode, 0, sizeof(*paltmode));
- paltmode->svid = pmdata->svids[pmdata->svid_index];
+ paltmode->svid = pmdata->svids[pmdata->svid_index];
+ paltmode->mode = i;
+ paltmode->vdo = le32_to_cpu(payload[i]);
- tcpm_log(port, " Alternate mode %d: SVID 0x%04x",
- pmdata->altmodes, paltmode->svid);
+ tcpm_log(port, " Alternate mode %d: SVID 0x%04x, VDO %d: 0x%08x",
+ pmdata->altmodes, paltmode->svid,
+ paltmode->mode, paltmode->vdo);
- for (i = 1; i < cnt && paltmode->n_modes < ALTMODE_MAX_MODES; i++) {
- pmode = &paltmode->modes[paltmode->n_modes];
- memset(pmode, 0, sizeof(*pmode));
- pmode->vdo = le32_to_cpu(payload[i]);
- pmode->index = i - 1;
- paltmode->n_modes++;
- tcpm_log(port, " VDO %d: 0x%08x",
- pmode->index, pmode->vdo);
- }
- port->partner_altmode[pmdata->altmodes] =
- typec_partner_register_altmode(port->partner, paltmode);
- if (!port->partner_altmode[pmdata->altmodes]) {
- tcpm_log(port,
- "Failed to register alternate modes for SVID 0x%04x",
- paltmode->svid);
- return;
+ port->partner_altmode[pmdata->altmodes] =
+ typec_partner_register_altmode(port->partner, paltmode);
+ if (!port->partner_altmode[pmdata->altmodes]) {
+ tcpm_log(port,
+ "Failed to register modes for SVID 0x%04x",
+ paltmode->svid);
+ return;
+ }
+ pmdata->altmodes++;
}
- pmdata->altmodes++;
}
#define supports_modal(port) PD_IDH_MODAL_SUPP((port)->partner_ident.id_header)
diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h
index a5f6433e218b..51c76591c506 100644
--- a/include/linux/usb/typec.h
+++ b/include/linux/usb/typec.h
@@ -93,41 +93,23 @@ int typec_partner_set_identity(struct typec_partner *partner);
int typec_cable_set_identity(struct typec_cable *cable);
/*
- * struct typec_mode_desc - Individual Mode of an Alternate Mode
- * @index: Index of the Mode within the SVID
+ * struct typec_altmode_desc - USB Type-C Alternate Mode Descriptor
+ * @svid: Standard or Vendor ID
+ * @mode: Index of the Mode
* @vdo: VDO returned by Discover Modes USB PD command
- * @desc: Optional human readable description of the mode
* @roles: Only for ports. DRP if the mode is available in both roles
*
- * Description of a mode of an Alternate Mode which a connector, cable plug or
- * partner supports. Every mode will have it's own sysfs group. The details are
- * the VDO returned by discover modes command, description for the mode and
- * active flag telling has the mode being entered or not.
+ * Description of an Alternate Mode which a connector, cable plug or partner
+ * supports.
*/
-struct typec_mode_desc {
- int index;
+struct typec_altmode_desc {
+ u16 svid;
+ u8 mode;
u32 vdo;
- char *desc;
/* Only used with ports */
enum typec_port_type roles;
};
-/*
- * struct typec_altmode_desc - USB Type-C Alternate Mode Descriptor
- * @svid: Standard or Vendor ID
- * @n_modes: Number of modes
- * @modes: Array of modes supported by the Alternate Mode
- *
- * Representation of an Alternate Mode that has SVID assigned by USB-IF. The
- * array of modes will list the modes of a particular SVID that are supported by
- * a connector, partner of a cable plug.
- */
-struct typec_altmode_desc {
- u16 svid;
- int n_modes;
- struct typec_mode_desc modes[ALTMODE_MAX_MODES];
-};
-
struct typec_altmode
*typec_partner_register_altmode(struct typec_partner *partner,
const struct typec_altmode_desc *desc);
@@ -141,8 +123,7 @@ void typec_unregister_altmode(struct typec_altmode *altmode);
struct typec_port *typec_altmode2port(struct typec_altmode *alt);
-void typec_altmode_update_active(struct typec_altmode *alt, int mode,
- bool active);
+void typec_altmode_update_active(struct typec_altmode *alt, bool active);
enum typec_plug_index {
TYPEC_PLUG_SOP_P,
--
2.17.1
This adds function typec_get_orientation() that can be used
for checking the current cable plug orientation.
Signed-off-by: Heikki Krogerus <[email protected]>
---
drivers/usb/typec/class.c | 11 +++++++++++
include/linux/usb/typec.h | 1 +
2 files changed, 12 insertions(+)
diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c
index 53df10df2f9d..02db9bcbac0c 100644
--- a/drivers/usb/typec/class.c
+++ b/drivers/usb/typec/class.c
@@ -1279,6 +1279,17 @@ int typec_set_orientation(struct typec_port *port,
}
EXPORT_SYMBOL_GPL(typec_set_orientation);
+/**
+ * typec_get_orientation - Get USB Type-C cable plug orientation
+ * @port: USB Type-C Port
+ *
+ * Get current cable plug orientation for @port.
+ */
+enum typec_orientation typec_get_orientation(struct typec_port *port)
+{
+ return port->orientation;
+}
+
/**
* typec_set_mode - Set mode of operation for USB Type-C connector
* @port: USB Type-C port for the connector
diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h
index 672b39bb0adc..a5f6433e218b 100644
--- a/include/linux/usb/typec.h
+++ b/include/linux/usb/typec.h
@@ -265,6 +265,7 @@ void typec_set_pwr_opmode(struct typec_port *port, enum typec_pwr_opmode mode);
int typec_set_orientation(struct typec_port *port,
enum typec_orientation orientation);
+enum typec_orientation typec_get_orientation(struct typec_port *port);
int typec_set_mode(struct typec_port *port, int mode);
#endif /* __LINUX_USB_TYPEC_H */
--
2.17.1
Some of the macros use le16_to_cpu().
Signed-off-by: Heikki Krogerus <[email protected]>
---
include/linux/usb/pd.h | 1 +
1 file changed, 1 insertion(+)
diff --git a/include/linux/usb/pd.h b/include/linux/usb/pd.h
index 09b570feb297..f2162e0fe531 100644
--- a/include/linux/usb/pd.h
+++ b/include/linux/usb/pd.h
@@ -15,6 +15,7 @@
#ifndef __LINUX_USB_PD_H
#define __LINUX_USB_PD_H
+#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/usb/typec.h>
--
2.17.1
In order for the muxes to be usable with alternate modes,
the alternate mode devices will need also to be able to get
a handle to the muxes on top of the port devices. To make
that possible, the muxes need to be possible to request with
an identifier.
This will change the API so that the mux identifier is given
as a function parameter to typec_mux_get(), and the hard-coded
"typec-mux" is replaced with that value.
Signed-off-by: Heikki Krogerus <[email protected]>
---
drivers/usb/typec/class.c | 2 +-
drivers/usb/typec/mux.c | 6 +++---
include/linux/usb/typec_mux.h | 2 +-
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c
index 02db9bcbac0c..49468f5329d6 100644
--- a/drivers/usb/typec/class.c
+++ b/drivers/usb/typec/class.c
@@ -1356,7 +1356,7 @@ struct typec_port *typec_register_port(struct device *parent,
goto err_switch;
}
- port->mux = typec_mux_get(cap->fwnode ? &port->dev : parent);
+ port->mux = typec_mux_get(parent, "typec-mux");
if (IS_ERR(port->mux)) {
ret = PTR_ERR(port->mux);
goto err_mux;
diff --git a/drivers/usb/typec/mux.c b/drivers/usb/typec/mux.c
index 9d8330e9c431..ddaac63ecf12 100644
--- a/drivers/usb/typec/mux.c
+++ b/drivers/usb/typec/mux.c
@@ -123,19 +123,19 @@ static void *typec_mux_match(struct device_connection *con, int ep, void *data)
/**
* typec_mux_get - Find USB Type-C Multiplexer
* @dev: The caller device
+ * @name: Mux identifier
*
* Finds a mux linked to the caller. This function is primarily meant for the
* Type-C drivers. Returns a reference to the mux on success, NULL if no
* matching connection was found, or ERR_PTR(-EPROBE_DEFER) when a connection
* was found but the mux has not been enumerated yet.
*/
-struct typec_mux *typec_mux_get(struct device *dev)
+struct typec_mux *typec_mux_get(struct device *dev, const char *name)
{
struct typec_mux *mux;
mutex_lock(&mux_lock);
- mux = device_connection_find_match(dev, "typec-mux", NULL,
- typec_mux_match);
+ mux = device_connection_find_match(dev, name, NULL, typec_mux_match);
if (!IS_ERR_OR_NULL(mux))
get_device(mux->dev);
mutex_unlock(&mux_lock);
diff --git a/include/linux/usb/typec_mux.h b/include/linux/usb/typec_mux.h
index 12c1b057834b..79293f630ee1 100644
--- a/include/linux/usb/typec_mux.h
+++ b/include/linux/usb/typec_mux.h
@@ -47,7 +47,7 @@ void typec_switch_put(struct typec_switch *sw);
int typec_switch_register(struct typec_switch *sw);
void typec_switch_unregister(struct typec_switch *sw);
-struct typec_mux *typec_mux_get(struct device *dev);
+struct typec_mux *typec_mux_get(struct device *dev, const char *name);
void typec_mux_put(struct typec_mux *mux);
int typec_mux_register(struct typec_mux *mux);
void typec_mux_unregister(struct typec_mux *mux);
--
2.17.1
On Fri, Jun 08, 2018 at 02:29:39PM +0300, Heikki Krogerus wrote:
> diff --git a/drivers/usb/typec/altmodes/displayport.c b/drivers/usb/typec/altmodes/displayport.c
> new file mode 100644
> index 000000000000..a5054d86a4d9
> --- /dev/null
> +++ b/drivers/usb/typec/altmodes/displayport.c
> @@ -0,0 +1,543 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/**
> + * USB Typec-C DisplayPort Alternate Mode driver
> + *
> + * Copyright (C) 2018 Intel Corporation
> + * Author: Heikki Krogerus <[email protected]>
> + *
> + * DisplayPort is trademark of VESA (http://www.vesa.org)
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/mutex.h>
> +#include <linux/module.h>
> +#include <linux/usb/pd_vdo.h>
> +#include <linux/usb/typec_dp.h>
> +
> +#define DP_HEADER(cmd) (VDO(USB_TYPEC_DP_SID, 1, cmd) | \
> + VDO_OPOS(USB_TYPEC_DP_MODE))
> +
> +/* DisplayPort alt mode specific commands */
> +#define DP_CMD_STATUS_UPDATE VDO_CMD_VENDOR(0)
> +#define DP_CMD_CONFIGURE VDO_CMD_VENDOR(1)
> +
> +enum {
> + DP_CONF_USB,
> + DP_CONF_DFP_D,
> + DP_CONF_UFP_D,
> + DP_CONF_DUAL_D,
> +};
> +
> +/* DisplayPort Capabilities VDO bits */
> +#define DP_CAP_CAPABILITY(_cap_) ((_cap_) & 3)
> +#define DP_CAP_UFP_D 1
> +#define DP_CAP_DFP_D 2
> +#define DP_CAP_DFP_D_AND_UFP_D 3
> +#define DP_CAP_DP_SIGNALING BIT(2) /* Always set */
> +#define DP_CAP_GEN2 BIT(3) /* Reserved after v1.0b */
> +#define DP_CAP_RECEPTACLE BIT(6)
> +#define DP_CAP_USB BIT(7)
> +#define DP_CAP_DFP_D_PIN_ASSIGN(_cap_) (((_cap_) & GENMASK(15, 8)) >> 8)
> +#define DP_CAP_UFP_D_PIN_ASSIGN(_cap_) (((_cap_) & GENMASK(23, 16)) >> 16)
> +
> +enum {
> + DP_PIN_ASSIGN_A, /* Not supported after v1.0b */
> + DP_PIN_ASSIGN_B, /* Not supported after v1.0b */
> + DP_PIN_ASSIGN_C,
> + DP_PIN_ASSIGN_D,
> + DP_PIN_ASSIGN_E,
> + DP_PIN_ASSIGN_F, /* Not supported after v1.0b */
> +};
> +
> +/* Helper for setting/getting the pin assignement value to the configuration */
> +#define DP_CONF_SET_PIN_ASSIGN(_a_) ((_a_) << 8)
> +#define DP_CONF_GET_PIN_ASSIGN(_conf_) (((_conf_) & GENMASK(15, 8)) >> 8)
> +
> +/* Pin assignments that use USB3.1 Gen2 signaling to carry DP protocol */
> +#define DP_PIN_ASSIGN_GEN2_BR_MASK (BIT(DP_PIN_ASSIGN_A) | \
> + BIT(DP_PIN_ASSIGN_B))
> +
> +/* Pin assignments that use DP v1.3 signaling to carry DP protocol */
> +#define DP_PIN_ASSIGN_DP_BR_MASK (BIT(DP_PIN_ASSIGN_C) | \
> + BIT(DP_PIN_ASSIGN_D) | \
> + BIT(DP_PIN_ASSIGN_E) | \
> + BIT(DP_PIN_ASSIGN_F))
> +
> +/* DP only pin assignments */
> +#define DP_PIN_ASSIGN_DP_ONLY_MASK (BIT(DP_PIN_ASSIGN_A) | \
> + BIT(DP_PIN_ASSIGN_C) | \
> + BIT(DP_PIN_ASSIGN_E))
> +
> +/* Pin assignments where one channel is for USB */
> +#define DP_PIN_ASSIGN_MULTI_FUNC_MASK (BIT(DP_PIN_ASSIGN_B) | \
> + BIT(DP_PIN_ASSIGN_D) | \
> + BIT(DP_PIN_ASSIGN_F))
> +
> +enum dp_state {
> + DP_STATE_NONE,
> + DP_STATE_ENTER,
> + DP_STATE_UPDATE,
> + DP_STATE_CONFIGURE,
> + DP_STATE_EXIT,
> +};
> +
> +struct dp_altmode {
> + struct typec_displayport_data data;
> +
> + enum dp_state state;
> +
> + struct mutex lock;
> + struct work_struct work;
> + struct typec_altmode *alt;
> + const struct typec_altmode *port;
> +};
> +
> +static int dp_altmode_configure(struct dp_altmode *dp, u8 con)
> +{
> + u32 conf = DP_CONF_SIGNALING_DP; /* Only DP signaling supported */
> + u8 pin_assign = 0;
> +
> + switch (con) {
> + case DP_STATUS_CON_DISABLED:
> + dp->data.conf = 0;
> + return 0;
> + case DP_STATUS_CON_DFP_D:
> + conf |= DP_CONF_UFP_U_AS_DFP_D;
> + pin_assign = DP_CAP_UFP_D_PIN_ASSIGN(dp->alt->vdo) &
> + DP_CAP_DFP_D_PIN_ASSIGN(dp->port->vdo);
> + break;
> + case DP_STATUS_CON_UFP_D:
> + case DP_STATUS_CON_BOTH: /* NOTE: First acting as DP source */
> + conf |= DP_CONF_UFP_U_AS_UFP_D;
> + pin_assign = DP_CAP_DFP_D_PIN_ASSIGN(dp->alt->vdo) &
> + DP_CAP_UFP_D_PIN_ASSIGN(dp->port->vdo);
> + break;
> + default:
> + break;
> + }
> +
> + /* Determining the initial pin assignment. */
> + if (!DP_CONF_GET_PIN_ASSIGN(dp->data.conf)) {
> + /* Is USB together with DP preferred */
> + if (dp->data.status & DP_STATUS_PREFER_MULTI_FUNC &&
> + pin_assign & DP_PIN_ASSIGN_MULTI_FUNC_MASK)
> + pin_assign &= DP_PIN_ASSIGN_MULTI_FUNC_MASK;
> + else
> + pin_assign &= DP_PIN_ASSIGN_DP_ONLY_MASK;
> +
> + if (!pin_assign)
> + return -EINVAL;
> +
> + conf |= DP_CONF_SET_PIN_ASSIGN(pin_assign);
> + }
> +
> + dp->data.conf |= conf;
> +
> + return 0;
> +}
> +
> +static int dp_altmode_status_update(struct dp_altmode *dp)
> +{
> + bool configured = !!DP_CONF_GET_PIN_ASSIGN(dp->data.conf);
> + u8 con = DP_STATUS_CONNECTION(dp->data.status);
> + int ret = 0;
> +
> + if (configured && (dp->data.status & DP_STATUS_SWITCH_TO_USB)) {
> + dp->data.conf = 0;
> + dp->state = DP_STATE_CONFIGURE;
> + } else if (dp->data.status & DP_STATUS_EXIT_DP_MODE) {
> + dp->state = DP_STATE_EXIT;
> + } else if (!(con & DP_CONF_CURRENTLY(dp->data.conf))) {
> + ret = dp_altmode_configure(dp, con);
> + if (!ret)
> + dp->state = DP_STATE_CONFIGURE;
> + }
> +
> + return ret;
> +}
> +
> +static int dp_altmode_configured(struct dp_altmode *dp)
> +{
> + u8 state;
> + int ret;
> +
> + sysfs_notify(&dp->alt->dev.kobj, "displayport", "configuration");
> +
> + if (!dp->data.conf)
> + return typec_altmode_notify(dp->alt, TYPEC_STATE_USB,
> + &dp->data);
> +
> + state = get_count_order(DP_CONF_GET_PIN_ASSIGN(dp->data.conf));
> + ret = typec_altmode_notify(dp->alt, TYPEC_MODAL_STATE(state),
> + &dp->data);
> + if (ret)
> + return ret;
> +
> + sysfs_notify(&dp->alt->dev.kobj, "displayport", "pin_assignment");
> +
> + return 0;
> +}
> +
> +static void dp_altmode_work(struct work_struct *work)
> +{
> + struct dp_altmode *dp = container_of(work, struct dp_altmode, work);
> + u32 header = 0;
> + u32 vdo;
> + int ret;
> +
> + mutex_lock(&dp->lock);
> +
> + switch (dp->state) {
> + case DP_STATE_ENTER:
> + ret = typec_altmode_enter(dp->alt);
> + if (ret)
> + dev_err(&dp->alt->dev, "failed to enter mode\n");
> + break;
> + case DP_STATE_UPDATE:
> + header = DP_HEADER(DP_CMD_STATUS_UPDATE);
> + vdo = 1;
> + break;
> + case DP_STATE_CONFIGURE:
> + ret = typec_altmode_notify(dp->alt, TYPEC_STATE_SAFE,
> + &dp->data);
> + if (ret) {
> + dev_err(&dp->alt->dev,
> + "unable to put to connector to safe mode\n");
> + break;
> + }
> + header = DP_HEADER(DP_CMD_CONFIGURE);
> + vdo = dp->data.conf;
> + break;
> + case DP_STATE_EXIT:
> + if (typec_altmode_exit(dp->alt))
> + dev_err(&dp->alt->dev, "Exit Mode Failed!\n");
> + break;
> + default:
> + break;
> + }
> +
> + if (header) {
> + if (typec_altmode_vdm(dp->alt, header, &vdo, 2))
> + dev_err(&dp->alt->dev, "unable to send VDM\n");
> + }
> +
> + mutex_unlock(&dp->lock);
> +}
FYI. Currently this little state machine is horribly racy. It needs to
be fixed.
> +static void dp_altmode_attention(struct typec_altmode *alt, const u32 vdo)
> +{
> + struct dp_altmode *dp = typec_altmode_get_drvdata(alt);
> + u8 state;
> +
> + mutex_lock(&dp->lock);
> +
> + dp->state = DP_STATE_NONE;
> + dp->data.status = vdo;
> + dp_altmode_status_update(dp);
> +
> + if (dp->state == DP_STATE_NONE) {
> + state = get_count_order(DP_CONF_GET_PIN_ASSIGN(dp->data.conf));
> + if (typec_altmode_notify(dp->alt, TYPEC_MODAL_STATE(state),
> + &dp->data))
> + dev_err(&alt->dev, "%s: notification failed\n",
> + __func__);
> + } else {
> + schedule_work(&dp->work);
> + }
> +
> + mutex_unlock(&dp->lock);
> +}
> +
> +static int dp_altmode_vdm(struct typec_altmode *alt,
> + const u32 hdr, const u32 *vdo, int count)
> +{
> + struct dp_altmode *dp = typec_altmode_get_drvdata(alt);
> + int cmd_type = PD_VDO_CMDT(hdr);
> + int cmd = PD_VDO_CMD(hdr);
> + int ret = 0;
> +
> + mutex_lock(&dp->lock);
> +
> + dp->state = DP_STATE_NONE;
> +
> + switch (cmd_type) {
> + case CMDT_RSP_ACK:
> + switch (cmd) {
> + case CMD_ENTER_MODE:
> + dp->state = DP_STATE_UPDATE;
> + break;
> + case CMD_EXIT_MODE:
> + dp->data.status = 0;
> + dp->data.conf = 0;
> + break;
> + case DP_CMD_STATUS_UPDATE:
> + dp->data.status = *vdo;
> + ret = dp_altmode_status_update(dp);
> + break;
> + case DP_CMD_CONFIGURE:
> + ret = dp_altmode_configured(dp);
> + break;
> + default:
> + break;
> + }
> + break;
> + case CMDT_RSP_NAK:
> + switch (cmd) {
> + case DP_CMD_CONFIGURE:
> + dp->data.conf = 0;
> + ret = dp_altmode_configured(dp);
> + break;
> + default:
> + break;
> + }
> + break;
> + default:
> + break;
> + }
> +
> + if (dp->state != DP_STATE_NONE)
> + schedule_work(&dp->work);
> +
> + mutex_unlock(&dp->lock);
> + return ret;
> +}
> +
> +static int dp_altmode_activate(struct typec_altmode *alt, int activate)
> +{
> + return activate ? typec_altmode_enter(alt) : typec_altmode_exit(alt);
> +}
> +
> +static const struct typec_altmode_ops dp_altmode_ops = {
> + .attention = dp_altmode_attention,
> + .vdm = dp_altmode_vdm,
> + .activate = dp_altmode_activate,
> +};
> +
> +static const char * const configurations[] = {
> + [DP_CONF_USB] = "USB",
> + [DP_CONF_DFP_D] = "source",
> + [DP_CONF_UFP_D] = "sink",
> +};
> +
> +static ssize_t
> +configuration_store(struct device *dev, struct device_attribute *attr,
> + const char *buf, size_t size)
> +{
> + struct dp_altmode *dp = dev_get_drvdata(dev);
> + int conf;
> + int ret;
> +
> + conf = sysfs_match_string(configurations, buf);
> + if (conf < 0)
> + return conf;
> +
> + mutex_lock(&dp->lock);
> +
> + ret = dp_altmode_configure(dp, conf);
> + if (!ret && dp->alt->active) {
> + dp->state = DP_STATE_CONFIGURE;
> + schedule_work(&dp->work);
> + }
> +
> + mutex_unlock(&dp->lock);
> +
> + return ret ? ret : size;
> +}
> +
> +static ssize_t configuration_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct dp_altmode *dp = dev_get_drvdata(dev);
> + int len;
> + u8 cap;
> + u8 cur;
> + int i;
> +
> + mutex_lock(&dp->lock);
> +
> + cap = DP_CAP_CAPABILITY(dp->alt->vdo);
> + cur = DP_CONF_CURRENTLY(dp->data.conf);
> +
> + len = sprintf(buf, "%s ", cur ? "USB" : "[USB]");
> +
> + for (i = 1; i < ARRAY_SIZE(configurations); i++) {
> + if (i == cur)
> + len += sprintf(buf + len, "[%s] ", configurations[i]);
> + else if ((i == DP_CONF_DFP_D && cap & DP_CAP_DFP_D) ||
> + (i == DP_CONF_UFP_D && cap & DP_CAP_UFP_D))
> + len += sprintf(buf + len, "%s ", configurations[i]);
> + }
> +
> + mutex_unlock(&dp->lock);
> +
> + buf[len - 1] = '\n';
> + return len;
> +}
> +static DEVICE_ATTR_RW(configuration);
> +
> +static const char * const pin_assignments[] = {
> + [DP_PIN_ASSIGN_A] = "A",
> + [DP_PIN_ASSIGN_B] = "B",
> + [DP_PIN_ASSIGN_C] = "C",
> + [DP_PIN_ASSIGN_D] = "D",
> + [DP_PIN_ASSIGN_E] = "E",
> + [DP_PIN_ASSIGN_F] = "F",
> +};
> +
> +static ssize_t
> +pin_assignment_store(struct device *dev, struct device_attribute *attr,
> + const char *buf, size_t size)
> +{
> + struct dp_altmode *dp = dev_get_drvdata(dev);
> + u8 assignments;
> + u32 conf;
> + int ret;
> +
> + ret = sysfs_match_string(pin_assignments, buf);
> + if (ret < 0)
> + return ret;
> +
> + conf = DP_CONF_SET_PIN_ASSIGN(BIT(ret));
> + ret = 0;
> +
> + mutex_lock(&dp->lock);
> +
> + if (conf & dp->data.conf)
> + goto out_unlock;
> +
> + if (DP_CONF_CURRENTLY(dp->data.conf) == DP_CONF_DFP_D)
> + assignments = DP_CAP_UFP_D_PIN_ASSIGN(dp->alt->vdo);
> + else
> + assignments = DP_CAP_DFP_D_PIN_ASSIGN(dp->alt->vdo);
> +
> + if (!(DP_CONF_GET_PIN_ASSIGN(conf) & assignments)) {
> + ret = -EINVAL;
> + goto out_unlock;
> + }
> +
> + /* Only send Configure command if a configuration has been set */
> + if (dp->alt->active && DP_CONF_CURRENTLY(dp->data.conf)) {
> + dp->state = DP_STATE_CONFIGURE;
> + schedule_work(&dp->work);
> + }
> +
> + dp->data.conf &= ~DP_CONF_PIN_ASSIGNEMENT_MASK;
> + dp->data.conf |= conf;
> +
> +out_unlock:
> + mutex_unlock(&dp->lock);
> +
> + return ret ? ret : size;
> +}
> +
> +static ssize_t pin_assignment_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct dp_altmode *dp = dev_get_drvdata(dev);
> + u8 assignments;
> + int len = 0;
> + u8 cur;
> + int i;
> +
> + mutex_lock(&dp->lock);
> +
> + cur = get_count_order(DP_CONF_GET_PIN_ASSIGN(dp->data.conf));
> +
> + if (DP_CONF_CURRENTLY(dp->data.conf) == DP_CONF_DFP_D)
> + assignments = DP_CAP_UFP_D_PIN_ASSIGN(dp->alt->vdo);
> + else
> + assignments = DP_CAP_DFP_D_PIN_ASSIGN(dp->alt->vdo);
> +
> + for (i = 0; assignments; assignments >>= 1, i++) {
> + if (assignments & 1) {
> + if (i == cur)
> + len += sprintf(buf + len, "[%s] ",
> + pin_assignments[i]);
> + else
> + len += sprintf(buf + len, "%s ",
> + pin_assignments[i]);
> + }
> + }
> +
> + mutex_unlock(&dp->lock);
> +
> + buf[len - 1] = '\n';
> + return len;
> +}
> +static DEVICE_ATTR_RW(pin_assignment);
> +
> +static struct attribute *dp_altmode_attrs[] = {
> + &dev_attr_configuration.attr,
> + &dev_attr_pin_assignment.attr,
> + NULL
> +};
> +
> +static const struct attribute_group dp_altmode_group = {
> + .name = "displayport",
> + .attrs = dp_altmode_attrs,
> +};
> +
> +static int dp_altmode_probe(struct typec_altmode *alt)
> +{
> + const struct typec_altmode *port = typec_altmode_get_partner(alt);
> + struct dp_altmode *dp;
> + int ret;
> +
> + /* FIXME: Port can only be DFP_U. */
> +
> + /* Make sure we have compatiple pin configurations */
> + if (!(DP_CAP_DFP_D_PIN_ASSIGN(port->vdo) &
> + DP_CAP_UFP_D_PIN_ASSIGN(alt->vdo)) &&
> + !(DP_CAP_UFP_D_PIN_ASSIGN(port->vdo) &
> + DP_CAP_DFP_D_PIN_ASSIGN(alt->vdo)))
> + return -ENODEV;
> +
> + ret = sysfs_create_group(&alt->dev.kobj, &dp_altmode_group);
> + if (ret)
> + return ret;
> +
> + dp = devm_kzalloc(&alt->dev, sizeof(*dp), GFP_KERNEL);
> + if (!dp)
> + return -ENOMEM;
> +
> + INIT_WORK(&dp->work, dp_altmode_work);
> + mutex_init(&dp->lock);
> + dp->port = port;
> + dp->alt = alt;
> +
> + alt->desc = "DisplayPort";
> + alt->ops = &dp_altmode_ops;
> +
> + typec_altmode_set_drvdata(alt, dp);
> +
> + dp->state = DP_STATE_ENTER;
> + schedule_work(&dp->work);
> +
> + return 0;
> +}
> +
> +static void dp_altmode_remove(struct typec_altmode *alt)
> +{
> + sysfs_remove_group(&alt->dev.kobj, &dp_altmode_group);
> +}
> +
> +static const struct typec_device_id dp_typec_id[] = {
> + { USB_TYPEC_DP_SID, USB_TYPEC_DP_MODE },
> + { },
> +};
> +MODULE_DEVICE_TABLE(typec, dp_typec_id);
> +
> +static struct typec_altmode_driver dp_altmode_driver = {
> + .id_table = dp_typec_id,
> + .probe = dp_altmode_probe,
> + .remove = dp_altmode_remove,
> + .driver = {
> + .name = "typec_displayport",
> + .owner = THIS_MODULE,
> + },
> +};
> +module_typec_altmode_driver(dp_altmode_driver);
> +
> +MODULE_AUTHOR("Heikki Krogerus <[email protected]>");
> +MODULE_LICENSE("GPL v2");
> +MODULE_DESCRIPTION("DisplayPort Alternate Mode");
Br,
--
heikki
On 06/08/2018 02:29 PM, Heikki Krogerus wrote:
> Instead of the tcpm specific mux states, using the generic
> USB type-c connector state values, and with DisplayPort
> using connector states defined for the DisplayPort Alt Mode.
>
> Signed-off-by: Heikki Krogerus <[email protected]>
> ---
> drivers/usb/typec/mux/pi3usb30532.c | 15 +++++++++------
> 1 file changed, 9 insertions(+), 6 deletions(-)
>
> diff --git a/drivers/usb/typec/mux/pi3usb30532.c b/drivers/usb/typec/mux/pi3usb30532.c
> index b0e88db60ecf..bcea242852f2 100644
> --- a/drivers/usb/typec/mux/pi3usb30532.c
> +++ b/drivers/usb/typec/mux/pi3usb30532.c
[...]
> @@ -83,21 +83,24 @@ static int pi3usb30532_mux_set(struct typec_mux *mux, int state)
> new_conf = pi->conf;
>
> switch (state) {
> - case TYPEC_MUX_NONE:
> + case TYPEC_STATE_SAFE:
> new_conf = PI3USB30532_CONF_OPEN;
> break;
> - case TYPEC_MUX_USB:
> + case TYPEC_STATE_USB:
> new_conf = (new_conf & PI3USB30532_CONF_SWAP) |
> - PI3USB30532_CONF_USB3;
> + PI3USB30532_CONF_USB3;
Unrelated white space change, inconsistent with the others below...
> break;
> - case TYPEC_MUX_DP:
> + case TYPEC_DP_STATE_C:
> + case TYPEC_DP_STATE_E:
> new_conf = (new_conf & PI3USB30532_CONF_SWAP) |
> PI3USB30532_CONF_4LANE_DP;
> break;
> - case TYPEC_MUX_DOCK:
> + case TYPEC_DP_STATE_D:
> new_conf = (new_conf & PI3USB30532_CONF_SWAP) |
> PI3USB30532_CONF_USB3_AND_2LANE_DP;
> break;
> + default:
> + break;
> }
>
> ret = pi3usb30532_set_conf(pi, new_conf);
MBR, Sergei
On Fri, Jun 08, 2018 at 02:29:34PM +0300, Heikki Krogerus wrote:
> Some of the macros use le16_to_cpu().
What macros?
Is this causing build errors today? If so, why should it not be
included now?
thanks,
greg k-h
On Mon, Jun 11, 2018 at 09:08:59AM +0200, Greg Kroah-Hartman wrote:
> On Fri, Jun 08, 2018 at 02:29:34PM +0300, Heikki Krogerus wrote:
> > Some of the macros use le16_to_cpu().
>
> What macros?
>
> Is this causing build errors today? If so, why should it not be
> included now?
There are no build errors today.
I'll write a better commit message for this patch. I think I need to
improve the commit messages of some of the other patches in this
series as well.
Thanks,
--
heikki
Hi Heikki,
On 2018-06-08 13:29, Heikki Krogerus wrote:
> This adds function typec_get_orientation() that can be used
> for checking the current cable plug orientation.
>
> Signed-off-by: Heikki Krogerus <[email protected]>
> ---
> drivers/usb/typec/class.c | 11 +++++++++++
> include/linux/usb/typec.h | 1 +
> 2 files changed, 12 insertions(+)
>
> diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c
> index 53df10df2f9d..02db9bcbac0c 100644
> --- a/drivers/usb/typec/class.c
> +++ b/drivers/usb/typec/class.c
> @@ -1279,6 +1279,17 @@ int typec_set_orientation(struct typec_port *port,
> }
> EXPORT_SYMBOL_GPL(typec_set_orientation);
>
> +/**
> + * typec_get_orientation - Get USB Type-C cable plug orientation
> + * @port: USB Type-C Port
> + *
> + * Get current cable plug orientation for @port.
> + */
> +enum typec_orientation typec_get_orientation(struct typec_port *port)
> +{
> + return port->orientation;
> +}
+EXPORT_SYMBOL_GPL(typec_get_orientation);
Isn't this a candidate for an inline?
BR // Mats
> +
> /**
> * typec_set_mode - Set mode of operation for USB Type-C connector
> * @port: USB Type-C port for the connector
> diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h
> index 672b39bb0adc..a5f6433e218b 100644
> --- a/include/linux/usb/typec.h
> +++ b/include/linux/usb/typec.h
> @@ -265,6 +265,7 @@ void typec_set_pwr_opmode(struct typec_port *port, enum typec_pwr_opmode mode);
>
> int typec_set_orientation(struct typec_port *port,
> enum typec_orientation orientation);
> +enum typec_orientation typec_get_orientation(struct typec_port *port);
> int typec_set_mode(struct typec_port *port, int mode);
>
> #endif /* __LINUX_USB_TYPEC_H */
>
On Tue, Jun 12, 2018 at 08:56:31PM +0200, Mats Karrman wrote:
> Hi Heikki,
>
> On 2018-06-08 13:29, Heikki Krogerus wrote:
>
> >This adds function typec_get_orientation() that can be used
> >for checking the current cable plug orientation.
> >
> >Signed-off-by: Heikki Krogerus <[email protected]>
> >---
> > drivers/usb/typec/class.c | 11 +++++++++++
> > include/linux/usb/typec.h | 1 +
> > 2 files changed, 12 insertions(+)
> >
> >diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c
> >index 53df10df2f9d..02db9bcbac0c 100644
> >--- a/drivers/usb/typec/class.c
> >+++ b/drivers/usb/typec/class.c
> >@@ -1279,6 +1279,17 @@ int typec_set_orientation(struct typec_port *port,
> > }
> > EXPORT_SYMBOL_GPL(typec_set_orientation);
> >+/**
> >+ * typec_get_orientation - Get USB Type-C cable plug orientation
> >+ * @port: USB Type-C Port
> >+ *
> >+ * Get current cable plug orientation for @port.
> >+ */
> >+enum typec_orientation typec_get_orientation(struct typec_port *port)
> >+{
> >+ return port->orientation;
> >+}
>
> +EXPORT_SYMBOL_GPL(typec_get_orientation);
>
> Isn't this a candidate for an inline?
>
Currently struct typec_port is class private. To make this function inline,
struct typec_port would have to be moved from drivers/usb/typec/class.c to
include/linux/usb/typec.h. That doesn't sound like a good idea to me.
Guenter
> BR // Mats
>
> >+
> > /**
> > * typec_set_mode - Set mode of operation for USB Type-C connector
> > * @port: USB Type-C port for the connector
> >diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h
> >index 672b39bb0adc..a5f6433e218b 100644
> >--- a/include/linux/usb/typec.h
> >+++ b/include/linux/usb/typec.h
> >@@ -265,6 +265,7 @@ void typec_set_pwr_opmode(struct typec_port *port, enum typec_pwr_opmode mode);
> > int typec_set_orientation(struct typec_port *port,
> > enum typec_orientation orientation);
> >+enum typec_orientation typec_get_orientation(struct typec_port *port);
> > int typec_set_mode(struct typec_port *port, int mode);
> > #endif /* __LINUX_USB_TYPEC_H */
> >
On Tue, Jun 12, 2018 at 03:18:42PM -0700, Guenter Roeck wrote:
> On Tue, Jun 12, 2018 at 08:56:31PM +0200, Mats Karrman wrote:
> > Hi Heikki,
> >
> > On 2018-06-08 13:29, Heikki Krogerus wrote:
> >
> > >This adds function typec_get_orientation() that can be used
> > >for checking the current cable plug orientation.
> > >
> > >Signed-off-by: Heikki Krogerus <[email protected]>
> > >---
> > > drivers/usb/typec/class.c | 11 +++++++++++
> > > include/linux/usb/typec.h | 1 +
> > > 2 files changed, 12 insertions(+)
> > >
> > >diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c
> > >index 53df10df2f9d..02db9bcbac0c 100644
> > >--- a/drivers/usb/typec/class.c
> > >+++ b/drivers/usb/typec/class.c
> > >@@ -1279,6 +1279,17 @@ int typec_set_orientation(struct typec_port *port,
> > > }
> > > EXPORT_SYMBOL_GPL(typec_set_orientation);
> > >+/**
> > >+ * typec_get_orientation - Get USB Type-C cable plug orientation
> > >+ * @port: USB Type-C Port
> > >+ *
> > >+ * Get current cable plug orientation for @port.
> > >+ */
> > >+enum typec_orientation typec_get_orientation(struct typec_port *port)
> > >+{
> > >+ return port->orientation;
> > >+}
> >
> > +EXPORT_SYMBOL_GPL(typec_get_orientation);
> >
> > Isn't this a candidate for an inline?
> >
>
> Currently struct typec_port is class private. To make this function inline,
> struct typec_port would have to be moved from drivers/usb/typec/class.c to
> include/linux/usb/typec.h. That doesn't sound like a good idea to me.
Yes, let's keep the structure protected.
But thanks for the report, I'll fix it.
Br,
--
heikki