usb: typec: Bus type for alternate modes

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 <heikki.krogerus@linux.intel.com>
Tested-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Heikki Krogerus 2018-06-27 18:19:50 +03:00 коммит произвёл Greg Kroah-Hartman
Родитель 4ab8c18d4d
Коммит 8a37d87d72
14 изменённых файлов: 1174 добавлений и 147 удалений

Просмотреть файл

@ -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 <heikki.krogerus@linux.intel.com>
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 <heikki.krogerus@linux.intel.com>
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 <heikki.krogerus@linux.intel.com>
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 <heikki.krogerus@linux.intel.com>
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 <heikki.krogerus@linux.intel.com>
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

Просмотреть файл

@ -0,0 +1,51 @@
What: /sys/bus/typec/devices/.../active
Date: July 2018
Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
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 <heikki.krogerus@linux.intel.com>
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 <heikki.krogerus@linux.intel.com>
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 <heikki.krogerus@linux.intel.com>
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 <heikki.krogerus@linux.intel.com>
Description:
Shows the VDO in hexadecimal returned by Discover Modes command
for this mode.

Просмотреть файл

@ -222,70 +222,12 @@ Description:
available. The value can be polled. 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. What: /sys/class/typec/<port>/<alt mode>/supported_roles
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 <heikki.krogerus@linux.intel.com>
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 <heikki.krogerus@linux.intel.com>
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 <heikki.krogerus@linux.intel.com>
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 <heikki.krogerus@linux.intel.com>
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 <heikki.krogerus@linux.intel.com>
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
Date: April 2017 Date: April 2017
Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Description: Description:
Space separated list of the supported roles. 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 Valid values: source, sink

Просмотреть файл

@ -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 operation callbacks 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

Просмотреть файл

@ -14955,7 +14955,7 @@ L: linux-usb@vger.kernel.org
S: Maintained S: Maintained
F: drivers/usb/typec/mux/pi3usb30532.c F: drivers/usb/typec/mux/pi3usb30532.c
USB TYPEC SUBSYSTEM USB TYPEC CLASS
M: Heikki Krogerus <heikki.krogerus@linux.intel.com> M: Heikki Krogerus <heikki.krogerus@linux.intel.com>
L: linux-usb@vger.kernel.org L: linux-usb@vger.kernel.org
S: Maintained S: Maintained
@ -14964,6 +14964,15 @@ F: Documentation/driver-api/usb/typec.rst
F: drivers/usb/typec/ F: drivers/usb/typec/
F: include/linux/usb/typec.h F: include/linux/usb/typec.h
USB TYPEC BUS FOR ALTERNATE MODES
M: Heikki Krogerus <heikki.krogerus@linux.intel.com>
L: linux-usb@vger.kernel.org
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 USB UHCI DRIVER
M: Alan Stern <stern@rowland.harvard.edu> M: Alan Stern <stern@rowland.harvard.edu>
L: linux-usb@vger.kernel.org L: linux-usb@vger.kernel.org

Просмотреть файл

@ -1,6 +1,6 @@
# SPDX-License-Identifier: GPL-2.0 # SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_TYPEC) += typec.o 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-$(CONFIG_TYPEC_TCPM) += tcpm.o
obj-y += fusb302/ obj-y += fusb302/
obj-$(CONFIG_TYPEC_WCOVE) += typec_wcove.o obj-$(CONFIG_TYPEC_WCOVE) += typec_wcove.o

401
drivers/usb/typec/bus.c Normal file
Просмотреть файл

@ -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 <heikki.krogerus@linux.intel.com>
*/
#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;
if (!pdev->ops || !pdev->ops->enter)
return -EOPNOTSUPP;
/* Moving to USB Safe State */
ret = typec_altmode_set_state(adev, TYPEC_STATE_SAFE);
if (ret)
return ret;
/* Enter Mode */
return pdev->ops->enter(pdev);
}
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;
if (!pdev->ops || !pdev->ops->enter)
return -EOPNOTSUPP;
/* Moving to USB Safe State */
ret = typec_altmode_set_state(adev, TYPEC_STATE_SAFE);
if (ret)
return ret;
/* Exit Mode command */
return pdev->ops->exit(pdev);
}
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 -EOPNOTSUPP;
return pdev->ops->vdm(pdev, header, vdo, count);
}
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,
};

38
drivers/usb/typec/bus.h Normal file
Просмотреть файл

@ -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__ */

Просмотреть файл

@ -10,28 +10,13 @@
#include <linux/module.h> #include <linux/module.h>
#include <linux/mutex.h> #include <linux/mutex.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/usb/typec.h>
#include <linux/usb/typec_mux.h>
struct typec_altmode { #include "bus.h"
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];
};
struct typec_plug { struct typec_plug {
struct device dev; struct device dev;
enum typec_plug_index index; enum typec_plug_index index;
struct ida mode_ids;
}; };
struct typec_cable { struct typec_cable {
@ -46,11 +31,13 @@ struct typec_partner {
unsigned int usb_pd:1; unsigned int usb_pd:1;
struct usb_pd_identity *identity; struct usb_pd_identity *identity;
enum typec_accessory accessory; enum typec_accessory accessory;
struct ida mode_ids;
}; };
struct typec_port { struct typec_port {
unsigned int id; unsigned int id;
struct device dev; struct device dev;
struct ida mode_ids;
int prefer_role; int prefer_role;
enum typec_data_role data_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_plug(_dev_) container_of(_dev_, struct typec_plug, dev)
#define to_typec_cable(_dev_) container_of(_dev_, struct typec_cable, 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_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_partner_dev_type;
static const struct device_type typec_cable_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_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_partner(_dev_) (_dev_->type == &typec_partner_dev_type)
#define is_typec_cable(_dev_) (_dev_->type == &typec_cable_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_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 DEFINE_IDA(typec_index_ida);
static struct class *typec_class; static struct class *typec_class;
@ -163,25 +147,148 @@ static void typec_report_identity(struct device *dev)
/* ------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------- */
/* Alternate Modes */ /* 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 * 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 * @active: True when the mode has been entered
* *
* If a partner or cable plug executes Enter/Exit Mode command successfully, the * 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. * 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]; char dir[6];
if (alt->active == active) if (adev->active == active)
return; return;
alt->active = active; if (!is_typec_port(adev->dev.parent)) {
snprintf(dir, sizeof(dir), "mode%d", alt->mode); if (!active)
sysfs_notify(&alt->dev.kobj, dir, "active"); module_put(adev->dev.driver->owner);
kobject_uevent(&alt->dev.kobj, KOBJ_CHANGE); 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); EXPORT_SYMBOL_GPL(typec_altmode_update_active);
@ -208,7 +315,7 @@ EXPORT_SYMBOL_GPL(typec_altmode2port);
static ssize_t static ssize_t
vdo_show(struct device *dev, struct device_attribute *attr, char *buf) 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); return sprintf(buf, "0x%08x\n", alt->vdo);
} }
@ -217,7 +324,7 @@ static DEVICE_ATTR_RO(vdo);
static ssize_t static ssize_t
description_show(struct device *dev, struct device_attribute *attr, char *buf) 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 : ""); return sprintf(buf, "%s\n", alt->desc ? alt->desc : "");
} }
@ -226,7 +333,7 @@ static DEVICE_ATTR_RO(description);
static ssize_t static ssize_t
active_show(struct device *dev, struct device_attribute *attr, char *buf) 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"); 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, static ssize_t active_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t size) const char *buf, size_t size)
{ {
struct typec_altmode *alt = to_altmode(dev); struct typec_altmode *adev = to_typec_altmode(dev);
struct typec_port *port = typec_altmode2port(alt); struct altmode *altmode = to_altmode(adev);
bool activate; bool enter;
int ret; int ret;
if (!port->cap->activate_mode) ret = kstrtobool(buf, &enter);
return -EOPNOTSUPP;
ret = kstrtobool(buf, &activate);
if (ret) if (ret)
return ret; return ret;
ret = port->cap->activate_mode(port->cap, alt->mode, activate); if (adev->active == enter)
if (ret) return size;
return ret;
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; return size;
} }
@ -258,7 +381,7 @@ static ssize_t
supported_roles_show(struct device *dev, struct device_attribute *attr, supported_roles_show(struct device *dev, struct device_attribute *attr,
char *buf) char *buf)
{ {
struct typec_altmode *alt = to_altmode(dev); struct altmode *alt = to_altmode(to_typec_altmode(dev));
ssize_t ret; ssize_t ret;
switch (alt->roles) { switch (alt->roles) {
@ -277,29 +400,72 @@ supported_roles_show(struct device *dev, struct device_attribute *attr,
} }
static DEVICE_ATTR_RO(supported_roles); 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, static ssize_t
char *buf) 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 DEVICE_ATTR_RO(svid);
static struct attribute *typec_altmode_attrs[] = { static struct attribute *typec_altmode_attrs[] = {
&dev_attr_active.attr,
&dev_attr_mode.attr,
&dev_attr_svid.attr, &dev_attr_svid.attr,
&dev_attr_vdo.attr,
NULL NULL
}; };
ATTRIBUTE_GROUPS(typec_altmode); 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", .name = "typec_alternate_mode",
.groups = typec_altmode_groups, .groups = typec_altmode_groups,
.release = typec_altmode_release, .release = typec_altmode_release,
@ -309,58 +475,74 @@ static struct typec_altmode *
typec_register_altmode(struct device *parent, typec_register_altmode(struct device *parent,
const struct typec_altmode_desc *desc) 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; int ret;
alt = kzalloc(sizeof(*alt), GFP_KERNEL); alt = kzalloc(sizeof(*alt), GFP_KERNEL);
if (!alt) if (!alt)
return ERR_PTR(-ENOMEM); return ERR_PTR(-ENOMEM);
alt->svid = desc->svid; alt->adev.svid = desc->svid;
alt->mode = desc->mode; alt->adev.mode = desc->mode;
alt->vdo = desc->vdo; alt->adev.vdo = desc->vdo;
alt->roles = desc->roles; alt->roles = desc->roles;
alt->id = id;
alt->attrs[0] = &dev_attr_vdo.attr; alt->attrs[0] = &dev_attr_vdo.attr;
alt->attrs[1] = &dev_attr_description.attr; alt->attrs[1] = &dev_attr_description.attr;
alt->attrs[2] = &dev_attr_active.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->attrs[3] = &dev_attr_supported_roles.attr;
alt->adev.active = true; /* Enabled by default */
}
sprintf(alt->group_name, "mode%d", desc->mode); sprintf(alt->group_name, "mode%d", desc->mode);
alt->group.name = alt->group_name; alt->group.name = alt->group_name;
alt->group.attrs = alt->attrs; alt->group.attrs = alt->attrs;
alt->groups[0] = &alt->group; alt->groups[0] = &alt->group;
alt->dev.parent = parent; alt->adev.dev.parent = parent;
alt->dev.groups = alt->groups; alt->adev.dev.groups = alt->groups;
alt->dev.type = &typec_altmode_dev_type; alt->adev.dev.type = &typec_altmode_dev_type;
dev_set_name(&alt->dev, "%s-%04x:%u", dev_name(parent), dev_set_name(&alt->adev.dev, "%s.%u", dev_name(parent), id);
alt->svid, alt->mode);
ret = device_register(&alt->dev); /* 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->adev.dev);
if (ret) { if (ret) {
dev_err(parent, "failed to register alternate mode (%d)\n", dev_err(parent, "failed to register alternate mode (%d)\n",
ret); ret);
put_device(&alt->dev); put_device(&alt->adev.dev);
return ERR_PTR(ret); return ERR_PTR(ret);
} }
return alt; return &alt->adev;
} }
/** /**
* typec_unregister_altmode - Unregister Alternate Mode * 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(), * Unregister device created with typec_partner_register_altmode(),
* typec_plug_register_altmode() or typec_port_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)) if (IS_ERR_OR_NULL(adev))
device_unregister(&alt->dev); return;
typec_mux_put(to_altmode(adev)->mux);
device_unregister(&adev->dev);
} }
EXPORT_SYMBOL_GPL(typec_unregister_altmode); 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); struct typec_partner *partner = to_typec_partner(dev);
ida_destroy(&partner->mode_ids);
kfree(partner); kfree(partner);
} }
@ -463,6 +646,7 @@ struct typec_partner *typec_register_partner(struct typec_port *port,
if (!partner) if (!partner)
return ERR_PTR(-ENOMEM); return ERR_PTR(-ENOMEM);
ida_init(&partner->mode_ids);
partner->usb_pd = desc->usb_pd; partner->usb_pd = desc->usb_pd;
partner->accessory = desc->accessory; partner->accessory = desc->accessory;
@ -511,6 +695,7 @@ static void typec_plug_release(struct device *dev)
{ {
struct typec_plug *plug = to_typec_plug(dev); struct typec_plug *plug = to_typec_plug(dev);
ida_destroy(&plug->mode_ids);
kfree(plug); kfree(plug);
} }
@ -563,6 +748,7 @@ struct typec_plug *typec_register_plug(struct typec_cable *cable,
sprintf(name, "plug%d", desc->index); sprintf(name, "plug%d", desc->index);
ida_init(&plug->mode_ids);
plug->index = desc->index; plug->index = desc->index;
plug->dev.class = typec_class; plug->dev.class = typec_class;
plug->dev.parent = &cable->dev; plug->dev.parent = &cable->dev;
@ -1083,12 +1269,13 @@ static void typec_release(struct device *dev)
struct typec_port *port = to_typec_port(dev); struct typec_port *port = to_typec_port(dev);
ida_simple_remove(&typec_index_ida, port->id); ida_simple_remove(&typec_index_ida, port->id);
ida_destroy(&port->mode_ids);
typec_switch_put(port->sw); typec_switch_put(port->sw);
typec_mux_put(port->mux); typec_mux_put(port->mux);
kfree(port); kfree(port);
} }
static const struct device_type typec_port_dev_type = { const struct device_type typec_port_dev_type = {
.name = "typec_port", .name = "typec_port",
.groups = typec_groups, .groups = typec_groups,
.uevent = typec_uevent, .uevent = typec_uevent,
@ -1279,11 +1466,11 @@ EXPORT_SYMBOL_GPL(typec_get_orientation);
/** /**
* typec_set_mode - Set mode of operation for USB Type-C connector * typec_set_mode - Set mode of operation for USB Type-C connector
* @port: USB Type-C port for the connector * @port: USB Type-C connector
* @mode: Operation mode for the connector * @mode: Accessory Mode, USB Operation or Safe State
* *
* Set mode @mode for @port. This function will configure the muxes needed to * Configure @port for Accessory Mode @mode. This function will configure the
* enter @mode. * muxes needed for @mode.
*/ */
int typec_set_mode(struct typec_port *port, int mode) int typec_set_mode(struct typec_port *port, int mode)
{ {
@ -1297,6 +1484,7 @@ EXPORT_SYMBOL_GPL(typec_set_mode);
* typec_port_register_altmode - Register USB Type-C Port Alternate Mode * typec_port_register_altmode - Register USB Type-C Port Alternate Mode
* @port: USB Type-C Port that supports the alternate mode * @port: USB Type-C Port that supports the alternate mode
* @desc: Description of 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 * This routine is used to register an alternate mode that @port is capable of
* supporting. * supporting.
@ -1307,7 +1495,23 @@ struct typec_altmode *
typec_port_register_altmode(struct typec_port *port, typec_port_register_altmode(struct typec_port *port,
const struct typec_altmode_desc *desc) 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); EXPORT_SYMBOL_GPL(typec_port_register_altmode);
@ -1381,10 +1585,12 @@ struct typec_port *typec_register_port(struct device *parent,
break; break;
} }
ida_init(&port->mode_ids);
mutex_init(&port->port_type_lock);
port->id = id; port->id = id;
port->cap = cap; port->cap = cap;
port->port_type = cap->type; port->port_type = cap->type;
mutex_init(&port->port_type_lock);
port->prefer_role = cap->prefer_role; port->prefer_role = cap->prefer_role;
port->dev.class = typec_class; port->dev.class = typec_class;
@ -1428,8 +1634,19 @@ EXPORT_SYMBOL_GPL(typec_unregister_port);
static int __init typec_init(void) static int __init typec_init(void)
{ {
int ret;
ret = bus_register(&typec_bus);
if (ret)
return ret;
typec_class = class_create(THIS_MODULE, "typec"); 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); subsys_initcall(typec_init);
@ -1437,6 +1654,7 @@ static void __exit typec_exit(void)
{ {
class_destroy(typec_class); class_destroy(typec_class);
ida_destroy(&typec_index_ida); ida_destroy(&typec_index_ida);
bus_unregister(&typec_bus);
} }
module_exit(typec_exit); module_exit(typec_exit);

Просмотреть файл

@ -746,4 +746,19 @@ struct tb_service_id {
#define TBSVC_MATCH_PROTOCOL_VERSION 0x0004 #define TBSVC_MATCH_PROTOCOL_VERSION 0x0004
#define TBSVC_MATCH_PROTOCOL_REVISION 0x0008 #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 */ #endif /* LINUX_MOD_DEVICETABLE_H */

Просмотреть файл

@ -5,21 +5,18 @@
#include <linux/types.h> #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 */ /* USB Type-C Specification releases */
#define USB_TYPEC_REV_1_0 0x100 /* 1.0 */ #define USB_TYPEC_REV_1_0 0x100 /* 1.0 */
#define USB_TYPEC_REV_1_1 0x110 /* 1.1 */ #define USB_TYPEC_REV_1_1 0x110 /* 1.1 */
#define USB_TYPEC_REV_1_2 0x120 /* 1.2 */ #define USB_TYPEC_REV_1_2 0x120 /* 1.2 */
struct typec_altmode;
struct typec_partner; struct typec_partner;
struct typec_cable; struct typec_cable;
struct typec_plug; struct typec_plug;
struct typec_port; struct typec_port;
struct fwnode_handle; struct fwnode_handle;
struct device;
enum typec_port_type { enum typec_port_type {
TYPEC_PORT_SRC, TYPEC_PORT_SRC,
@ -107,7 +104,7 @@ struct typec_altmode_desc {
u8 mode; u8 mode;
u32 vdo; u32 vdo;
/* Only used with ports */ /* Only used with ports */
enum typec_port_type roles; enum typec_port_data roles;
}; };
struct typec_altmode struct typec_altmode
@ -186,7 +183,6 @@ struct typec_partner_desc {
* @dr_set: Set Data Role * @dr_set: Set Data Role
* @pr_set: Set Power Role * @pr_set: Set Power Role
* @vconn_set: Set VCONN Role * @vconn_set: Set VCONN Role
* @activate_mode: Enter/exit given Alternate Mode
* @port_type_set: Set port type * @port_type_set: Set port type
* *
* Static capabilities of a single USB Type-C port. * Static capabilities of a single USB Type-C port.
@ -212,12 +208,8 @@ struct typec_capability {
enum typec_role); enum typec_role);
int (*vconn_set)(const struct typec_capability *, int (*vconn_set)(const struct typec_capability *,
enum typec_role); enum typec_role);
int (*activate_mode)(const struct typec_capability *,
int mode, int activate);
int (*port_type_set)(const struct typec_capability *, 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. */ /* Specific to try_role(). Indicates the user want's to clear the preference. */

Просмотреть файл

@ -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 {
int (*enter)(struct typec_altmode *altmode);
int (*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 */

Просмотреть файл

@ -221,5 +221,9 @@ int main(void)
DEVID_FIELD(tb_service_id, protocol_version); DEVID_FIELD(tb_service_id, protocol_version);
DEVID_FIELD(tb_service_id, protocol_revision); 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; return 0;
} }

Просмотреть файл

@ -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); 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? */ /* Does namelen bytes of name exactly match the symbol? */
static bool sym_is(const char *name, unsigned namelen, const char *symbol) static bool sym_is(const char *name, unsigned namelen, const char *symbol)
{ {