Bus for binding SVID specific drivers to the altnernate mode devices. Signed-off-by: Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx> --- 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.krogerus@xxxxxxxxxxxxxxx> + * + * 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 majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html