On Wed, Jun 21, 2023 at 11:03:29AM +0200, Ahmad Fatoum wrote: > Board code or user scripts may want to base their decisions on the > state of a USB-C connector: > > - Is the USB role that of a USB device or of a USB host? > - Is the board being powered externally? > - Is a debug accessory attached? > > For this reason, we add here a very simplified Type-C driver core inspired > by Linux that just registers a device and populates it with three parameters: > > - $dev.usb_role = { device, host } > - $dev.pwr_role = { sink, source } > - $dev.accessory = { none, audio, debug } > > These variables are updated by struct typec_operations::poll, which > corresponds to the IRQ handler for the Linux drivers. The poll function > is called on every variable access and early exits if no IRQ was > indicated. > > In future, we may elect to register the poll function with the poller > framework, so it's called cyclically, like we do for e.g. Ethernet link > detection, but for now, this is probably sufficient. > > Signed-off-by: Ahmad Fatoum <a.fatoum@xxxxxxxxxxxxxx> > --- > v1 -> v2: > - add missing <linux/usb/role.h> header from fixup > - split framework from driver Applied, thanks Sascha > --- > Documentation/user/usb.rst | 12 ++ > drivers/usb/Kconfig | 2 + > drivers/usb/Makefile | 1 + > drivers/usb/typec/Kconfig | 6 + > drivers/usb/typec/Makefile | 2 + > drivers/usb/typec/class.c | 179 ++++++++++++++++++++++++++++++ > include/linux/usb/role.h | 12 ++ > include/linux/usb/typec.h | 54 +++++++++ > include/linux/usb/typec_altmode.h | 44 ++++++++ > 9 files changed, 312 insertions(+) > create mode 100644 drivers/usb/typec/Kconfig > create mode 100644 drivers/usb/typec/Makefile > create mode 100644 drivers/usb/typec/class.c > create mode 100644 include/linux/usb/role.h > create mode 100644 include/linux/usb/typec.h > create mode 100644 include/linux/usb/typec_altmode.h > > diff --git a/Documentation/user/usb.rst b/Documentation/user/usb.rst > index f2f57ead98d4..20e223da93b5 100644 > --- a/Documentation/user/usb.rst > +++ b/Documentation/user/usb.rst > @@ -247,6 +247,18 @@ mode. Once a specific mode has been selected it can't be changed later anymore. > musb-hdrc: 28/31 max ep, 16384/16384 memory > barebox:/ > > +USB Type-C support > +------------------ > + > +barebox can usually stay oblivious to the type of connector used. Sometimes though, > +board code and user scripts may want to base their decisions on how a USB-C connector > +is connected. Type C drivers can thus register with the Type C driver core to > +export a number of device parameters: > + > +- ``$typec0.usb_role`` = { ``none``, ``device``, ``host`` } > +- ``$typec0.pwr_role`` = { ``sink``, ``source`` } > +- ``$typec0.accessory`` = { ``none``, ``audio``, ``debug`` } > + > USB Gadget autostart Support > ---------------------------- > > diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig > index e43c28113f41..d66a75635d46 100644 > --- a/drivers/usb/Kconfig > +++ b/drivers/usb/Kconfig > @@ -24,6 +24,8 @@ source "drivers/usb/misc/Kconfig" > > endif > > +source "drivers/usb/typec/Kconfig" > + > source "drivers/usb/gadget/Kconfig" > > source "drivers/usb/musb/Kconfig" > diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile > index 8f1557d5d497..0cac50c0f39b 100644 > --- a/drivers/usb/Makefile > +++ b/drivers/usb/Makefile > @@ -8,5 +8,6 @@ obj-$(CONFIG_USB_STORAGE) += storage/ > obj-y += host/ > obj-y += otg/ > obj-y += gadget/ > +obj-y += typec/ > obj-$(CONFIG_USB) += misc/ > > diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig > new file mode 100644 > index 000000000000..56f10e6ca549 > --- /dev/null > +++ b/drivers/usb/typec/Kconfig > @@ -0,0 +1,6 @@ > +# SPDX-License-Identifier: GPL-2.0-only > + > + > +config TYPEC > + prompt "Compile USB Type-C framework support" if COMPILE_TEST > + bool > diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile > new file mode 100644 > index 000000000000..6b8347f10c06 > --- /dev/null > +++ b/drivers/usb/typec/Makefile > @@ -0,0 +1,2 @@ > +# SPDX-License-Identifier: GPL-2.0-only > +obj-$(CONFIG_TYPEC) += class.o > diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c > new file mode 100644 > index 000000000000..7f498550f80e > --- /dev/null > +++ b/drivers/usb/typec/class.c > @@ -0,0 +1,179 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * USB Type-C Connector Class > + * > + * Copyright (C) 2017, Intel Corporation > + * Author: Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx> > + */ > + > +#include <module.h> > +#include <driver.h> > +#include <linux/usb/role.h> > +#include <linux/usb/typec.h> > +#include <linux/usb/typec_altmode.h> > +#include <param.h> > + > +enum typec_param_accessory { > + TYPEC_PARAM_ACCESSORY_NONE, > + TYPEC_PARAM_ACCESSORY_AUDIO, > + TYPEC_PARAM_ACCESSORY_DEBUG, > +}; > + > +struct typec_port { > + struct device dev; > + const struct typec_operations *ops; > + int pwr_role; /* enum typec_role */ > + int usb_role; /* enum usb_role role */ > + int accessory; /* enum typec_param_accessory */ > +}; > + > +/** > + * typec_set_pwr_role - Report power role change > + * @port: The USB Type-C Port where the role was changed > + * @role: The new data role > + * > + * This routine is used by the port drivers to report power role changes. > + */ > +void typec_set_pwr_role(struct typec_port *port, enum typec_role role) > +{ > + port->pwr_role = role; > +} > +EXPORT_SYMBOL_GPL(typec_set_pwr_role); > + > +static inline enum typec_param_accessory typec_mode_to_accessory(int mode) > +{ > + switch (mode) { > + case TYPEC_MODE_AUDIO: > + return TYPEC_PARAM_ACCESSORY_AUDIO; > + case TYPEC_MODE_DEBUG: > + return TYPEC_PARAM_ACCESSORY_DEBUG; > + default: > + return TYPEC_PARAM_ACCESSORY_NONE; > + } > +} > + > +/** > + * typec_set_mode - Set mode of operation for USB Type-C connector > + * @port: USB Type-C connector > + * @mode: Accessory Mode, USB Operation or Safe State > + * > + * 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) > +{ > + port->accessory = typec_mode_to_accessory(mode); > + return 0; > +} > +EXPORT_SYMBOL_GPL(typec_set_mode); > + > +/** > + * typec_set_role - Set USB role for a Type-C connector > + * @port: USB Type-C connector > + * @role: USB role to be switched to > + * > + * Set USB role @role for @sw. This is equivalent to Linux > + * usb_role_switch_set_role(); > + */ > +int typec_set_role(struct typec_port *port, enum usb_role role) > +{ > + port->usb_role = role; > + return 0; > +} > +EXPORT_SYMBOL_GPL(typec_set_role); > + > +/** > + * typec_get_drvdata - Return private driver data pointer > + * @port: USB Type-C port > + */ > +void *typec_get_drvdata(struct typec_port *port) > +{ > + return port->dev.priv; > +} > +EXPORT_SYMBOL_GPL(typec_get_drvdata); > + > +static int typec_register_port_dev(struct typec_port *port, const char *name, int id) > +{ > + port->dev.id = id; > + dev_set_name(&port->dev, name); > + > + return register_device(&port->dev); > +} > + > +static const char * const usb_role_names[] = { > + [USB_ROLE_NONE] = "none", > + [USB_ROLE_HOST] = "host", > + [USB_ROLE_DEVICE] = "device", > +}; > + > +static const char * const pwr_role_names[] = { > + [TYPEC_SINK] = "sink", > + [TYPEC_SOURCE] = "source", > +}; > + > +static const char * const accessory_names[] = { > + [TYPEC_PARAM_ACCESSORY_NONE] = "none", > + [TYPEC_PARAM_ACCESSORY_AUDIO] = "audio", /* analog */ > + [TYPEC_PARAM_ACCESSORY_DEBUG] = "debug", > +}; > + > +static int typec_param_update(struct param_d *p, void *priv) > +{ > + struct typec_port *port = priv; > + > + return port->ops->poll(port); > +} > + > +/** > + * typec_register_port - Register a USB Type-C Port > + * @parent: Parent device > + * @cap: Description of the port > + * > + * Registers a device for USB Type-C Port described in @cap. > + * > + * Returns handle to the port on success or ERR_PTR on failure. > + */ > +struct typec_port *typec_register_port(struct device *parent, > + const struct typec_capability *cap) > +{ > + struct typec_port *port; > + struct device *dev; > + const char *alias; > + int ret; > + > + port = kzalloc(sizeof(*port), GFP_KERNEL); > + if (!port) > + return ERR_PTR(-ENOMEM); > + > + port->ops = cap->ops; > + dev = &port->dev; > + dev->parent = parent; > + dev->of_node = cap->of_node; > + dev->priv = cap->driver_data; > + > + alias = dev->of_node ? of_alias_get(dev->of_node) : NULL; > + if (alias) > + ret = typec_register_port_dev(port, alias, DEVICE_ID_SINGLE); > + if (!alias || ret) > + ret = typec_register_port_dev(port, "typec", DEVICE_ID_DYNAMIC); > + > + if (ret) > + return ERR_PTR(ret); > + > + of_platform_device_dummy_drv(dev); > + if (dev->of_node) > + dev->of_node->dev = dev; > + > + dev_add_param_enum(dev, "usb_role", param_set_readonly, typec_param_update, > + &port->usb_role, usb_role_names, > + ARRAY_SIZE(usb_role_names), port); > + dev_add_param_enum(dev, "pwr_role", param_set_readonly, typec_param_update, > + &port->pwr_role, pwr_role_names, > + ARRAY_SIZE(pwr_role_names), port); > + dev_add_param_enum(dev, "accessory", param_set_readonly, typec_param_update, > + &port->accessory, accessory_names, > + ARRAY_SIZE(accessory_names), port); > + > + return port; > +} > +EXPORT_SYMBOL_GPL(typec_register_port); > diff --git a/include/linux/usb/role.h b/include/linux/usb/role.h > new file mode 100644 > index 000000000000..bf78db7e6fa8 > --- /dev/null > +++ b/include/linux/usb/role.h > @@ -0,0 +1,12 @@ > +// SPDX-License-Identifier: GPL-2.0 > + > +#ifndef __LINUX_USB_ROLE_H > +#define __LINUX_USB_ROLE_H > + > +enum usb_role { > + USB_ROLE_NONE, > + USB_ROLE_HOST, > + USB_ROLE_DEVICE, > +}; > + > +#endif /* __LINUX_USB_ROLE_H */ > diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h > new file mode 100644 > index 000000000000..315dee95e47f > --- /dev/null > +++ b/include/linux/usb/typec.h > @@ -0,0 +1,54 @@ > +/* SPDX-License-Identifier: GPL-2.0 */ > + > +#ifndef __LINUX_USB_TYPEC_H > +#define __LINUX_USB_TYPEC_H > + > +#include <linux/types.h> > +#include <linux/usb/role.h> > + > +struct typec_port; > + > +struct device; > +struct device_node; > + > +enum typec_role { > + TYPEC_SINK, > + TYPEC_SOURCE, > +}; > + > +enum typec_accessory { > + TYPEC_ACCESSORY_NONE, > + TYPEC_ACCESSORY_AUDIO, > + TYPEC_ACCESSORY_DEBUG, > +}; > + > +struct typec_operations { > + int (*poll)(struct typec_port *port); > +}; > + > +/* > + * struct typec_capability - USB Type-C Port Capabilities > + * @driver_data: Private pointer for driver specific info > + * @ops: Port operations vector > + * > + * Static capabilities of a single USB Type-C port. > + */ > +struct typec_capability { > + void *driver_data; > + > + const struct typec_operations *ops; > + struct device_node *of_node; > +}; > + > +struct typec_port *typec_register_port(struct device *parent, > + const struct typec_capability *cap); > + > +void typec_set_pwr_role(struct typec_port *port, enum typec_role role); > + > +int typec_set_mode(struct typec_port *port, int mode); > + > +int typec_set_role(struct typec_port *port, enum usb_role role); > + > +void *typec_get_drvdata(struct typec_port *port); > + > +#endif /* __LINUX_USB_TYPEC_H */ > diff --git a/include/linux/usb/typec_altmode.h b/include/linux/usb/typec_altmode.h > new file mode 100644 > index 000000000000..ffa4a8f75420 > --- /dev/null > +++ b/include/linux/usb/typec_altmode.h > @@ -0,0 +1,44 @@ > +/* SPDX-License-Identifier: GPL-2.0 */ > + > +#ifndef __USB_TYPEC_ALTMODE_H > +#define __USB_TYPEC_ALTMODE_H > + > +/* > + * 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. > + * > + * USB4 also requires that the pins on the connector are repurposed, just like > + * Alternate Modes. USB4 mode is however not entered with the Enter Mode Command > + * like the Alternate Modes are, but instead with a special Enter_USB Message. > + * The Enter_USB Message can also be used for setting to connector to operate in > + * USB 3.2 or in USB 2.0 mode instead of USB4. > + * > + * The Enter_USB specific "USB Modes" are also supplied here as special modal > + * state values, just like the Accessory Modes. > + */ > +enum { > + TYPEC_MODE_USB2 = TYPEC_STATE_MODAL, /* USB 2.0 mode */ > + TYPEC_MODE_USB3, /* USB 3.2 mode */ > + TYPEC_MODE_USB4, /* USB4 mode */ > + TYPEC_MODE_AUDIO, /* Audio Accessory */ > + TYPEC_MODE_DEBUG, /* Debug Accessory */ > +}; > + > +#endif /* __USB_TYPEC_ALTMODE_H */ > -- > 2.39.2 > > > -- Pengutronix e.K. | | Steuerwalder Str. 21 | http://www.pengutronix.de/ | 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 | Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |