Bus for binding SVID specific drivers to the altnernate mode
devices.

Signed-off-by: Heikki Krogerus <heikki.kroge...@linux.intel.com>
---
 drivers/usb/typec/Makefile             |   2 +
 drivers/usb/typec/altmode.c            | 249 +++++++++++++++++++++++++++++++++
 drivers/usb/typec/altmode.h            |  46 ++++++
 drivers/usb/typec/{typec.c => class.c} | 128 ++++++++++++-----
 drivers/usb/typec/tcpm.c               |   2 +-
 include/linux/usb/typec.h              |   6 +-
 include/linux/usb/typec_altmode.h      |  65 +++++++++
 7 files changed, 461 insertions(+), 37 deletions(-)
 create mode 100644 drivers/usb/typec/altmode.c
 create mode 100644 drivers/usb/typec/altmode.h
 rename drivers/usb/typec/{typec.c => class.c} (93%)
 create mode 100644 include/linux/usb/typec_altmode.h

diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile
index b77688ce1f16..e157cc4526f0 100644
--- a/drivers/usb/typec/Makefile
+++ b/drivers/usb/typec/Makefile
@@ -1,4 +1,6 @@
 obj-$(CONFIG_TYPEC)            += typec.o
+typec-y                                := class.o
+typec-y                                += altmode.o
 obj-$(CONFIG_TYPEC_TCPM)       += tcpm.o
 obj-y                          += fusb302/
 obj-$(CONFIG_TYPEC_WCOVE)      += typec_wcove.o
diff --git a/drivers/usb/typec/altmode.c b/drivers/usb/typec/altmode.c
new file mode 100644
index 000000000000..81fe0af37da0
--- /dev/null
+++ b/drivers/usb/typec/altmode.c
@@ -0,0 +1,249 @@
+/**
+ * USB Type-C Alternate Mode bus
+ *
+ * Copyright (C) 2017 Intel Corporation
+ * Author: Heikki Krogerus <heikki.kroge...@linux.intel.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/usb/typec_altmode.h>
+
+#include "altmode.h"
+
+/* -------------------------------------------------------------------------- 
*/
+/* Common API */
+
+/**
+ * typec_altmode_notify - Communicate with the platform
+ * @altmode: Handle to the alternate mode
+ * @conf: Alternate mode specific configuration value
+ * @data: Alternate mode specific data to be passed to the partner
+ *
+ * The primary purpose for this function is to allow the alternate mode drivers
+ * to tell the platform which pin configuration has been negotiated with the
+ * partner, but communication to the other direction is also possible, so low
+ * level device drivers can also send notifications to the alternate mode
+ * drivers. The actual communication will be specific to every alternate mode.
+ */
+int typec_altmode_notify(struct typec_altmode *altmode,
+                        unsigned long conf, void *data)
+{
+       struct typec_altmode *partner;
+
+       if (!altmode)
+               return 0;
+
+       if (!altmode->partner)
+               return -ENODEV;
+
+       partner = altmode->partner;
+
+       /*
+        * This is where we will later pass the data to the remote-endpoints,
+        * but for now simply passing the data to the port.
+        *
+        * More information about the remote-endpoint concept:
+        *   Documentation/acpi/dsd/graph.txt
+        *   Documentation/devicetree/bindings/graph.txt
+        *
+        * Check drivers/base/property.c to see the API for the endpoint
+        * handling (the fwnode_graph* functions).
+        */
+
+       if (partner->ops && partner->ops->notify)
+               return partner->ops->notify(partner, conf, data);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(typec_altmode_notify);
+
+/**
+ * typec_altmode_send_vdm - Send Vendor Defined Messages to the partner
+ * @altmode: 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_send_vdm(struct typec_altmode *altmode,
+                          u32 header, u32 *vdo, int count)
+{
+       struct typec_altmode *partner;
+
+       if (!altmode)
+               return 0;
+
+       if (!altmode->partner)
+               return -ENODEV;
+
+       partner = altmode->partner;
+
+       if (partner->ops && partner->ops->vdm)
+               partner->ops->vdm(partner, header, vdo, count);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(typec_altmode_send_vdm);
+
+void typec_altmode_set_drvdata(struct typec_altmode *altmode, void *data)
+{
+       dev_set_drvdata(&altmode->dev, data);
+}
+EXPORT_SYMBOL_GPL(typec_altmode_set_drvdata);
+
+void *typec_altmode_get_drvdata(struct typec_altmode *altmode)
+{
+       return dev_get_drvdata(&altmode->dev);
+}
+EXPORT_SYMBOL_GPL(typec_altmode_get_drvdata);
+
+/* -------------------------------------------------------------------------- 
*/
+/* API for the alternate mode drivers */
+
+/**
+ * typec_altmode_register_ops - Register alternate mode specific operations
+ * @altmode: Handle to the alternate mode
+ * @ops: Alternate mode specific operations vector
+ *
+ * Used by the alternate mode drivers for registering their operation vectors
+ * with the alternate mode device.
+ */
+void typec_altmode_register_ops(struct typec_altmode *altmode,
+                               struct typec_altmode_ops *ops)
+{
+       altmode->ops = ops;
+}
+EXPORT_SYMBOL_GPL(typec_altmode_register_ops);
+
+/**
+ * typec_altmode_get_plug - Find cable plug alternate mode
+ * @altmode: 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 *altmode,
+                                            int index)
+{
+       if (altmode->partner->plug[index]) {
+               get_device(&altmode->partner->plug[index]->dev);
+               return altmode->partner->plug[index];
+       }
+
+       return NULL;
+}
+EXPORT_SYMBOL_GPL(typec_altmode_get_plug);
+
+/**
+ * typec_altmode_get_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);
+
+/* -------------------------------------------------------------------------- 
*/
+/* API for the port drivers */
+
+/**
+ * typec_find_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_find_altmode(struct typec_altmode **altmodes,
+                                        size_t n, u16 svid)
+{
+       int i;
+
+       for (i = 0; i < n; i++) {
+               if (!altmodes[i] || !altmodes[i]->svid)
+                       break;
+               if (altmodes[i]->svid == svid)
+                       return altmodes[i];
+       }
+
+       return NULL;
+}
+EXPORT_SYMBOL_GPL(typec_find_altmode);
+
+/* -------------------------------------------------------------------------- 
*/
+
+static int typec_altmode_match(struct device *dev, struct device_driver 
*driver)
+{
+       struct typec_altmode_driver *drv = to_altmode_driver(driver);
+       struct typec_altmode *altmode = to_altmode(dev);
+
+       return drv->svid == altmode->svid;
+}
+
+static int typec_altmode_uevent(struct device *dev, struct kobj_uevent_env 
*env)
+{
+       struct typec_altmode *altmode = to_altmode(dev);
+
+       return add_uevent_var(env, "MODALIAS=svid:%04x", altmode->svid);
+}
+
+static int typec_altmode_probe(struct device *dev)
+{
+       struct typec_altmode_driver *drv = to_altmode_driver(dev->driver);
+       struct typec_altmode *altmode = to_altmode(dev);
+
+       /* Fail if the port does not support the alternate mode */
+       if (!altmode->partner)
+               return -ENODEV;
+
+       return drv->probe(altmode);
+}
+
+static int typec_altmode_remove(struct device *dev)
+{
+       struct typec_altmode_driver *drv = to_altmode_driver(dev->driver);
+
+       if (drv->remove)
+               drv->remove(to_altmode(dev));
+
+       return 0;
+}
+
+struct bus_type typec_altmode_bus = {
+       .name = "typec_altmode",
+       .match = typec_altmode_match,
+       .uevent = typec_altmode_uevent,
+       .probe = typec_altmode_probe,
+       .remove = typec_altmode_remove,
+};
+
+/* -------------------------------------------------------------------------- 
*/
+
+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_altmode_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);
diff --git a/drivers/usb/typec/altmode.h b/drivers/usb/typec/altmode.h
new file mode 100644
index 000000000000..3ef0a46de68f
--- /dev/null
+++ b/drivers/usb/typec/altmode.h
@@ -0,0 +1,46 @@
+#ifndef __USB_TYPEC_ALTMODE_H__
+#define __USB_TYPEC_ALTMODE_H__
+
+#include <linux/device.h>
+#include <linux/usb/typec.h>
+
+struct typec_altmode_ops;
+
+struct typec_mode {
+       int                             index;
+       u32                             vdo;
+       char                            *desc;
+       enum typec_port_type            roles;
+
+       unsigned int                    active:1;
+
+       struct typec_altmode            *alt_mode;
+
+       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];
+
+       struct typec_altmode                    *partner;
+       struct typec_altmode                    *plug[2];
+       const struct typec_altmode_ops          *ops;
+};
+
+#define to_altmode(d) container_of(d, struct typec_altmode, dev)
+
+extern struct bus_type typec_altmode_bus;
+extern const struct device_type typec_altmode_dev_type;
+
+#endif /* __USB_TYPEC_ALTMODE_H__ */
diff --git a/drivers/usb/typec/typec.c b/drivers/usb/typec/class.c
similarity index 93%
rename from drivers/usb/typec/typec.c
rename to drivers/usb/typec/class.c
index 24e355ba109d..019673df152d 100644
--- a/drivers/usb/typec/typec.c
+++ b/drivers/usb/typec/class.c
@@ -15,32 +15,7 @@
 #include <linux/slab.h>
 #include <linux/usb/typec.h>
 
-struct typec_mode {
-       int                             index;
-       u32                             vdo;
-       char                            *desc;
-       enum typec_port_type            roles;
-
-       struct typec_altmode            *alt_mode;
-
-       unsigned int                    active:1;
-
-       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];
-};
+#include "altmode.h"
 
 struct typec_plug {
        struct device                   dev;
@@ -80,7 +55,6 @@ 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;
@@ -171,6 +145,54 @@ static void typec_report_identity(struct device *dev)
 /* ------------------------------------------------------------------------- */
 /* Alternate Modes */
 
+static int altmode_match(struct device *dev, void *data)
+{
+       struct typec_altmode *partner = data;
+
+       if (dev->type != &typec_altmode_dev_type)
+               return 0;
+
+       return to_altmode(dev)->svid == partner->svid;
+}
+
+static void typec_altmode_get_partner(struct typec_altmode *altmode)
+{
+       struct typec_port *port = typec_altmode2port(altmode);
+       struct typec_altmode *partner;
+       struct device *dev;
+
+       dev = device_find_child(&port->dev, altmode, altmode_match);
+       if (!dev)
+               return;
+
+       partner = to_altmode(dev);
+       altmode->partner = partner;
+
+       if (is_typec_plug(altmode->dev.parent)) {
+               struct typec_plug *plug = to_typec_plug(altmode->dev.parent);
+
+               partner->plug[plug->index] = altmode;
+       } else {
+               partner->partner = altmode;
+       }
+}
+
+static void typec_altmode_put_partner(struct typec_altmode *altmode)
+{
+       if (!altmode->partner)
+               return;
+
+       if (is_typec_plug(altmode->dev.parent)) {
+               struct typec_plug *plug = to_typec_plug(altmode->dev.parent);
+
+               altmode->partner->plug[plug->index] = NULL;
+       } else {
+               altmode->partner->partner = NULL;
+       }
+
+       put_device(&altmode->partner->dev);
+}
+
 /**
  * typec_altmode_update_active - Report Enter/Exit mode
  * @alt: Handle to the alternate mode
@@ -369,12 +391,14 @@ static void typec_altmode_release(struct device *dev)
        struct typec_altmode *alt = to_altmode(dev);
        int i;
 
+       typec_altmode_put_partner(alt);
+
        for (i = 0; i < alt->n_modes; i++)
                kfree(alt->modes[i].desc);
        kfree(alt);
 }
 
-static const struct device_type typec_altmode_dev_type = {
+const struct device_type typec_altmode_dev_type = {
        .name = "typec_alternate_mode",
        .groups = typec_altmode_groups,
        .release = typec_altmode_release,
@@ -382,8 +406,9 @@ static const struct device_type typec_altmode_dev_type = {
 
 static struct typec_altmode *
 typec_register_altmode(struct device *parent,
-                      const struct typec_altmode_desc *desc)
+                      const struct typec_altmode_desc *desc, void *priv)
 {
+       bool is_port = is_typec_port(parent);
        struct typec_altmode *alt;
        int ret;
 
@@ -393,13 +418,22 @@ typec_register_altmode(struct device *parent,
 
        alt->svid = desc->svid;
        alt->n_modes = desc->n_modes;
-       typec_init_modes(alt, desc->modes, is_typec_port(parent));
+       typec_init_modes(alt, desc->modes, is_port);
 
        alt->dev.parent = parent;
+       alt->dev.driver_data = priv;
        alt->dev.groups = alt->mode_groups;
        alt->dev.type = &typec_altmode_dev_type;
        dev_set_name(&alt->dev, "svid-%04x", alt->svid);
 
+       /* Linking partners and plugs with the ports */
+       if (!is_port)
+               typec_altmode_get_partner(alt);
+
+       /* The partners are bind to drivers */
+       if (is_typec_partner(parent))
+               alt->dev.bus = &typec_altmode_bus;
+
        ret = device_register(&alt->dev);
        if (ret) {
                dev_err(parent, "failed to register alternate mode (%d)\n",
@@ -501,7 +535,7 @@ struct typec_altmode *
 typec_partner_register_altmode(struct typec_partner *partner,
                               const struct typec_altmode_desc *desc)
 {
-       return typec_register_altmode(&partner->dev, desc);
+       return typec_register_altmode(&partner->dev, desc, NULL);
 }
 EXPORT_SYMBOL_GPL(typec_partner_register_altmode);
 
@@ -596,7 +630,7 @@ struct typec_altmode *
 typec_plug_register_altmode(struct typec_plug *plug,
                            const struct typec_altmode_desc *desc)
 {
-       return typec_register_altmode(&plug->dev, desc);
+       return typec_register_altmode(&plug->dev, desc, NULL);
 }
 EXPORT_SYMBOL_GPL(typec_plug_register_altmode);
 
@@ -1255,6 +1289,8 @@ EXPORT_SYMBOL_GPL(typec_set_pwr_opmode);
  * 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
+ * @ops: Port specific operations for 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.
@@ -1263,9 +1299,19 @@ EXPORT_SYMBOL_GPL(typec_set_pwr_opmode);
  */
 struct typec_altmode *
 typec_port_register_altmode(struct typec_port *port,
-                           const struct typec_altmode_desc *desc)
+                           const struct typec_altmode_desc *desc,
+                           const struct typec_altmode_ops *ops,
+                           void *driver_data)
 {
-       return typec_register_altmode(&port->dev, desc);
+       struct typec_altmode *altmode;
+
+       altmode =  typec_register_altmode(&port->dev, desc, driver_data);
+       if (!altmode)
+               return NULL;
+
+       altmode->ops = ops;
+
+       return altmode;
 }
 EXPORT_SYMBOL_GPL(typec_port_register_altmode);
 
@@ -1351,8 +1397,19 @@ EXPORT_SYMBOL_GPL(typec_unregister_port);
 
 static int __init typec_init(void)
 {
+       int ret;
+
+       ret = bus_register(&typec_altmode_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_altmode_bus);
+               return PTR_ERR(typec_class);
+       }
+
+       return 0;
 }
 subsys_initcall(typec_init);
 
@@ -1360,6 +1417,7 @@ static void __exit typec_exit(void)
 {
        class_destroy(typec_class);
        ida_destroy(&typec_index_ida);
+       bus_unregister(&typec_altmode_bus);
 }
 module_exit(typec_exit);
 
diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c
index 8483d3e33853..ffc26a3294e6 100644
--- a/drivers/usb/typec/tcpm.c
+++ b/drivers/usb/typec/tcpm.c
@@ -3572,7 +3572,7 @@ struct tcpm_port *tcpm_register_port(struct device *dev, 
struct tcpc_dev *tcpc)
                while (paltmode->svid && i < ARRAY_SIZE(port->port_altmode)) {
                        port->port_altmode[i] =
                          typec_port_register_altmode(port->typec_port,
-                                                     paltmode);
+                                                     paltmode, NULL, NULL);
                        if (!port->port_altmode[i]) {
                                tcpm_log(port,
                                         "%s: failed to register port alternate 
mode 0x%x",
diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h
index ffe7487886ca..3bee4d60d874 100644
--- a/include/linux/usb/typec.h
+++ b/include/linux/usb/typec.h
@@ -12,6 +12,7 @@
 #define USB_TYPEC_REV_1_1      0x110 /* 1.1 */
 #define USB_TYPEC_REV_1_2      0x120 /* 1.2 */
 
+struct typec_altmode_ops;
 struct typec_altmode;
 struct typec_partner;
 struct typec_cable;
@@ -123,7 +124,10 @@ struct typec_altmode
                             const struct typec_altmode_desc *desc);
 struct typec_altmode
 *typec_port_register_altmode(struct typec_port *port,
-                            const struct typec_altmode_desc *desc);
+                           const struct typec_altmode_desc *desc,
+                           const struct typec_altmode_ops *ops,
+                           void *driver_data);
+
 void typec_unregister_altmode(struct typec_altmode *altmode);
 
 struct typec_port *typec_altmode2port(struct typec_altmode *alt);
diff --git a/include/linux/usb/typec_altmode.h 
b/include/linux/usb/typec_altmode.h
new file mode 100644
index 000000000000..4c74cb19bdd3
--- /dev/null
+++ b/include/linux/usb/typec_altmode.h
@@ -0,0 +1,65 @@
+#ifndef __USB_TYPEC_ALTMODE_H
+#define __USB_TYPEC_ALTMODE_H
+
+#include <linux/device.h>
+
+struct typec_altmode;
+
+/**
+ * struct typec_altmode_ops - Alternate mode specific operations vector
+ * @vdm: Process VDM
+ * @notify: Communication channel for platform and the alternate mode
+ */
+struct typec_altmode_ops {
+       void (*vdm)(struct typec_altmode *altmode, u32 hdr, u32 *vdo, int cnt);
+       int (*notify)(struct typec_altmode *altmode,
+                     unsigned long conf, void *data);
+};
+
+int typec_altmode_notify(struct typec_altmode *altmode,
+                        unsigned long conf, void *data);
+int typec_altmode_send_vdm(struct typec_altmode *altmode,
+                          u32 header, u32 *vdo, int count);
+void typec_altmode_set_drvdata(struct typec_altmode *altmode, void *data);
+void *typec_altmode_get_drvdata(struct typec_altmode *altmode);
+
+void typec_altmode_register_ops(struct typec_altmode *altmode,
+                               struct typec_altmode_ops *ops);
+struct typec_altmode *typec_altmode_get_plug(struct typec_altmode *altmode,
+                                            int index);
+void typec_altmode_put_plug(struct typec_altmode *plug);
+
+struct typec_altmode *typec_find_altmode(struct typec_altmode **altmodes,
+                                        size_t n, u16 svid);
+
+/**
+ * struct typec_altmode_driver - USB Type-C alternate mode device driver
+ * @svid: Standard or Vendor ID of the alternate mode
+ * @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 using VDMs (Vendor Defined Messages).
+ */
+struct typec_altmode_driver {
+       const u16 svid;
+       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 */
-- 
2.14.1

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to