OSDN Git Service

usb: typec: USB Power Delivery helpers for ports and partners
authorHeikki Krogerus <heikki.krogerus@linux.intel.com>
Mon, 2 May 2022 13:20:57 +0000 (16:20 +0300)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sun, 12 Jun 2022 04:49:47 +0000 (06:49 +0200)
All the USB Type-C Connector Class devices are protected, so
the drivers can not directly access them. This will adds a
few helpers that can be used to link the ports and partners
to the correct USB Power Delivery objects.

For ports a new optional sysfs attribute file is also added
that can be used to select the USB Power Delivery
capabilities that the port will advertise to the partner.

Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Link: https://lore.kernel.org/r/20220502132058.86236-3-heikki.krogerus@linux.intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Documentation/ABI/testing/sysfs-class-typec
drivers/usb/typec/class.c
drivers/usb/typec/class.h
include/linux/usb/typec.h

index 75088ec..281b995 100644 (file)
@@ -141,6 +141,14 @@ Description:
                - "reverse": CC2 orientation
                - "unknown": Orientation cannot be determined.
 
+What:          /sys/class/typec/<port>/select_usb_power_delivery
+Date:          May 2022
+Contact:       Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+               Lists the USB Power Delivery Capabilities that the port can
+               advertise to the partner. The currently used capabilities are in
+               brackets. Selection happens by writing to the file.
+
 USB Type-C partner devices (eg. /sys/class/typec/port0-partner/)
 
 What:          /sys/class/typec/<port>-partner/accessory_mode
index ee0e520..bbc46b1 100644 (file)
@@ -15,6 +15,7 @@
 
 #include "bus.h"
 #include "class.h"
+#include "pd.h"
 
 static DEFINE_IDA(typec_index_ida);
 
@@ -721,6 +722,39 @@ void typec_partner_set_pd_revision(struct typec_partner *partner, u16 pd_revisio
 EXPORT_SYMBOL_GPL(typec_partner_set_pd_revision);
 
 /**
+ * typec_partner_set_usb_power_delivery - Declare USB Power Delivery Contract.
+ * @partner: The partner device.
+ * @pd: The USB PD instance.
+ *
+ * This routine can be used to declare USB Power Delivery Contract with @partner
+ * by linking @partner to @pd which contains the objects that were used during the
+ * negotiation of the contract.
+ *
+ * If @pd is NULL, the link is removed and the contract with @partner has ended.
+ */
+int typec_partner_set_usb_power_delivery(struct typec_partner *partner,
+                                        struct usb_power_delivery *pd)
+{
+       int ret;
+
+       if (IS_ERR_OR_NULL(partner) || partner->pd == pd)
+               return 0;
+
+       if (pd) {
+               ret = usb_power_delivery_link_device(pd, &partner->dev);
+               if (ret)
+                       return ret;
+       } else {
+               usb_power_delivery_unlink_device(partner->pd, &partner->dev);
+       }
+
+       partner->pd = pd;
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(typec_partner_set_usb_power_delivery);
+
+/**
  * typec_partner_set_num_altmodes - Set the number of available partner altmodes
  * @partner: The partner to be updated.
  * @num_altmodes: The number of altmodes we want to specify as available.
@@ -1170,6 +1204,104 @@ EXPORT_SYMBOL_GPL(typec_unregister_cable);
 /* ------------------------------------------------------------------------- */
 /* USB Type-C ports */
 
+/**
+ * typec_port_set_usb_power_delivery - Assign USB PD for port.
+ * @port: USB Type-C port.
+ * @pd: USB PD instance.
+ *
+ * This routine can be used to set the USB Power Delivery Capabilities for @port
+ * that it will advertise to the partner.
+ *
+ * If @pd is NULL, the assignment is removed.
+ */
+int typec_port_set_usb_power_delivery(struct typec_port *port, struct usb_power_delivery *pd)
+{
+       int ret;
+
+       if (IS_ERR_OR_NULL(port) || port->pd == pd)
+               return 0;
+
+       if (pd) {
+               ret = usb_power_delivery_link_device(pd, &port->dev);
+               if (ret)
+                       return ret;
+       } else {
+               usb_power_delivery_unlink_device(port->pd, &port->dev);
+       }
+
+       port->pd = pd;
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(typec_port_set_usb_power_delivery);
+
+static ssize_t select_usb_power_delivery_store(struct device *dev,
+                                              struct device_attribute *attr,
+                                              const char *buf, size_t size)
+{
+       struct typec_port *port = to_typec_port(dev);
+       struct usb_power_delivery *pd;
+
+       if (!port->ops || !port->ops->pd_set)
+               return -EOPNOTSUPP;
+
+       pd = usb_power_delivery_find(buf);
+       if (!pd)
+               return -EINVAL;
+
+       return port->ops->pd_set(port, pd);
+}
+
+static ssize_t select_usb_power_delivery_show(struct device *dev,
+                                             struct device_attribute *attr, char *buf)
+{
+       struct typec_port *port = to_typec_port(dev);
+       struct usb_power_delivery **pds;
+       struct usb_power_delivery *pd;
+       int ret = 0;
+
+       if (!port->ops || !port->ops->pd_get)
+               return -EOPNOTSUPP;
+
+       pds = port->ops->pd_get(port);
+       if (!pds)
+               return 0;
+
+       for (pd = pds[0]; pd; pd++) {
+               if (pd == port->pd)
+                       ret += sysfs_emit(buf + ret, "[%s] ", dev_name(&pd->dev));
+               else
+                       ret += sysfs_emit(buf + ret, "%s ", dev_name(&pd->dev));
+       }
+
+       buf[ret - 1] = '\n';
+
+       return ret;
+}
+static DEVICE_ATTR_RW(select_usb_power_delivery);
+
+static struct attribute *port_attrs[] = {
+       &dev_attr_select_usb_power_delivery.attr,
+       NULL
+};
+
+static umode_t port_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n)
+{
+       struct typec_port *port = to_typec_port(kobj_to_dev(kobj));
+
+       if (!port->pd || !port->ops || !port->ops->pd_get)
+               return 0;
+       if (!port->ops->pd_set)
+               return 0444;
+
+       return attr->mode;
+}
+
+static const struct attribute_group pd_group = {
+       .is_visible = port_attr_is_visible,
+       .attrs = port_attrs,
+};
+
 static const char * const typec_orientations[] = {
        [TYPEC_ORIENTATION_NONE]        = "unknown",
        [TYPEC_ORIENTATION_NORMAL]      = "normal",
@@ -1581,6 +1713,7 @@ static const struct attribute_group typec_group = {
 
 static const struct attribute_group *typec_groups[] = {
        &typec_group,
+       &pd_group,
        NULL
 };
 
@@ -2123,6 +2256,13 @@ struct typec_port *typec_register_port(struct device *parent,
                return ERR_PTR(ret);
        }
 
+       ret = typec_port_set_usb_power_delivery(port, cap->pd);
+       if (ret) {
+               dev_err(&port->dev, "failed to link pd\n");
+               device_unregister(&port->dev);
+               return ERR_PTR(ret);
+       }
+
        ret = typec_link_ports(port);
        if (ret)
                dev_warn(&port->dev, "failed to create symlinks (%d)\n", ret);
@@ -2141,6 +2281,7 @@ void typec_unregister_port(struct typec_port *port)
 {
        if (!IS_ERR_OR_NULL(port)) {
                typec_unlink_ports(port);
+               typec_port_set_usb_power_delivery(port, NULL);
                device_unregister(&port->dev);
        }
 }
@@ -2162,8 +2303,15 @@ static int __init typec_init(void)
        if (ret)
                goto err_unregister_mux_class;
 
+       ret = usb_power_delivery_init();
+       if (ret)
+               goto err_unregister_class;
+
        return 0;
 
+err_unregister_class:
+       class_unregister(&typec_class);
+
 err_unregister_mux_class:
        class_unregister(&typec_mux_class);
 
@@ -2176,6 +2324,7 @@ subsys_initcall(typec_init);
 
 static void __exit typec_exit(void)
 {
+       usb_power_delivery_exit();
        class_unregister(&typec_class);
        ida_destroy(&typec_index_ida);
        bus_unregister(&typec_bus);
index 0f1bd6d..b531f98 100644 (file)
@@ -33,6 +33,8 @@ struct typec_partner {
        int                             num_altmodes;
        u16                             pd_revision; /* 0300H = "3.0" */
        enum usb_pd_svdm_ver            svdm_version;
+
+       struct usb_power_delivery       *pd;
 };
 
 struct typec_port {
@@ -40,6 +42,8 @@ struct typec_port {
        struct device                   dev;
        struct ida                      mode_ids;
 
+       struct usb_power_delivery       *pd;
+
        int                             prefer_role;
        enum typec_data_role            data_role;
        enum typec_role                 pwr_role;
index 45e28d1..7751bed 100644 (file)
@@ -22,6 +22,8 @@ struct typec_altmode_ops;
 struct fwnode_handle;
 struct device;
 
+struct usb_power_delivery;
+
 enum typec_port_type {
        TYPEC_PORT_SRC,
        TYPEC_PORT_SNK,
@@ -223,6 +225,8 @@ struct typec_partner_desc {
  * @pr_set: Set Power Role
  * @vconn_set: Source VCONN
  * @port_type_set: Set port type
+ * @pd_get: Get available USB Power Delivery Capabilities.
+ * @pd_set: Set USB Power Delivery Capabilities.
  */
 struct typec_operations {
        int (*try_role)(struct typec_port *port, int role);
@@ -231,6 +235,8 @@ struct typec_operations {
        int (*vconn_set)(struct typec_port *port, enum typec_role role);
        int (*port_type_set)(struct typec_port *port,
                             enum typec_port_type type);
+       struct usb_power_delivery **(*pd_get)(struct typec_port *port);
+       int (*pd_set)(struct typec_port *port, struct usb_power_delivery *pd);
 };
 
 enum usb_pd_svdm_ver {
@@ -250,6 +256,7 @@ enum usb_pd_svdm_ver {
  * @accessory: Supported Accessory Modes
  * @fwnode: Optional fwnode of the port
  * @driver_data: Private pointer for driver specific info
+ * @pd: Optional USB Power Delivery Support
  * @ops: Port operations vector
  *
  * Static capabilities of a single USB Type-C port.
@@ -267,6 +274,8 @@ struct typec_capability {
        struct fwnode_handle    *fwnode;
        void                    *driver_data;
 
+       struct usb_power_delivery *pd;
+
        const struct typec_operations   *ops;
 };
 
@@ -318,4 +327,8 @@ void typec_partner_set_svdm_version(struct typec_partner *partner,
                                    enum usb_pd_svdm_ver svdm_version);
 int typec_get_negotiated_svdm_version(struct typec_port *port);
 
+int typec_port_set_usb_power_delivery(struct typec_port *port, struct usb_power_delivery *pd);
+int typec_partner_set_usb_power_delivery(struct typec_partner *partner,
+                                        struct usb_power_delivery *pd);
+
 #endif /* __LINUX_USB_TYPEC_H */