Skip to content
Navigation Menu
Toggle navigation
Sign in
In this repository
All GitHub Enterprise
↵
Jump to
↵
No suggested jump to results
In this repository
All GitHub Enterprise
↵
Jump to
↵
In this organization
All GitHub Enterprise
↵
Jump to
↵
In this repository
All GitHub Enterprise
↵
Jump to
↵
Sign in
Reseting focus
You signed in with another tab or window.
Reload
to refresh your session.
You signed out in another tab or window.
Reload
to refresh your session.
You switched accounts on another tab or window.
Reload
to refresh your session.
Dismiss alert
{{ message }}
mariux64
/
linux
Public
Notifications
You must be signed in to change notification settings
Fork
0
Star
0
Code
Issues
2
Pull requests
0
Actions
Projects
0
Wiki
Security
Insights
Additional navigation options
Code
Issues
Pull requests
Actions
Projects
Wiki
Security
Insights
Files
8541bf0
Documentation
LICENSES
arch
block
certs
crypto
drivers
accel
accessibility
acpi
amba
android
ata
atm
auxdisplay
base
bcma
block
bluetooth
bus
cache
cdrom
cdx
char
clk
clocksource
comedi
connector
counter
cpufreq
cpuidle
crypto
cxl
dax
dca
devfreq
dio
dma-buf
dma
dpll
edac
eisa
extcon
firewire
firmware
fpga
fsi
gnss
gpio
gpu
greybus
hid
hsi
hte
hv
hwmon
hwspinlock
hwtracing
i2c
i3c
idle
iio
infiniband
input
interconnect
iommu
ipack
irqchip
isdn
leds
macintosh
mailbox
mcb
md
media
memory
memstick
message
mfd
misc
mmc
most
mtd
mux
net
nfc
ntb
nubus
nvdimm
nvme
nvmem
of
opp
parisc
parport
pci
pcmcia
peci
perf
phy
pinctrl
platform
pmdomain
pnp
power
powercap
pps
ps3
ptp
pwm
rapidio
ras
regulator
remoteproc
reset
rpmsg
rtc
s390
sbus
scsi
sh
siox
slimbus
soc
soundwire
spi
spmi
ssb
staging
target
tc
tee
thermal
thunderbolt
tty
ufs
uio
usb
atm
c67x00
cdns3
chipidea
class
common
core
dwc2
dwc3
early
fotg210
gadget
host
image
isp1760
misc
mon
mtu3
musb
phy
renesas_usbhs
roles
serial
storage
typec
altmodes
mux
tcpm
tipd
ucsi
Kconfig
Makefile
anx7411.c
bus.c
bus.h
class.c
class.h
hd3ss3220.c
mux.c
mux.h
pd.c
pd.h
port-mapper.c
retimer.c
retimer.h
rt1719.c
stusb160x.c
wusb3801.c
usbip
Kconfig
Makefile
usb-skeleton.c
vdpa
vfio
vhost
video
virt
virtio
w1
watchdog
xen
zorro
Kconfig
Makefile
fs
include
init
io_uring
ipc
kernel
lib
mm
net
rust
samples
scripts
security
sound
tools
usr
virt
.clang-format
.clippy.toml
.cocciconfig
.editorconfig
.get_maintainer.ignore
.gitattributes
.gitignore
.mailmap
.rustfmt.toml
COPYING
CREDITS
Kbuild
Kconfig
MAINTAINERS
Makefile
README
Breadcrumbs
linux
/
drivers
/
usb
/
typec
/
class.c
Blame
Blame
Latest commit
History
History
2763 lines (2306 loc) · 69.4 KB
Breadcrumbs
linux
/
drivers
/
usb
/
typec
/
class.c
Top
File metadata and controls
Code
Blame
2763 lines (2306 loc) · 69.4 KB
Raw
// SPDX-License-Identifier: GPL-2.0 /* * USB Type-C Connector Class * * Copyright (C) 2017, Intel Corporation * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com> */ #include <linux/module.h> #include <linux/mutex.h> #include <linux/property.h> #include <linux/slab.h> #include <linux/usb/pd_vdo.h> #include <linux/usb/typec_mux.h> #include <linux/usb/typec_retimer.h> #include <linux/usb.h> #include "bus.h" #include "class.h" #include "pd.h" static DEFINE_IDA(typec_index_ida); const struct class typec_class = { .name = "typec", }; /* ------------------------------------------------------------------------- */ /* Common attributes */ static const char * const typec_accessory_modes[] = { [TYPEC_ACCESSORY_NONE] = "none", [TYPEC_ACCESSORY_AUDIO] = "analog_audio", [TYPEC_ACCESSORY_DEBUG] = "debug", }; /* Product types defined in USB PD Specification R3.0 V2.0 */ static const char * const product_type_ufp[8] = { [IDH_PTYPE_NOT_UFP] = "not_ufp", [IDH_PTYPE_HUB] = "hub", [IDH_PTYPE_PERIPH] = "peripheral", [IDH_PTYPE_PSD] = "psd", [IDH_PTYPE_AMA] = "ama", }; static const char * const product_type_dfp[8] = { [IDH_PTYPE_NOT_DFP] = "not_dfp", [IDH_PTYPE_DFP_HUB] = "hub", [IDH_PTYPE_DFP_HOST] = "host", [IDH_PTYPE_DFP_PB] = "power_brick", }; static const char * const product_type_cable[8] = { [IDH_PTYPE_NOT_CABLE] = "not_cable", [IDH_PTYPE_PCABLE] = "passive", [IDH_PTYPE_ACABLE] = "active", [IDH_PTYPE_VPD] = "vpd", }; static struct usb_pd_identity *get_pd_identity(struct device *dev) { if (is_typec_partner(dev)) { struct typec_partner *partner = to_typec_partner(dev); return partner->identity; } else if (is_typec_cable(dev)) { struct typec_cable *cable = to_typec_cable(dev); return cable->identity; } return NULL; } static const char *get_pd_product_type(struct device *dev) { struct typec_port *port = to_typec_port(dev->parent); struct usb_pd_identity *id = get_pd_identity(dev); const char *ptype = NULL; if (is_typec_partner(dev)) { if (!id) return NULL; if (port->data_role == TYPEC_HOST) ptype = product_type_ufp[PD_IDH_PTYPE(id->id_header)]; else ptype = product_type_dfp[PD_IDH_DFP_PTYPE(id->id_header)]; } else if (is_typec_cable(dev)) { if (id) ptype = product_type_cable[PD_IDH_PTYPE(id->id_header)]; else ptype = to_typec_cable(dev)->active ? product_type_cable[IDH_PTYPE_ACABLE] : product_type_cable[IDH_PTYPE_PCABLE]; } return ptype; } static ssize_t id_header_show(struct device *dev, struct device_attribute *attr, char *buf) { struct usb_pd_identity *id = get_pd_identity(dev); return sprintf(buf, "0x%08x\n", id->id_header); } static DEVICE_ATTR_RO(id_header); static ssize_t cert_stat_show(struct device *dev, struct device_attribute *attr, char *buf) { struct usb_pd_identity *id = get_pd_identity(dev); return sprintf(buf, "0x%08x\n", id->cert_stat); } static DEVICE_ATTR_RO(cert_stat); static ssize_t product_show(struct device *dev, struct device_attribute *attr, char *buf) { struct usb_pd_identity *id = get_pd_identity(dev); return sprintf(buf, "0x%08x\n", id->product); } static DEVICE_ATTR_RO(product); static ssize_t product_type_vdo1_show(struct device *dev, struct device_attribute *attr, char *buf) { struct usb_pd_identity *id = get_pd_identity(dev); return sysfs_emit(buf, "0x%08x\n", id->vdo[0]); } static DEVICE_ATTR_RO(product_type_vdo1); static ssize_t product_type_vdo2_show(struct device *dev, struct device_attribute *attr, char *buf) { struct usb_pd_identity *id = get_pd_identity(dev); return sysfs_emit(buf, "0x%08x\n", id->vdo[1]); } static DEVICE_ATTR_RO(product_type_vdo2); static ssize_t product_type_vdo3_show(struct device *dev, struct device_attribute *attr, char *buf) { struct usb_pd_identity *id = get_pd_identity(dev); return sysfs_emit(buf, "0x%08x\n", id->vdo[2]); } static DEVICE_ATTR_RO(product_type_vdo3); static struct attribute *usb_pd_id_attrs[] = { &dev_attr_id_header.attr, &dev_attr_cert_stat.attr, &dev_attr_product.attr, &dev_attr_product_type_vdo1.attr, &dev_attr_product_type_vdo2.attr, &dev_attr_product_type_vdo3.attr, NULL }; static const struct attribute_group usb_pd_id_group = { .name = "identity", .attrs = usb_pd_id_attrs, }; static const struct attribute_group *usb_pd_id_groups[] = { &usb_pd_id_group, NULL, }; static void typec_product_type_notify(struct device *dev) { char *envp[2] = { }; const char *ptype; ptype = get_pd_product_type(dev); if (!ptype) return; sysfs_notify(&dev->kobj, NULL, "type"); envp[0] = kasprintf(GFP_KERNEL, "PRODUCT_TYPE=%s", ptype); if (!envp[0]) return; kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp); kfree(envp[0]); } static void typec_report_identity(struct device *dev) { sysfs_notify(&dev->kobj, "identity", "id_header"); sysfs_notify(&dev->kobj, "identity", "cert_stat"); sysfs_notify(&dev->kobj, "identity", "product"); sysfs_notify(&dev->kobj, "identity", "product_type_vdo1"); sysfs_notify(&dev->kobj, "identity", "product_type_vdo2"); sysfs_notify(&dev->kobj, "identity", "product_type_vdo3"); typec_product_type_notify(dev); } static ssize_t type_show(struct device *dev, struct device_attribute *attr, char *buf) { const char *ptype; ptype = get_pd_product_type(dev); if (!ptype) return 0; return sysfs_emit(buf, "%s\n", ptype); } static DEVICE_ATTR_RO(type); static ssize_t usb_power_delivery_revision_show(struct device *dev, struct device_attribute *attr, char *buf); static DEVICE_ATTR_RO(usb_power_delivery_revision); static const char * const usb_modes[] = { [USB_MODE_NONE] = "none", [USB_MODE_USB2] = "usb2", [USB_MODE_USB3] = "usb3", [USB_MODE_USB4] = "usb4" }; /* ------------------------------------------------------------------------- */ /* 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); } static void typec_altmode_set_partner(struct altmode *altmode) { struct typec_altmode *adev = &altmode->adev; struct typec_device_id id = { adev->svid }; 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; struct typec_altmode *partner_adev; if (!partner) return; adev = &altmode->adev; partner_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(&partner_adev->dev); } /** * typec_altmode_update_active - Report Enter/Exit mode * @adev: Handle to the alternate mode * @active: True when the mode has been entered * * 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. */ void typec_altmode_update_active(struct typec_altmode *adev, bool active) { char dir[6]; if (adev->active == active) return; if (!is_typec_port(adev->dev.parent) && adev->dev.driver) { if (!active) module_put(adev->dev.driver->owner); 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); /** * typec_altmode2port - Alternate Mode to USB Type-C port * @alt: The Alternate Mode * * Returns handle to the port that a cable plug or partner with @alt is * connected to. */ struct typec_port *typec_altmode2port(struct typec_altmode *alt) { if (is_typec_plug(alt->dev.parent)) return to_typec_port(alt->dev.parent->parent->parent); if (is_typec_partner(alt->dev.parent)) return to_typec_port(alt->dev.parent->parent); if (is_typec_port(alt->dev.parent)) return to_typec_port(alt->dev.parent); return NULL; } EXPORT_SYMBOL_GPL(typec_altmode2port); static ssize_t vdo_show(struct device *dev, struct device_attribute *attr, char *buf) { struct typec_altmode *alt = to_typec_altmode(dev); return sprintf(buf, "0x%08x\n", alt->vdo); } static DEVICE_ATTR_RO(vdo); 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 ssize_t active_show(struct device *dev, struct device_attribute *attr, char *buf) { struct typec_altmode *alt = to_typec_altmode(dev); return sprintf(buf, "%s\n", alt->active ? "yes" : "no"); } static ssize_t active_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct typec_altmode *adev = to_typec_altmode(dev); struct altmode *altmode = to_altmode(adev); bool enter; int ret; ret = kstrtobool(buf, &enter); if (ret) return ret; if (adev->active == enter) return size; 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; } static DEVICE_ATTR_RW(active); static ssize_t supported_roles_show(struct device *dev, struct device_attribute *attr, char *buf) { struct altmode *alt = to_altmode(to_typec_altmode(dev)); ssize_t ret; switch (alt->roles) { case TYPEC_PORT_SRC: ret = sprintf(buf, "source\n"); break; case TYPEC_PORT_SNK: ret = sprintf(buf, "sink\n"); break; case TYPEC_PORT_DRP: default: ret = sprintf(buf, "source sink\n"); break; } return ret; } static DEVICE_ATTR_RO(supported_roles); static ssize_t mode_show(struct device *dev, struct device_attribute *attr, char *buf) { struct typec_altmode *adev = to_typec_altmode(dev); return sprintf(buf, "%u\n", adev->mode); } static DEVICE_ATTR_RO(mode); static ssize_t svid_show(struct device *dev, struct device_attribute *attr, char *buf) { struct typec_altmode *adev = to_typec_altmode(dev); return sprintf(buf, "%04x\n", adev->svid); } static DEVICE_ATTR_RO(svid); static struct attribute *typec_altmode_attrs[] = { &dev_attr_active.attr, &dev_attr_mode.attr, &dev_attr_svid.attr, &dev_attr_vdo.attr, NULL }; static umode_t typec_altmode_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) { struct typec_altmode *adev = to_typec_altmode(kobj_to_dev(kobj)); if (attr == &dev_attr_active.attr) if (!adev->ops || !adev->ops->activate) return 0444; return attr->mode; } static const struct attribute_group typec_altmode_group = { .is_visible = typec_altmode_attr_is_visible, .attrs = typec_altmode_attrs, }; static const struct attribute_group *typec_altmode_groups[] = { &typec_altmode_group, NULL }; /** * typec_altmode_set_ops - Set ops for altmode * @adev: Handle to the alternate mode * @ops: Ops for the alternate mode * * After setting ops, attribute visiblity needs to be refreshed if the alternate * mode can be activated. */ void typec_altmode_set_ops(struct typec_altmode *adev, const struct typec_altmode_ops *ops) { adev->ops = ops; sysfs_update_group(&adev->dev.kobj, &typec_altmode_group); } EXPORT_SYMBOL_GPL(typec_altmode_set_ops); 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_alloc(ids, 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_free(ids, id); } static void typec_altmode_release(struct device *dev) { struct altmode *alt = to_altmode(to_typec_altmode(dev)); if (!is_typec_port(dev->parent)) typec_altmode_put_partner(alt); altmode_id_remove(alt->adev.dev.parent, alt->id); put_device(alt->adev.dev.parent); kfree(alt); } const struct device_type typec_altmode_dev_type = { .name = "typec_alternate_mode", .groups = typec_altmode_groups, .release = typec_altmode_release, }; static struct typec_altmode * typec_register_altmode(struct device *parent, const struct typec_altmode_desc *desc) { unsigned int id = altmode_id_get(parent); bool is_port = is_typec_port(parent); struct altmode *alt; int ret; alt = kzalloc(sizeof(*alt), GFP_KERNEL); if (!alt) { altmode_id_remove(parent, id); return ERR_PTR(-ENOMEM); } alt->adev.svid = desc->svid; alt->adev.mode = desc->mode; alt->adev.vdo = desc->vdo; alt->roles = desc->roles; alt->id = id; alt->attrs[0] = &dev_attr_vdo.attr; alt->attrs[1] = &dev_attr_description.attr; alt->attrs[2] = &dev_attr_active.attr; if (is_port) { alt->attrs[3] = &dev_attr_supported_roles.attr; alt->adev.active = true; /* Enabled by default */ } sprintf(alt->group_name, "mode%d", desc->mode); alt->group.name = alt->group_name; alt->group.attrs = alt->attrs; alt->groups[0] = &alt->group; alt->adev.dev.parent = parent; alt->adev.dev.groups = alt->groups; alt->adev.dev.type = &typec_altmode_dev_type; dev_set_name(&alt->adev.dev, "%s.%u", dev_name(parent), id); get_device(alt->adev.dev.parent); /* Link partners and plugs with the ports */ if (!is_port) typec_altmode_set_partner(alt); /* The partners are bind to drivers */ if (is_typec_partner(parent)) alt->adev.dev.bus = &typec_bus; /* Plug alt modes need a class to generate udev events. */ if (is_typec_plug(parent)) alt->adev.dev.class = &typec_class; ret = device_register(&alt->adev.dev); if (ret) { dev_err(parent, "failed to register alternate mode (%d)\n", ret); put_device(&alt->adev.dev); return ERR_PTR(ret); } return &alt->adev; } /** * typec_unregister_altmode - Unregister Alternate Mode * @adev: The alternate mode to be unregistered * * Unregister device created with typec_partner_register_altmode(), * typec_plug_register_altmode() or typec_port_register_altmode(). */ void typec_unregister_altmode(struct typec_altmode *adev) { if (IS_ERR_OR_NULL(adev)) return; typec_retimer_put(to_altmode(adev)->retimer); typec_mux_put(to_altmode(adev)->mux); device_unregister(&adev->dev); } EXPORT_SYMBOL_GPL(typec_unregister_altmode); /* ------------------------------------------------------------------------- */ /* Type-C Partners */ /** * typec_partner_set_usb_mode - Assign active USB Mode for the partner * @partner: USB Type-C partner * @mode: USB Mode (USB2, USB3 or USB4) * * The port drivers can use this function to assign the active USB Mode to * @partner. The USB Mode can change for example due to Data Reset. */ void typec_partner_set_usb_mode(struct typec_partner *partner, enum usb_mode mode) { if (!partner || partner->usb_mode == mode) return; partner->usb_capability |= BIT(mode - 1); partner->usb_mode = mode; sysfs_notify(&partner->dev.kobj, NULL, "usb_mode"); } EXPORT_SYMBOL_GPL(typec_partner_set_usb_mode); static ssize_t usb_mode_show(struct device *dev, struct device_attribute *attr, char *buf) { struct typec_partner *partner = to_typec_partner(dev); int len = 0; int i; for (i = USB_MODE_USB2; i < USB_MODE_USB4 + 1; i++) { if (!(BIT(i - 1) & partner->usb_capability)) continue; if (i == partner->usb_mode) len += sysfs_emit_at(buf, len, "[%s] ", usb_modes[i]); else len += sysfs_emit_at(buf, len, "%s ", usb_modes[i]); } sysfs_emit_at(buf, len - 1, "\n"); return len; } static ssize_t usb_mode_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct typec_partner *partner = to_typec_partner(dev); struct typec_port *port = to_typec_port(dev->parent); int mode; int ret; if (!port->ops || !port->ops->enter_usb_mode) return -EOPNOTSUPP; mode = sysfs_match_string(usb_modes, buf); if (mode < 0) return mode; if (mode == partner->usb_mode) return size; ret = port->ops->enter_usb_mode(port, mode); if (ret) return ret; typec_partner_set_usb_mode(partner, mode); return size; } static DEVICE_ATTR_RW(usb_mode); static ssize_t accessory_mode_show(struct device *dev, struct device_attribute *attr, char *buf) { struct typec_partner *p = to_typec_partner(dev); return sprintf(buf, "%s\n", typec_accessory_modes[p->accessory]); } static DEVICE_ATTR_RO(accessory_mode); static ssize_t supports_usb_power_delivery_show(struct device *dev, struct device_attribute *attr, char *buf) { struct typec_partner *p = to_typec_partner(dev); return sprintf(buf, "%s\n", p->usb_pd ? "yes" : "no"); } static DEVICE_ATTR_RO(supports_usb_power_delivery); static ssize_t number_of_alternate_modes_show(struct device *dev, struct device_attribute *attr, char *buf) { struct typec_partner *partner; struct typec_plug *plug; int num_altmodes; if (is_typec_partner(dev)) { partner = to_typec_partner(dev); num_altmodes = partner->num_altmodes; } else if (is_typec_plug(dev)) { plug = to_typec_plug(dev); num_altmodes = plug->num_altmodes; } else { return 0; } return sysfs_emit(buf, "%d\n", num_altmodes); } static DEVICE_ATTR_RO(number_of_alternate_modes); static struct attribute *typec_partner_attrs[] = { &dev_attr_accessory_mode.attr, &dev_attr_supports_usb_power_delivery.attr, &dev_attr_number_of_alternate_modes.attr, &dev_attr_type.attr, &dev_attr_usb_mode.attr, &dev_attr_usb_power_delivery_revision.attr, NULL }; static umode_t typec_partner_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) { struct typec_partner *partner = to_typec_partner(kobj_to_dev(kobj)); struct typec_port *port = to_typec_port(partner->dev.parent); if (attr == &dev_attr_usb_mode.attr) { if (!partner->usb_capability) return 0; if (!port->ops || !port->ops->enter_usb_mode) return 0444; } if (attr == &dev_attr_number_of_alternate_modes.attr) { if (partner->num_altmodes < 0) return 0; } if (attr == &dev_attr_type.attr) if (!get_pd_product_type(kobj_to_dev(kobj))) return 0; return attr->mode; } static const struct attribute_group typec_partner_group = { .is_visible = typec_partner_attr_is_visible, .attrs = typec_partner_attrs }; static const struct attribute_group *typec_partner_groups[] = { &typec_partner_group, NULL }; static void typec_partner_release(struct device *dev) { struct typec_partner *partner = to_typec_partner(dev); ida_destroy(&partner->mode_ids); kfree(partner); } const struct device_type typec_partner_dev_type = { .name = "typec_partner", .groups = typec_partner_groups, .release = typec_partner_release, }; static void typec_partner_link_device(struct typec_partner *partner, struct device *dev) { int ret; ret = sysfs_create_link(&dev->kobj, &partner->dev.kobj, "typec"); if (ret) return; ret = sysfs_create_link(&partner->dev.kobj, &dev->kobj, dev_name(dev)); if (ret) { sysfs_remove_link(&dev->kobj, "typec"); return; } if (partner->attach) partner->attach(partner, dev); } static void typec_partner_unlink_device(struct typec_partner *partner, struct device *dev) { sysfs_remove_link(&partner->dev.kobj, dev_name(dev)); sysfs_remove_link(&dev->kobj, "typec"); if (partner->deattach) partner->deattach(partner, dev); } /** * typec_partner_set_identity - Report result from Discover Identity command * @partner: The partner updated identity values * * This routine is used to report that the result of Discover Identity USB power * delivery command has become available. */ int typec_partner_set_identity(struct typec_partner *partner) { u8 usb_capability = partner->usb_capability; struct device *dev = &partner->dev; struct usb_pd_identity *id; id = get_pd_identity(dev); if (!id) return -EINVAL; if (to_typec_port(dev->parent)->data_role == TYPEC_HOST) { u32 devcap = PD_VDO_UFP_DEVCAP(id->vdo[0]); if (devcap & (DEV_USB2_CAPABLE | DEV_USB2_BILLBOARD)) usb_capability |= USB_CAPABILITY_USB2; if (devcap & DEV_USB3_CAPABLE) usb_capability |= USB_CAPABILITY_USB3; if (devcap & DEV_USB4_CAPABLE) usb_capability |= USB_CAPABILITY_USB4; } else { usb_capability = PD_VDO_DFP_HOSTCAP(id->vdo[0]); } if (partner->usb_capability != usb_capability) { partner->usb_capability = usb_capability; sysfs_notify(&dev->kobj, NULL, "usb_mode"); } typec_report_identity(dev); return 0; } EXPORT_SYMBOL_GPL(typec_partner_set_identity); /** * typec_partner_set_pd_revision - Set the PD revision supported by the partner * @partner: The partner to be updated. * @pd_revision: USB Power Delivery Specification Revision supported by partner * * This routine is used to report that the PD revision of the port partner has * become available. */ void typec_partner_set_pd_revision(struct typec_partner *partner, u16 pd_revision) { if (partner->pd_revision == pd_revision) return; partner->pd_revision = pd_revision; sysfs_notify(&partner->dev.kobj, NULL, "usb_power_delivery_revision"); if (pd_revision != 0 && !partner->usb_pd) { partner->usb_pd = 1; sysfs_notify(&partner->dev.kobj, NULL, "supports_usb_power_delivery"); } kobject_uevent(&partner->dev.kobj, KOBJ_CHANGE); } EXPORT_SYMBOL_GPL(typec_partner_set_pd_revision); /** * typec_partner_set_usb_power_delivery - Declare USB Power Delivery Contract. * @partner: The partner device. * @pd: The USB PD instance. * * This routine can be used to declare USB Power Delivery Contract with @partner * by linking @partner to @pd which contains the objects that were used during the * negotiation of the contract. * * If @pd is NULL, the link is removed and the contract with @partner has ended. */ int typec_partner_set_usb_power_delivery(struct typec_partner *partner, struct usb_power_delivery *pd) { int ret; if (IS_ERR_OR_NULL(partner) || partner->pd == pd) return 0; if (pd) { ret = usb_power_delivery_link_device(pd, &partner->dev); if (ret) return ret; } else { usb_power_delivery_unlink_device(partner->pd, &partner->dev); } partner->pd = pd; return 0; } EXPORT_SYMBOL_GPL(typec_partner_set_usb_power_delivery); /** * typec_partner_set_num_altmodes - Set the number of available partner altmodes * @partner: The partner to be updated. * @num_altmodes: The number of altmodes we want to specify as available. * * This routine is used to report the number of alternate modes supported by the * partner. This value is *not* enforced in alternate mode registration routines. * * @partner.num_altmodes is set to -1 on partner registration, denoting that * a valid value has not been set for it yet. * * Returns 0 on success or negative error number on failure. */ int typec_partner_set_num_altmodes(struct typec_partner *partner, int num_altmodes) { int ret; if (num_altmodes < 0) return -EINVAL; partner->num_altmodes = num_altmodes; ret = sysfs_update_group(&partner->dev.kobj, &typec_partner_group); if (ret < 0) return ret; sysfs_notify(&partner->dev.kobj, NULL, "number_of_alternate_modes"); kobject_uevent(&partner->dev.kobj, KOBJ_CHANGE); return 0; } EXPORT_SYMBOL_GPL(typec_partner_set_num_altmodes); /** * typec_partner_register_altmode - Register USB Type-C Partner Alternate Mode * @partner: USB Type-C Partner that supports the alternate mode * @desc: Description of the alternate mode * * This routine is used to register each alternate mode individually that * @partner has listed in response to Discover SVIDs command. The modes for a * SVID listed in response to Discover Modes command need to be listed in an * array in @desc. * * Returns handle to the alternate mode on success or ERR_PTR on failure. */ struct typec_altmode * typec_partner_register_altmode(struct typec_partner *partner, const struct typec_altmode_desc *desc) { return typec_register_altmode(&partner->dev, desc); } EXPORT_SYMBOL_GPL(typec_partner_register_altmode); /** * typec_partner_set_svdm_version - Set negotiated Structured VDM (SVDM) Version * @partner: USB Type-C Partner that supports SVDM * @svdm_version: Negotiated SVDM Version * * This routine is used to save the negotiated SVDM Version. */ void typec_partner_set_svdm_version(struct typec_partner *partner, enum usb_pd_svdm_ver svdm_version) { partner->svdm_version = svdm_version; } EXPORT_SYMBOL_GPL(typec_partner_set_svdm_version); /** * typec_partner_usb_power_delivery_register - Register Type-C partner USB Power Delivery Support * @partner: Type-C partner device. * @desc: Description of the USB PD contract. * * This routine is a wrapper around usb_power_delivery_register(). It registers * USB Power Delivery Capabilities for a Type-C partner device. Specifically, * it sets the Type-C partner device as a parent for the resulting USB Power Delivery object. * * Returns handle to struct usb_power_delivery or ERR_PTR. */ struct usb_power_delivery * typec_partner_usb_power_delivery_register(struct typec_partner *partner, struct usb_power_delivery_desc *desc) { return usb_power_delivery_register(&partner->dev, desc); } EXPORT_SYMBOL_GPL(typec_partner_usb_power_delivery_register); /** * typec_register_partner - Register a USB Type-C Partner * @port: The USB Type-C Port the partner is connected to * @desc: Description of the partner * * Registers a device for USB Type-C Partner described in @desc. * * Returns handle to the partner on success or ERR_PTR on failure. */ struct typec_partner *typec_register_partner(struct typec_port *port, struct typec_partner_desc *desc) { struct typec_partner *partner; int ret; partner = kzalloc(sizeof(*partner), GFP_KERNEL); if (!partner) return ERR_PTR(-ENOMEM); ida_init(&partner->mode_ids); partner->usb_pd = desc->usb_pd; partner->accessory = desc->accessory; partner->num_altmodes = -1; partner->usb_capability = desc->usb_capability; partner->pd_revision = desc->pd_revision; partner->svdm_version = port->cap->svdm_version; partner->attach = desc->attach; partner->deattach = desc->deattach; if (desc->identity) { /* * Creating directory for the identity only if the driver is * able to provide data to it. */ partner->dev.groups = usb_pd_id_groups; partner->identity = desc->identity; } partner->dev.class = &typec_class; partner->dev.parent = &port->dev; partner->dev.type = &typec_partner_dev_type; dev_set_name(&partner->dev, "%s-partner", dev_name(&port->dev)); if (port->usb2_dev) { partner->usb_capability |= USB_CAPABILITY_USB2; partner->usb_mode = USB_MODE_USB2; } if (port->usb3_dev) { partner->usb_capability |= USB_CAPABILITY_USB2 | USB_CAPABILITY_USB3; partner->usb_mode = USB_MODE_USB3; } ret = device_register(&partner->dev); if (ret) { dev_err(&port->dev, "failed to register partner (%d)\n", ret); put_device(&partner->dev); return ERR_PTR(ret); } if (port->usb2_dev) typec_partner_link_device(partner, port->usb2_dev); if (port->usb3_dev) typec_partner_link_device(partner, port->usb3_dev); return partner; } EXPORT_SYMBOL_GPL(typec_register_partner); /** * typec_unregister_partner - Unregister a USB Type-C Partner * @partner: The partner to be unregistered * * Unregister device created with typec_register_partner(). */ void typec_unregister_partner(struct typec_partner *partner) { struct typec_port *port; if (IS_ERR_OR_NULL(partner)) return; port = to_typec_port(partner->dev.parent); if (port->usb2_dev) typec_partner_unlink_device(partner, port->usb2_dev); if (port->usb3_dev) typec_partner_unlink_device(partner, port->usb3_dev); device_unregister(&partner->dev); } EXPORT_SYMBOL_GPL(typec_unregister_partner); /* ------------------------------------------------------------------------- */ /* Type-C Cable Plugs */ static void typec_plug_release(struct device *dev) { struct typec_plug *plug = to_typec_plug(dev); ida_destroy(&plug->mode_ids); kfree(plug); } static struct attribute *typec_plug_attrs[] = { &dev_attr_number_of_alternate_modes.attr, NULL }; static umode_t typec_plug_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) { struct typec_plug *plug = to_typec_plug(kobj_to_dev(kobj)); if (attr == &dev_attr_number_of_alternate_modes.attr) { if (plug->num_altmodes < 0) return 0; } return attr->mode; } static const struct attribute_group typec_plug_group = { .is_visible = typec_plug_attr_is_visible, .attrs = typec_plug_attrs }; static const struct attribute_group *typec_plug_groups[] = { &typec_plug_group, NULL }; const struct device_type typec_plug_dev_type = { .name = "typec_plug", .groups = typec_plug_groups, .release = typec_plug_release, }; /** * typec_plug_set_num_altmodes - Set the number of available plug altmodes * @plug: The plug to be updated. * @num_altmodes: The number of altmodes we want to specify as available. * * This routine is used to report the number of alternate modes supported by the * plug. This value is *not* enforced in alternate mode registration routines. * * @plug.num_altmodes is set to -1 on plug registration, denoting that * a valid value has not been set for it yet. * * Returns 0 on success or negative error number on failure. */ int typec_plug_set_num_altmodes(struct typec_plug *plug, int num_altmodes) { int ret; if (num_altmodes < 0) return -EINVAL; plug->num_altmodes = num_altmodes; ret = sysfs_update_group(&plug->dev.kobj, &typec_plug_group); if (ret < 0) return ret; sysfs_notify(&plug->dev.kobj, NULL, "number_of_alternate_modes"); kobject_uevent(&plug->dev.kobj, KOBJ_CHANGE); return 0; } EXPORT_SYMBOL_GPL(typec_plug_set_num_altmodes); /** * typec_plug_register_altmode - Register USB Type-C Cable Plug Alternate Mode * @plug: USB Type-C Cable Plug that supports the alternate mode * @desc: Description of the alternate mode * * This routine is used to register each alternate mode individually that @plug * has listed in response to Discover SVIDs command. The modes for a SVID that * the plug lists in response to Discover Modes command need to be listed in an * array in @desc. * * Returns handle to the alternate mode on success or ERR_PTR on failure. */ struct typec_altmode * typec_plug_register_altmode(struct typec_plug *plug, const struct typec_altmode_desc *desc) { return typec_register_altmode(&plug->dev, desc); } EXPORT_SYMBOL_GPL(typec_plug_register_altmode); /** * typec_register_plug - Register a USB Type-C Cable Plug * @cable: USB Type-C Cable with the plug * @desc: Description of the cable plug * * Registers a device for USB Type-C Cable Plug described in @desc. A USB Type-C * Cable Plug represents a plug with electronics in it that can response to USB * Power Delivery SOP Prime or SOP Double Prime packages. * * Returns handle to the cable plug on success or ERR_PTR on failure. */ struct typec_plug *typec_register_plug(struct typec_cable *cable, struct typec_plug_desc *desc) { struct typec_plug *plug; char name[8]; int ret; plug = kzalloc(sizeof(*plug), GFP_KERNEL); if (!plug) return ERR_PTR(-ENOMEM); sprintf(name, "plug%d", desc->index); ida_init(&plug->mode_ids); plug->num_altmodes = -1; plug->index = desc->index; plug->dev.class = &typec_class; plug->dev.parent = &cable->dev; plug->dev.type = &typec_plug_dev_type; dev_set_name(&plug->dev, "%s-%s", dev_name(cable->dev.parent), name); ret = device_register(&plug->dev); if (ret) { dev_err(&cable->dev, "failed to register plug (%d)\n", ret); put_device(&plug->dev); return ERR_PTR(ret); } return plug; } EXPORT_SYMBOL_GPL(typec_register_plug); /** * typec_unregister_plug - Unregister a USB Type-C Cable Plug * @plug: The cable plug to be unregistered * * Unregister device created with typec_register_plug(). */ void typec_unregister_plug(struct typec_plug *plug) { if (!IS_ERR_OR_NULL(plug)) device_unregister(&plug->dev); } EXPORT_SYMBOL_GPL(typec_unregister_plug); /* Type-C Cables */ static const char * const typec_plug_types[] = { [USB_PLUG_NONE] = "unknown", [USB_PLUG_TYPE_A] = "type-a", [USB_PLUG_TYPE_B] = "type-b", [USB_PLUG_TYPE_C] = "type-c", [USB_PLUG_CAPTIVE] = "captive", }; static ssize_t plug_type_show(struct device *dev, struct device_attribute *attr, char *buf) { struct typec_cable *cable = to_typec_cable(dev); return sprintf(buf, "%s\n", typec_plug_types[cable->type]); } static DEVICE_ATTR_RO(plug_type); static struct attribute *typec_cable_attrs[] = { &dev_attr_type.attr, &dev_attr_plug_type.attr, &dev_attr_usb_power_delivery_revision.attr, NULL }; ATTRIBUTE_GROUPS(typec_cable); static void typec_cable_release(struct device *dev) { struct typec_cable *cable = to_typec_cable(dev); kfree(cable); } const struct device_type typec_cable_dev_type = { .name = "typec_cable", .groups = typec_cable_groups, .release = typec_cable_release, }; static int cable_match(struct device *dev, void *data) { return is_typec_cable(dev); } /** * typec_cable_get - Get a reference to the USB Type-C cable * @port: The USB Type-C Port the cable is connected to * * The caller must decrement the reference count with typec_cable_put() after * use. */ struct typec_cable *typec_cable_get(struct typec_port *port) { struct device *dev; dev = device_find_child(&port->dev, NULL, cable_match); if (!dev) return NULL; return to_typec_cable(dev); } EXPORT_SYMBOL_GPL(typec_cable_get); /** * typec_cable_put - Decrement the reference count on USB Type-C cable * @cable: The USB Type-C cable */ void typec_cable_put(struct typec_cable *cable) { put_device(&cable->dev); } EXPORT_SYMBOL_GPL(typec_cable_put); /** * typec_cable_is_active - Check is the USB Type-C cable active or passive * @cable: The USB Type-C Cable * * Return 1 if the cable is active or 0 if it's passive. */ int typec_cable_is_active(struct typec_cable *cable) { return cable->active; } EXPORT_SYMBOL_GPL(typec_cable_is_active); /** * typec_cable_set_identity - Report result from Discover Identity command * @cable: The cable updated identity values * * This routine is used to report that the result of Discover Identity USB power * delivery command has become available. */ int typec_cable_set_identity(struct typec_cable *cable) { if (!cable->identity) return -EINVAL; typec_report_identity(&cable->dev); return 0; } EXPORT_SYMBOL_GPL(typec_cable_set_identity); /** * typec_register_cable - Register a USB Type-C Cable * @port: The USB Type-C Port the cable is connected to * @desc: Description of the cable * * Registers a device for USB Type-C Cable described in @desc. The cable will be * parent for the optional cable plug devises. * * Returns handle to the cable on success or ERR_PTR on failure. */ struct typec_cable *typec_register_cable(struct typec_port *port, struct typec_cable_desc *desc) { struct typec_cable *cable; int ret; cable = kzalloc(sizeof(*cable), GFP_KERNEL); if (!cable) return ERR_PTR(-ENOMEM); cable->type = desc->type; cable->active = desc->active; cable->pd_revision = desc->pd_revision; if (desc->identity) { /* * Creating directory for the identity only if the driver is * able to provide data to it. */ cable->dev.groups = usb_pd_id_groups; cable->identity = desc->identity; } cable->dev.class = &typec_class; cable->dev.parent = &port->dev; cable->dev.type = &typec_cable_dev_type; dev_set_name(&cable->dev, "%s-cable", dev_name(&port->dev)); ret = device_register(&cable->dev); if (ret) { dev_err(&port->dev, "failed to register cable (%d)\n", ret); put_device(&cable->dev); return ERR_PTR(ret); } return cable; } EXPORT_SYMBOL_GPL(typec_register_cable); /** * typec_unregister_cable - Unregister a USB Type-C Cable * @cable: The cable to be unregistered * * Unregister device created with typec_register_cable(). */ void typec_unregister_cable(struct typec_cable *cable) { if (!IS_ERR_OR_NULL(cable)) device_unregister(&cable->dev); } EXPORT_SYMBOL_GPL(typec_unregister_cable); /* ------------------------------------------------------------------------- */ /* USB Type-C ports */ /** * typec_port_set_usb_mode - Set the operational USB mode for the port * @port: USB Type-C port * @mode: USB Mode (USB2, USB3 or USB4) * * @mode will be used with the next Enter_USB message. Existing connections are * not affected. */ void typec_port_set_usb_mode(struct typec_port *port, enum usb_mode mode) { port->usb_mode = mode; } EXPORT_SYMBOL_GPL(typec_port_set_usb_mode); static ssize_t usb_capability_show(struct device *dev, struct device_attribute *attr, char *buf) { struct typec_port *port = to_typec_port(dev); int len = 0; int i; for (i = USB_MODE_USB2; i < USB_MODE_USB4 + 1; i++) { if (!(BIT(i - 1) & port->cap->usb_capability)) continue; if (i == port->usb_mode) len += sysfs_emit_at(buf, len, "[%s] ", usb_modes[i]); else len += sysfs_emit_at(buf, len, "%s ", usb_modes[i]); } sysfs_emit_at(buf, len - 1, "\n"); return len; } static ssize_t usb_capability_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct typec_port *port = to_typec_port(dev); int ret = 0; int mode; if (!port->ops || !port->ops->default_usb_mode_set) return -EOPNOTSUPP; mode = sysfs_match_string(usb_modes, buf); if (mode < 0) return mode; ret = port->ops->default_usb_mode_set(port, mode); if (ret) return ret; port->usb_mode = mode; return size; } static DEVICE_ATTR_RW(usb_capability); /** * typec_port_set_usb_power_delivery - Assign USB PD for port. * @port: USB Type-C port. * @pd: USB PD instance. * * This routine can be used to set the USB Power Delivery Capabilities for @port * that it will advertise to the partner. * * If @pd is NULL, the assignment is removed. */ int typec_port_set_usb_power_delivery(struct typec_port *port, struct usb_power_delivery *pd) { int ret; if (IS_ERR_OR_NULL(port) || port->pd == pd) return 0; if (pd) { ret = usb_power_delivery_link_device(pd, &port->dev); if (ret) return ret; } else { usb_power_delivery_unlink_device(port->pd, &port->dev); } port->pd = pd; return 0; } EXPORT_SYMBOL_GPL(typec_port_set_usb_power_delivery); static ssize_t select_usb_power_delivery_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct typec_port *port = to_typec_port(dev); struct usb_power_delivery *pd; int ret; if (!port->ops || !port->ops->pd_set) return -EOPNOTSUPP; pd = usb_power_delivery_find(buf); if (!pd) return -EINVAL; ret = port->ops->pd_set(port, pd); if (ret) return ret; return size; } static ssize_t select_usb_power_delivery_show(struct device *dev, struct device_attribute *attr, char *buf) { struct typec_port *port = to_typec_port(dev); struct usb_power_delivery **pds; int i, ret = 0; if (!port->ops || !port->ops->pd_get) return -EOPNOTSUPP; pds = port->ops->pd_get(port); if (!pds) return 0; for (i = 0; pds[i]; i++) { if (pds[i] == port->pd) ret += sysfs_emit_at(buf, ret, "[%s] ", dev_name(&pds[i]->dev)); else ret += sysfs_emit_at(buf, ret, "%s ", dev_name(&pds[i]->dev)); } buf[ret - 1] = '\n'; return ret; } static DEVICE_ATTR_RW(select_usb_power_delivery); static struct attribute *port_attrs[] = { &dev_attr_select_usb_power_delivery.attr, NULL }; static umode_t port_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) { struct typec_port *port = to_typec_port(kobj_to_dev(kobj)); if (!port->pd || !port->ops || !port->ops->pd_get) return 0; if (!port->ops->pd_set) return 0444; return attr->mode; } static const struct attribute_group pd_group = { .is_visible = port_attr_is_visible, .attrs = port_attrs, }; static const char * const typec_orientations[] = { [TYPEC_ORIENTATION_NONE] = "unknown", [TYPEC_ORIENTATION_NORMAL] = "normal", [TYPEC_ORIENTATION_REVERSE] = "reverse", }; static const char * const typec_roles[] = { [TYPEC_SINK] = "sink", [TYPEC_SOURCE] = "source", }; static const char * const typec_data_roles[] = { [TYPEC_DEVICE] = "device", [TYPEC_HOST] = "host", }; static const char * const typec_port_power_roles[] = { [TYPEC_PORT_SRC] = "source", [TYPEC_PORT_SNK] = "sink", [TYPEC_PORT_DRP] = "dual", }; static const char * const typec_port_data_roles[] = { [TYPEC_PORT_DFP] = "host", [TYPEC_PORT_UFP] = "device", [TYPEC_PORT_DRD] = "dual", }; static const char * const typec_port_types_drp[] = { [TYPEC_PORT_SRC] = "dual [source] sink", [TYPEC_PORT_SNK] = "dual source [sink]", [TYPEC_PORT_DRP] = "[dual] source sink", }; static ssize_t preferred_role_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct typec_port *port = to_typec_port(dev); int role; int ret; if (port->cap->type != TYPEC_PORT_DRP) { dev_dbg(dev, "Preferred role only supported with DRP ports\n"); return -EOPNOTSUPP; } if (!port->ops || !port->ops->try_role) { dev_dbg(dev, "Setting preferred role not supported\n"); return -EOPNOTSUPP; } role = sysfs_match_string(typec_roles, buf); if (role < 0) { if (sysfs_streq(buf, "none")) role = TYPEC_NO_PREFERRED_ROLE; else return -EINVAL; } ret = port->ops->try_role(port, role); if (ret) return ret; port->prefer_role = role; return size; } static ssize_t preferred_role_show(struct device *dev, struct device_attribute *attr, char *buf) { struct typec_port *port = to_typec_port(dev); if (port->cap->type != TYPEC_PORT_DRP) return 0; if (port->prefer_role < 0) return 0; return sprintf(buf, "%s\n", typec_roles[port->prefer_role]); } static DEVICE_ATTR_RW(preferred_role); static ssize_t data_role_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct typec_port *port = to_typec_port(dev); int ret; if (!port->ops || !port->ops->dr_set) { dev_dbg(dev, "data role swapping not supported\n"); return -EOPNOTSUPP; } ret = sysfs_match_string(typec_data_roles, buf); if (ret < 0) return ret; mutex_lock(&port->port_type_lock); if (port->cap->data != TYPEC_PORT_DRD) { ret = -EOPNOTSUPP; goto unlock_and_ret; } ret = port->ops->dr_set(port, ret); if (ret) goto unlock_and_ret; ret = size; unlock_and_ret: mutex_unlock(&port->port_type_lock); return ret; } static ssize_t data_role_show(struct device *dev, struct device_attribute *attr, char *buf) { struct typec_port *port = to_typec_port(dev); if (port->cap->data == TYPEC_PORT_DRD) return sprintf(buf, "%s\n", port->data_role == TYPEC_HOST ? "[host] device" : "host [device]"); return sprintf(buf, "[%s]\n", typec_data_roles[port->data_role]); } static DEVICE_ATTR_RW(data_role); static ssize_t power_role_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct typec_port *port = to_typec_port(dev); int ret; if (!port->ops || !port->ops->pr_set) { dev_dbg(dev, "power role swapping not supported\n"); return -EOPNOTSUPP; } if (port->pwr_opmode != TYPEC_PWR_MODE_PD) { dev_dbg(dev, "partner unable to swap power role\n"); return -EIO; } ret = sysfs_match_string(typec_roles, buf); if (ret < 0) return ret; mutex_lock(&port->port_type_lock); if (port->port_type != TYPEC_PORT_DRP) { dev_dbg(dev, "port type fixed at \"%s\"", typec_port_power_roles[port->port_type]); ret = -EOPNOTSUPP; goto unlock_and_ret; } ret = port->ops->pr_set(port, ret); if (ret) goto unlock_and_ret; ret = size; unlock_and_ret: mutex_unlock(&port->port_type_lock); return ret; } static ssize_t power_role_show(struct device *dev, struct device_attribute *attr, char *buf) { struct typec_port *port = to_typec_port(dev); if (port->cap->type == TYPEC_PORT_DRP) return sprintf(buf, "%s\n", port->pwr_role == TYPEC_SOURCE ? "[source] sink" : "source [sink]"); return sprintf(buf, "[%s]\n", typec_roles[port->pwr_role]); } static DEVICE_ATTR_RW(power_role); static ssize_t port_type_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct typec_port *port = to_typec_port(dev); int ret; enum typec_port_type type; if (port->cap->type != TYPEC_PORT_DRP || !port->ops || !port->ops->port_type_set) { dev_dbg(dev, "changing port type not supported\n"); return -EOPNOTSUPP; } ret = sysfs_match_string(typec_port_power_roles, buf); if (ret < 0) return ret; type = ret; mutex_lock(&port->port_type_lock); if (port->port_type == type) { ret = size; goto unlock_and_ret; } ret = port->ops->port_type_set(port, type); if (ret) goto unlock_and_ret; port->port_type = type; ret = size; unlock_and_ret: mutex_unlock(&port->port_type_lock); return ret; } static ssize_t port_type_show(struct device *dev, struct device_attribute *attr, char *buf) { struct typec_port *port = to_typec_port(dev); if (port->cap->type == TYPEC_PORT_DRP) return sprintf(buf, "%s\n", typec_port_types_drp[port->port_type]); return sprintf(buf, "[%s]\n", typec_port_power_roles[port->cap->type]); } static DEVICE_ATTR_RW(port_type); static const char * const typec_pwr_opmodes[] = { [TYPEC_PWR_MODE_USB] = "default", [TYPEC_PWR_MODE_1_5A] = "1.5A", [TYPEC_PWR_MODE_3_0A] = "3.0A", [TYPEC_PWR_MODE_PD] = "usb_power_delivery", }; static ssize_t power_operation_mode_show(struct device *dev, struct device_attribute *attr, char *buf) { struct typec_port *port = to_typec_port(dev); return sprintf(buf, "%s\n", typec_pwr_opmodes[port->pwr_opmode]); } static DEVICE_ATTR_RO(power_operation_mode); static ssize_t vconn_source_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct typec_port *port = to_typec_port(dev); bool source; int ret; if (!port->cap->pd_revision) { dev_dbg(dev, "VCONN swap depends on USB Power Delivery\n"); return -EOPNOTSUPP; } if (!port->ops || !port->ops->vconn_set) { dev_dbg(dev, "VCONN swapping not supported\n"); return -EOPNOTSUPP; } ret = kstrtobool(buf, &source); if (ret) return ret; ret = port->ops->vconn_set(port, (enum typec_role)source); if (ret) return ret; return size; } static ssize_t vconn_source_show(struct device *dev, struct device_attribute *attr, char *buf) { struct typec_port *port = to_typec_port(dev); return sprintf(buf, "%s\n", port->vconn_role == TYPEC_SOURCE ? "yes" : "no"); } static DEVICE_ATTR_RW(vconn_source); static ssize_t supported_accessory_modes_show(struct device *dev, struct device_attribute *attr, char *buf) { struct typec_port *port = to_typec_port(dev); ssize_t ret = 0; int i; for (i = 0; i < ARRAY_SIZE(port->cap->accessory); i++) { if (port->cap->accessory[i]) ret += sprintf(buf + ret, "%s ", typec_accessory_modes[port->cap->accessory[i]]); } if (!ret) return sprintf(buf, "none\n"); buf[ret - 1] = '\n'; return ret; } static DEVICE_ATTR_RO(supported_accessory_modes); static ssize_t usb_typec_revision_show(struct device *dev, struct device_attribute *attr, char *buf) { struct typec_port *port = to_typec_port(dev); u16 rev = port->cap->revision; return sprintf(buf, "%d.%d\n", (rev >> 8) & 0xff, (rev >> 4) & 0xf); } static DEVICE_ATTR_RO(usb_typec_revision); static ssize_t usb_power_delivery_revision_show(struct device *dev, struct device_attribute *attr, char *buf) { u16 rev = 0; if (is_typec_partner(dev)) { struct typec_partner *partner = to_typec_partner(dev); rev = partner->pd_revision; } else if (is_typec_cable(dev)) { struct typec_cable *cable = to_typec_cable(dev); rev = cable->pd_revision; } else if (is_typec_port(dev)) { struct typec_port *p = to_typec_port(dev); rev = p->cap->pd_revision; } return sysfs_emit(buf, "%d.%d\n", (rev >> 8) & 0xff, (rev >> 4) & 0xf); } static ssize_t orientation_show(struct device *dev, struct device_attribute *attr, char *buf) { struct typec_port *port = to_typec_port(dev); return sprintf(buf, "%s\n", typec_orientations[port->orientation]); } static DEVICE_ATTR_RO(orientation); static struct attribute *typec_attrs[] = { &dev_attr_data_role.attr, &dev_attr_power_operation_mode.attr, &dev_attr_power_role.attr, &dev_attr_preferred_role.attr, &dev_attr_supported_accessory_modes.attr, &dev_attr_usb_power_delivery_revision.attr, &dev_attr_usb_typec_revision.attr, &dev_attr_vconn_source.attr, &dev_attr_port_type.attr, &dev_attr_orientation.attr, &dev_attr_usb_capability.attr, NULL, }; static umode_t typec_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) { struct typec_port *port = to_typec_port(kobj_to_dev(kobj)); if (attr == &dev_attr_data_role.attr) { if (port->cap->data != TYPEC_PORT_DRD || !port->ops || !port->ops->dr_set) return 0444; } else if (attr == &dev_attr_power_role.attr) { if (port->cap->type != TYPEC_PORT_DRP || !port->ops || !port->ops->pr_set) return 0444; } else if (attr == &dev_attr_vconn_source.attr) { if (!port->cap->pd_revision || !port->ops || !port->ops->vconn_set) return 0444; } else if (attr == &dev_attr_preferred_role.attr) { if (port->cap->type != TYPEC_PORT_DRP || !port->ops || !port->ops->try_role) return 0444; } else if (attr == &dev_attr_port_type.attr) { if (!port->ops || !port->ops->port_type_set) return 0; if (port->cap->type != TYPEC_PORT_DRP) return 0444; } else if (attr == &dev_attr_orientation.attr) { if (port->cap->orientation_aware) return 0444; return 0; } else if (attr == &dev_attr_usb_capability.attr) { if (!port->cap->usb_capability) return 0; if (!port->ops || !port->ops->default_usb_mode_set) return 0444; } return attr->mode; } static const struct attribute_group typec_group = { .is_visible = typec_attr_is_visible, .attrs = typec_attrs, }; static const struct attribute_group *typec_groups[] = { &typec_group, &pd_group, NULL }; static int typec_uevent(const struct device *dev, struct kobj_uevent_env *env) { int ret; ret = add_uevent_var(env, "TYPEC_PORT=%s", dev_name(dev)); if (ret) dev_err(dev, "failed to add uevent TYPEC_PORT\n"); return ret; } static void typec_release(struct device *dev) { struct typec_port *port = to_typec_port(dev); ida_free(&typec_index_ida, port->id); ida_destroy(&port->mode_ids); typec_switch_put(port->sw); typec_mux_put(port->mux); typec_retimer_put(port->retimer); kfree(port->cap); kfree(port); } const struct device_type typec_port_dev_type = { .name = "typec_port", .groups = typec_groups, .uevent = typec_uevent, .release = typec_release, }; /* --------------------------------------- */ /* Driver callbacks to report role updates */ static int partner_match(struct device *dev, void *data) { return is_typec_partner(dev); } static struct typec_partner *typec_get_partner(struct typec_port *port) { struct device *dev; dev = device_find_child(&port->dev, NULL, partner_match); if (!dev) return NULL; return to_typec_partner(dev); } static void typec_partner_attach(struct typec_connector *con, struct device *dev) { struct typec_port *port = container_of(con, struct typec_port, con); struct typec_partner *partner = typec_get_partner(port); struct usb_device *udev = to_usb_device(dev); enum usb_mode usb_mode; if (udev->speed < USB_SPEED_SUPER) { usb_mode = USB_MODE_USB2; port->usb2_dev = dev; } else { usb_mode = USB_MODE_USB3; port->usb3_dev = dev; } if (partner) { typec_partner_set_usb_mode(partner, usb_mode); typec_partner_link_device(partner, dev); put_device(&partner->dev); } } static void typec_partner_deattach(struct typec_connector *con, struct device *dev) { struct typec_port *port = container_of(con, struct typec_port, con); struct typec_partner *partner = typec_get_partner(port); if (partner) { typec_partner_unlink_device(partner, dev); put_device(&partner->dev); } if (port->usb2_dev == dev) port->usb2_dev = NULL; else if (port->usb3_dev == dev) port->usb3_dev = NULL; } /** * typec_set_data_role - Report data 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 data role changes. */ void typec_set_data_role(struct typec_port *port, enum typec_data_role role) { struct typec_partner *partner; if (port->data_role == role) return; port->data_role = role; sysfs_notify(&port->dev.kobj, NULL, "data_role"); kobject_uevent(&port->dev.kobj, KOBJ_CHANGE); partner = typec_get_partner(port); if (!partner) return; if (partner->identity) typec_product_type_notify(&partner->dev); put_device(&partner->dev); } EXPORT_SYMBOL_GPL(typec_set_data_role); /** * 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) { if (port->pwr_role == role) return; port->pwr_role = role; sysfs_notify(&port->dev.kobj, NULL, "power_role"); kobject_uevent(&port->dev.kobj, KOBJ_CHANGE); } EXPORT_SYMBOL_GPL(typec_set_pwr_role); /** * typec_set_vconn_role - Report VCONN source change * @port: The USB Type-C Port which VCONN role changed * @role: Source when @port is sourcing VCONN, or Sink when it's not * * This routine is used by the port drivers to report if the VCONN source is * changes. */ void typec_set_vconn_role(struct typec_port *port, enum typec_role role) { if (port->vconn_role == role) return; port->vconn_role = role; sysfs_notify(&port->dev.kobj, NULL, "vconn_source"); kobject_uevent(&port->dev.kobj, KOBJ_CHANGE); } EXPORT_SYMBOL_GPL(typec_set_vconn_role); /** * typec_set_pwr_opmode - Report changed power operation mode * @port: The USB Type-C Port where the mode was changed * @opmode: New power operation mode * * This routine is used by the port drivers to report changed power operation * mode in @port. The modes are USB (default), 1.5A, 3.0A as defined in USB * Type-C specification, and "USB Power Delivery" when the power levels are * negotiated with methods defined in USB Power Delivery specification. */ void typec_set_pwr_opmode(struct typec_port *port, enum typec_pwr_opmode opmode) { struct device *partner_dev; if (port->pwr_opmode == opmode) return; port->pwr_opmode = opmode; sysfs_notify(&port->dev.kobj, NULL, "power_operation_mode"); kobject_uevent(&port->dev.kobj, KOBJ_CHANGE); partner_dev = device_find_child(&port->dev, NULL, partner_match); if (partner_dev) { struct typec_partner *partner = to_typec_partner(partner_dev); if (opmode == TYPEC_PWR_MODE_PD && !partner->usb_pd) { partner->usb_pd = 1; sysfs_notify(&partner_dev->kobj, NULL, "supports_usb_power_delivery"); kobject_uevent(&partner_dev->kobj, KOBJ_CHANGE); } put_device(partner_dev); } } EXPORT_SYMBOL_GPL(typec_set_pwr_opmode); /** * typec_find_pwr_opmode - Get the typec power operation mode capability * @name: power operation mode string * * This routine is used to find the typec_pwr_opmode by its string @name. * * Returns typec_pwr_opmode if success, otherwise negative error code. */ int typec_find_pwr_opmode(const char *name) { return match_string(typec_pwr_opmodes, ARRAY_SIZE(typec_pwr_opmodes), name); } EXPORT_SYMBOL_GPL(typec_find_pwr_opmode); /** * typec_find_orientation - Convert orientation string to enum typec_orientation * @name: Orientation string * * This routine is used to find the typec_orientation by its string name @name. * * Returns the orientation value on success, otherwise negative error code. */ int typec_find_orientation(const char *name) { return match_string(typec_orientations, ARRAY_SIZE(typec_orientations), name); } EXPORT_SYMBOL_GPL(typec_find_orientation); /** * typec_find_port_power_role - Get the typec port power capability * @name: port power capability string * * This routine is used to find the typec_port_type by its string name. * * Returns typec_port_type if success, otherwise negative error code. */ int typec_find_port_power_role(const char *name) { return match_string(typec_port_power_roles, ARRAY_SIZE(typec_port_power_roles), name); } EXPORT_SYMBOL_GPL(typec_find_port_power_role); /** * typec_find_power_role - Find the typec one specific power role * @name: power role string * * This routine is used to find the typec_role by its string name. * * Returns typec_role if success, otherwise negative error code. */ int typec_find_power_role(const char *name) { return match_string(typec_roles, ARRAY_SIZE(typec_roles), name); } EXPORT_SYMBOL_GPL(typec_find_power_role); /** * typec_find_port_data_role - Get the typec port data capability * @name: port data capability string * * This routine is used to find the typec_port_data by its string name. * * Returns typec_port_data if success, otherwise negative error code. */ int typec_find_port_data_role(const char *name) { return match_string(typec_port_data_roles, ARRAY_SIZE(typec_port_data_roles), name); } EXPORT_SYMBOL_GPL(typec_find_port_data_role); /* ------------------------------------------ */ /* API for Multiplexer/DeMultiplexer Switches */ /** * typec_set_orientation - Set USB Type-C cable plug orientation * @port: USB Type-C Port * @orientation: USB Type-C cable plug orientation * * Set cable plug orientation for @port. */ int typec_set_orientation(struct typec_port *port, enum typec_orientation orientation) { int ret; ret = typec_switch_set(port->sw, orientation); if (ret) return ret; port->orientation = orientation; sysfs_notify(&port->dev.kobj, NULL, "orientation"); kobject_uevent(&port->dev.kobj, KOBJ_CHANGE); return 0; } EXPORT_SYMBOL_GPL(typec_set_orientation); /** * typec_get_orientation - Get USB Type-C cable plug orientation * @port: USB Type-C Port * * Get current cable plug orientation for @port. */ enum typec_orientation typec_get_orientation(struct typec_port *port) { return port->orientation; } EXPORT_SYMBOL_GPL(typec_get_orientation); /** * 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) { struct typec_mux_state state = { }; state.mode = mode; return typec_mux_set(port->mux, &state); } EXPORT_SYMBOL_GPL(typec_set_mode); /* --------------------------------------- */ /** * typec_get_negotiated_svdm_version - Get negotiated SVDM Version * @port: USB Type-C Port. * * Get the negotiated SVDM Version. The Version is set to the port default * value stored in typec_capability on partner registration, and updated after * a successful Discover Identity if the negotiated value is less than the * default value. * * Returns usb_pd_svdm_ver if the partner has been registered otherwise -ENODEV. */ int typec_get_negotiated_svdm_version(struct typec_port *port) { enum usb_pd_svdm_ver svdm_version; struct device *partner_dev; partner_dev = device_find_child(&port->dev, NULL, partner_match); if (!partner_dev) return -ENODEV; svdm_version = to_typec_partner(partner_dev)->svdm_version; put_device(partner_dev); return svdm_version; } EXPORT_SYMBOL_GPL(typec_get_negotiated_svdm_version); /** * typec_get_cable_svdm_version - Get cable negotiated SVDM Version * @port: USB Type-C Port. * * Get the negotiated SVDM Version for the cable. The Version is set to the port * default value based on the PD Revision during cable registration, and updated * after a successful Discover Identity if the negotiated value is less than the * default. * * Returns usb_pd_svdm_ver if the cable has been registered otherwise -ENODEV. */ int typec_get_cable_svdm_version(struct typec_port *port) { enum usb_pd_svdm_ver svdm_version; struct device *cable_dev; cable_dev = device_find_child(&port->dev, NULL, cable_match); if (!cable_dev) return -ENODEV; svdm_version = to_typec_cable(cable_dev)->svdm_version; put_device(cable_dev); return svdm_version; } EXPORT_SYMBOL_GPL(typec_get_cable_svdm_version); /** * typec_cable_set_svdm_version - Set negotiated Structured VDM (SVDM) Version * @cable: USB Type-C Active Cable that supports SVDM * @svdm_version: Negotiated SVDM Version * * This routine is used to save the negotiated SVDM Version. */ void typec_cable_set_svdm_version(struct typec_cable *cable, enum usb_pd_svdm_ver svdm_version) { cable->svdm_version = svdm_version; } EXPORT_SYMBOL_GPL(typec_cable_set_svdm_version); /** * typec_get_drvdata - Return private driver data pointer * @port: USB Type-C port */ void *typec_get_drvdata(struct typec_port *port) { return dev_get_drvdata(&port->dev); } EXPORT_SYMBOL_GPL(typec_get_drvdata); int typec_get_fw_cap(struct typec_capability *cap, struct fwnode_handle *fwnode) { const char *cap_str; int ret; cap->fwnode = fwnode; ret = fwnode_property_read_string(fwnode, "power-role", &cap_str); if (ret < 0) return ret; ret = typec_find_port_power_role(cap_str); if (ret < 0) return ret; cap->type = ret; /* USB data support is optional */ ret = fwnode_property_read_string(fwnode, "data-role", &cap_str); if (ret == 0) { ret = typec_find_port_data_role(cap_str); if (ret < 0) return ret; cap->data = ret; } /* Get the preferred power role for a DRP */ if (cap->type == TYPEC_PORT_DRP) { cap->prefer_role = TYPEC_NO_PREFERRED_ROLE; ret = fwnode_property_read_string(fwnode, "try-power-role", &cap_str); if (ret == 0) { ret = typec_find_power_role(cap_str); if (ret < 0) return ret; cap->prefer_role = ret; } } return 0; } EXPORT_SYMBOL_GPL(typec_get_fw_cap); /** * 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 * * This routine is used to register an alternate mode that @port is capable of * supporting. * * Returns handle to the alternate mode on success or ERR_PTR on failure. */ struct typec_altmode * typec_port_register_altmode(struct typec_port *port, const struct typec_altmode_desc *desc) { struct typec_altmode *adev; struct typec_mux *mux; struct typec_retimer *retimer; mux = typec_mux_get(&port->dev); if (IS_ERR(mux)) return ERR_CAST(mux); retimer = typec_retimer_get(&port->dev); if (IS_ERR(retimer)) { typec_mux_put(mux); return ERR_CAST(retimer); } adev = typec_register_altmode(&port->dev, desc); if (IS_ERR(adev)) { typec_retimer_put(retimer); typec_mux_put(mux); } else { to_altmode(adev)->mux = mux; to_altmode(adev)->retimer = retimer; } return adev; } EXPORT_SYMBOL_GPL(typec_port_register_altmode); void typec_port_register_altmodes(struct typec_port *port, const struct typec_altmode_ops *ops, void *drvdata, struct typec_altmode **altmodes, size_t n) { struct fwnode_handle *child; struct typec_altmode_desc desc; struct typec_altmode *alt; size_t index = 0; u16 svid; u32 vdo; int ret; struct fwnode_handle *altmodes_node __free(fwnode_handle) = device_get_named_child_node(&port->dev, "altmodes"); if (!altmodes_node) return; /* No altmodes specified */ fwnode_for_each_child_node(altmodes_node, child) { ret = fwnode_property_read_u16(child, "svid", &svid); if (ret) { dev_err(&port->dev, "Error reading svid for altmode %s\n", fwnode_get_name(child)); continue; } ret = fwnode_property_read_u32(child, "vdo", &vdo); if (ret) { dev_err(&port->dev, "Error reading vdo for altmode %s\n", fwnode_get_name(child)); continue; } if (index >= n) { dev_err(&port->dev, "Error not enough space for altmode %s\n", fwnode_get_name(child)); continue; } desc.svid = svid; desc.vdo = vdo; desc.mode = index + 1; alt = typec_port_register_altmode(port, &desc); if (IS_ERR(alt)) { dev_err(&port->dev, "Error registering altmode %s\n", fwnode_get_name(child)); continue; } typec_altmode_set_ops(alt, ops); typec_altmode_set_drvdata(alt, drvdata); altmodes[index] = alt; index++; } } EXPORT_SYMBOL_GPL(typec_port_register_altmodes); /** * typec_port_register_cable_ops - Register typec_cable_ops to port altmodes * @altmodes: USB Type-C Port's altmode vector * @max_altmodes: The maximum number of alt modes supported by the port * @ops: Cable alternate mode vector */ void typec_port_register_cable_ops(struct typec_altmode **altmodes, int max_altmodes, const struct typec_cable_ops *ops) { int i; for (i = 0; i < max_altmodes; i++) { if (!altmodes[i]) return; altmodes[i]->cable_ops = ops; } } EXPORT_SYMBOL_GPL(typec_port_register_cable_ops); /** * 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; int ret; int id; port = kzalloc(sizeof(*port), GFP_KERNEL); if (!port) return ERR_PTR(-ENOMEM); id = ida_alloc(&typec_index_ida, GFP_KERNEL); if (id < 0) { kfree(port); return ERR_PTR(id); } switch (cap->type) { case TYPEC_PORT_SRC: port->pwr_role = TYPEC_SOURCE; port->vconn_role = TYPEC_SOURCE; break; case TYPEC_PORT_SNK: port->pwr_role = TYPEC_SINK; port->vconn_role = TYPEC_SINK; break; case TYPEC_PORT_DRP: if (cap->prefer_role != TYPEC_NO_PREFERRED_ROLE) port->pwr_role = cap->prefer_role; else port->pwr_role = TYPEC_SINK; break; } switch (cap->data) { case TYPEC_PORT_DFP: port->data_role = TYPEC_HOST; break; case TYPEC_PORT_UFP: port->data_role = TYPEC_DEVICE; break; case TYPEC_PORT_DRD: if (cap->prefer_role == TYPEC_SOURCE) port->data_role = TYPEC_HOST; else port->data_role = TYPEC_DEVICE; break; } ida_init(&port->mode_ids); mutex_init(&port->port_type_lock); port->id = id; port->ops = cap->ops; port->port_type = cap->type; port->prefer_role = cap->prefer_role; port->con.attach = typec_partner_attach; port->con.deattach = typec_partner_deattach; if (cap->usb_capability & USB_CAPABILITY_USB4) port->usb_mode = USB_MODE_USB4; else if (cap->usb_capability & USB_CAPABILITY_USB3) port->usb_mode = USB_MODE_USB3; else if (cap->usb_capability & USB_CAPABILITY_USB2) port->usb_mode = USB_MODE_USB2; device_initialize(&port->dev); port->dev.class = &typec_class; port->dev.parent = parent; port->dev.fwnode = cap->fwnode; port->dev.type = &typec_port_dev_type; dev_set_name(&port->dev, "port%d", id); dev_set_drvdata(&port->dev, cap->driver_data); port->cap = kmemdup(cap, sizeof(*cap), GFP_KERNEL); if (!port->cap) { put_device(&port->dev); return ERR_PTR(-ENOMEM); } port->sw = typec_switch_get(&port->dev); if (IS_ERR(port->sw)) { ret = PTR_ERR(port->sw); put_device(&port->dev); return ERR_PTR(ret); } port->mux = typec_mux_get(&port->dev); if (IS_ERR(port->mux)) { ret = PTR_ERR(port->mux); put_device(&port->dev); return ERR_PTR(ret); } port->retimer = typec_retimer_get(&port->dev); if (IS_ERR(port->retimer)) { ret = PTR_ERR(port->retimer); put_device(&port->dev); return ERR_PTR(ret); } port->pd = cap->pd; ret = device_add(&port->dev); if (ret) { dev_err(parent, "failed to register port (%d)\n", ret); put_device(&port->dev); return ERR_PTR(ret); } ret = usb_power_delivery_link_device(port->pd, &port->dev); if (ret) { dev_err(&port->dev, "failed to link pd\n"); device_unregister(&port->dev); return ERR_PTR(ret); } ret = typec_link_ports(port); if (ret) dev_warn(&port->dev, "failed to create symlinks (%d)\n", ret); return port; } EXPORT_SYMBOL_GPL(typec_register_port); /** * typec_unregister_port - Unregister a USB Type-C Port * @port: The port to be unregistered * * Unregister device created with typec_register_port(). */ void typec_unregister_port(struct typec_port *port) { if (!IS_ERR_OR_NULL(port)) { typec_unlink_ports(port); typec_port_set_usb_power_delivery(port, NULL); device_unregister(&port->dev); } } EXPORT_SYMBOL_GPL(typec_unregister_port); static int __init typec_init(void) { int ret; ret = bus_register(&typec_bus); if (ret) return ret; ret = class_register(&typec_mux_class); if (ret) goto err_unregister_bus; ret = class_register(&retimer_class); if (ret) goto err_unregister_mux_class; ret = class_register(&typec_class); if (ret) goto err_unregister_retimer_class; ret = usb_power_delivery_init(); if (ret) goto err_unregister_class; return 0; err_unregister_class: class_unregister(&typec_class); err_unregister_retimer_class: class_unregister(&retimer_class); err_unregister_mux_class: class_unregister(&typec_mux_class); err_unregister_bus: bus_unregister(&typec_bus); return ret; } subsys_initcall(typec_init); static void __exit typec_exit(void) { usb_power_delivery_exit(); class_unregister(&typec_class); ida_destroy(&typec_index_ida); bus_unregister(&typec_bus); class_unregister(&typec_mux_class); class_unregister(&retimer_class); } module_exit(typec_exit); MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@linux.intel.com>"); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("USB Type-C Connector Class");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
You can’t perform that action at this time.