From 3b3cd24ae61b3bbe9d3cecaff33e7cb3250ce47a Mon Sep 17 00:00:00 2001 From: Manu Gautam Date: Tue, 16 Jan 2018 16:27:09 +0530 Subject: phy: Add USB speed related PHY modes Add following USB speed related PHY modes: LS (Low Speed), FS (Full Speed), HS (High Speed), SS (Super Speed) Speed related information is required by some QCOM PHY drivers to program PHY monitor resume/remote-wakeup events in suspended state. Speed is needed in order to set correct polarity of wakeup events for detection. E.g. QUSB2 PHY monitors DP/DM line state depending on whether speed is LS or FS/HS to detect resume. Similarly QMP USB3 PHY in SS mode should monitor RX terminations attach/detach and LFPS events depending on SSPHY is active or not. Signed-off-by: Manu Gautam Signed-off-by: Kishon Vijay Abraham I --- include/linux/phy/phy.h | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'include/linux') diff --git a/include/linux/phy/phy.h b/include/linux/phy/phy.h index 4f8423a948d5..485469e6fa7f 100644 --- a/include/linux/phy/phy.h +++ b/include/linux/phy/phy.h @@ -25,7 +25,15 @@ struct phy; enum phy_mode { PHY_MODE_INVALID, PHY_MODE_USB_HOST, + PHY_MODE_USB_HOST_LS, + PHY_MODE_USB_HOST_FS, + PHY_MODE_USB_HOST_HS, + PHY_MODE_USB_HOST_SS, PHY_MODE_USB_DEVICE, + PHY_MODE_USB_DEVICE_LS, + PHY_MODE_USB_DEVICE_FS, + PHY_MODE_USB_DEVICE_HS, + PHY_MODE_USB_DEVICE_SS, PHY_MODE_USB_OTG, PHY_MODE_SGMII, PHY_MODE_10GKR, @@ -61,6 +69,7 @@ struct phy_ops { */ struct phy_attrs { u32 bus_width; + enum phy_mode mode; }; /** @@ -144,6 +153,10 @@ int phy_exit(struct phy *phy); int phy_power_on(struct phy *phy); int phy_power_off(struct phy *phy); int phy_set_mode(struct phy *phy, enum phy_mode mode); +static inline enum phy_mode phy_get_mode(struct phy *phy) +{ + return phy->attrs.mode; +} int phy_reset(struct phy *phy); int phy_calibrate(struct phy *phy); static inline int phy_get_bus_width(struct phy *phy) @@ -260,6 +273,11 @@ static inline int phy_set_mode(struct phy *phy, enum phy_mode mode) return -ENOSYS; } +static inline enum phy_mode phy_get_mode(struct phy *phy) +{ + return PHY_MODE_INVALID; +} + static inline int phy_reset(struct phy *phy) { if (!phy) -- cgit v1.2.3-71-gd317 From becaf17a58473e358e056ada2642e895aae93b0e Mon Sep 17 00:00:00 2001 From: Dov Levenglick Date: Fri, 2 Feb 2018 18:34:50 +0200 Subject: phy: fix structure documentation Add missing documentation of structure members and modify the order of documentation to match that of the structure declaration. Signed-off-by: Dov Levenglick Signed-off-by: Kishon Vijay Abraham I --- include/linux/phy/phy.h | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'include/linux') diff --git a/include/linux/phy/phy.h b/include/linux/phy/phy.h index 485469e6fa7f..c9d14eeee7f5 100644 --- a/include/linux/phy/phy.h +++ b/include/linux/phy/phy.h @@ -81,7 +81,8 @@ struct phy_attrs { * @mutex: mutex to protect phy_ops * @init_count: used to protect when the PHY is used by multiple consumers * @power_count: used to protect when the PHY is used by multiple consumers - * @phy_attrs: used to specify PHY specific attributes + * @attrs: used to specify PHY specific attributes + * @pwr: power regulator associated with the phy */ struct phy { struct device dev; @@ -97,9 +98,10 @@ struct phy { /** * struct phy_provider - represents the phy provider * @dev: phy provider device + * @children: can be used to override the default (dev->of_node) child node * @owner: the module owner having of_xlate - * @of_xlate: function pointer to obtain phy instance from phy pointer * @list: to maintain a linked list of PHY providers + * @of_xlate: function pointer to obtain phy instance from phy pointer */ struct phy_provider { struct device *dev; @@ -110,6 +112,13 @@ struct phy_provider { struct of_phandle_args *args); }; +/** + * struct phy_lookup - PHY association in list of phys managed by the phy driver + * @node: list node + * @dev_id: the device of the association + * @con_id: connection ID string on device + * @phy: the phy of the association + */ struct phy_lookup { struct list_head node; const char *dev_id; -- cgit v1.2.3-71-gd317 From 5d6ae4f0da8a64a185074dabb1b2f8c148efa741 Mon Sep 17 00:00:00 2001 From: Chris Dickens Date: Sun, 31 Dec 2017 18:59:42 -0800 Subject: usb: gadget: composite: fix incorrect handling of OS desc requests When handling an OS descriptor request, one of the first operations is to zero out the request buffer using the wLength from the setup packet. There is no bounds checking, so a wLength > 4096 would clobber memory adjacent to the request buffer. Fix this by taking the min of wLength and the request buffer length prior to the memset. While at it, define the buffer length in a header file so that magic numbers don't appear throughout the code. When returning data to the host, the data length should be the min of the wLength and the valid data we have to return. Currently we are returning wLength, thus requests for a wLength greater than the amount of data in the OS descriptor buffer would return invalid (albeit zero'd) data following the valid descriptor data. Fix this by counting the number of bytes when constructing the data and using this when determining the length of the request. Signed-off-by: Chris Dickens Signed-off-by: Felipe Balbi --- drivers/usb/gadget/composite.c | 40 +++++++++++++++++++--------------------- include/linux/usb/composite.h | 3 +++ 2 files changed, 22 insertions(+), 21 deletions(-) (limited to 'include/linux') diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c index 77c7ecca816a..b8b629c615d3 100644 --- a/drivers/usb/gadget/composite.c +++ b/drivers/usb/gadget/composite.c @@ -1422,7 +1422,7 @@ static int count_ext_compat(struct usb_configuration *c) return res; } -static void fill_ext_compat(struct usb_configuration *c, u8 *buf) +static int fill_ext_compat(struct usb_configuration *c, u8 *buf) { int i, count; @@ -1449,10 +1449,12 @@ static void fill_ext_compat(struct usb_configuration *c, u8 *buf) buf += 23; } count += 24; - if (count >= 4096) - return; + if (count + 24 >= USB_COMP_EP0_OS_DESC_BUFSIZ) + return count; } } + + return count; } static int count_ext_prop(struct usb_configuration *c, int interface) @@ -1497,25 +1499,20 @@ static int fill_ext_prop(struct usb_configuration *c, int interface, u8 *buf) struct usb_os_desc *d; struct usb_os_desc_ext_prop *ext_prop; int j, count, n, ret; - u8 *start = buf; f = c->interface[interface]; + count = 10; /* header length */ for (j = 0; j < f->os_desc_n; ++j) { if (interface != f->os_desc_table[j].if_id) continue; d = f->os_desc_table[j].os_desc; if (d) list_for_each_entry(ext_prop, &d->ext_prop, entry) { - /* 4kB minus header length */ - n = buf - start; - if (n >= 4086) - return 0; - - count = ext_prop->data_len + + n = ext_prop->data_len + ext_prop->name_len + 14; - if (count > 4086 - n) - return -EINVAL; - usb_ext_prop_put_size(buf, count); + if (count + n >= USB_COMP_EP0_OS_DESC_BUFSIZ) + return count; + usb_ext_prop_put_size(buf, n); usb_ext_prop_put_type(buf, ext_prop->type); ret = usb_ext_prop_put_name(buf, ext_prop->name, ext_prop->name_len); @@ -1541,11 +1538,12 @@ static int fill_ext_prop(struct usb_configuration *c, int interface, u8 *buf) default: return -EINVAL; } - buf += count; + buf += n; + count += n; } } - return 0; + return count; } /* @@ -1827,6 +1825,7 @@ unknown: req->complete = composite_setup_complete; buf = req->buf; os_desc_cfg = cdev->os_desc_config; + w_length = min_t(u16, w_length, USB_COMP_EP0_OS_DESC_BUFSIZ); memset(buf, 0, w_length); buf[5] = 0x01; switch (ctrl->bRequestType & USB_RECIP_MASK) { @@ -1850,8 +1849,8 @@ unknown: count += 16; /* header */ put_unaligned_le32(count, buf); buf += 16; - fill_ext_compat(os_desc_cfg, buf); - value = w_length; + value = fill_ext_compat(os_desc_cfg, buf); + value = min_t(u16, w_length, value); } break; case USB_RECIP_INTERFACE: @@ -1880,8 +1879,7 @@ unknown: interface, buf); if (value < 0) return value; - - value = w_length; + value = min_t(u16, w_length, value); } break; } @@ -2156,8 +2154,8 @@ int composite_os_desc_req_prepare(struct usb_composite_dev *cdev, goto end; } - /* OS feature descriptor length <= 4kB */ - cdev->os_desc_req->buf = kmalloc(4096, GFP_KERNEL); + cdev->os_desc_req->buf = kmalloc(USB_COMP_EP0_OS_DESC_BUFSIZ, + GFP_KERNEL); if (!cdev->os_desc_req->buf) { ret = -ENOMEM; usb_ep_free_request(ep0, cdev->os_desc_req); diff --git a/include/linux/usb/composite.h b/include/linux/usb/composite.h index cef0e44601f8..4b6b9283fa7b 100644 --- a/include/linux/usb/composite.h +++ b/include/linux/usb/composite.h @@ -54,6 +54,9 @@ /* big enough to hold our biggest descriptor */ #define USB_COMP_EP0_BUFSIZ 1024 +/* OS feature descriptor length <= 4kB */ +#define USB_COMP_EP0_OS_DESC_BUFSIZ 4096 + #define USB_MS_TO_HS_INTERVAL(x) (ilog2((x * 1000 / 125)) + 1) struct usb_configuration; -- cgit v1.2.3-71-gd317 From ccefd976f921a280327b17b2896bc809baa7b672 Mon Sep 17 00:00:00 2001 From: Adam Thomson Date: Tue, 2 Jan 2018 15:50:49 +0000 Subject: typec: tcpm: Add PD Rev 3.0 definitions to PD header This commit adds definitions for PD Rev 3.0 messages, including APDO PPS and extended message support for TCPM. Signed-off-by: Adam Thomson Acked-by: Heikki Krogerus Signed-off-by: Greg Kroah-Hartman --- include/linux/usb/pd.h | 185 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 174 insertions(+), 11 deletions(-) (limited to 'include/linux') diff --git a/include/linux/usb/pd.h b/include/linux/usb/pd.h index b3d41d7409b3..ff359bdfdc7b 100644 --- a/include/linux/usb/pd.h +++ b/include/linux/usb/pd.h @@ -35,6 +35,13 @@ enum pd_ctrl_msg_type { PD_CTRL_WAIT = 12, PD_CTRL_SOFT_RESET = 13, /* 14-15 Reserved */ + PD_CTRL_NOT_SUPP = 16, + PD_CTRL_GET_SOURCE_CAP_EXT = 17, + PD_CTRL_GET_STATUS = 18, + PD_CTRL_FR_SWAP = 19, + PD_CTRL_GET_PPS_STATUS = 20, + PD_CTRL_GET_COUNTRY_CODES = 21, + /* 22-31 Reserved */ }; enum pd_data_msg_type { @@ -43,13 +50,39 @@ enum pd_data_msg_type { PD_DATA_REQUEST = 2, PD_DATA_BIST = 3, PD_DATA_SINK_CAP = 4, - /* 5-14 Reserved */ + PD_DATA_BATT_STATUS = 5, + PD_DATA_ALERT = 6, + PD_DATA_GET_COUNTRY_INFO = 7, + /* 8-14 Reserved */ PD_DATA_VENDOR_DEF = 15, + /* 16-31 Reserved */ +}; + +enum pd_ext_msg_type { + /* 0 Reserved */ + PD_EXT_SOURCE_CAP_EXT = 1, + PD_EXT_STATUS = 2, + PD_EXT_GET_BATT_CAP = 3, + PD_EXT_GET_BATT_STATUS = 4, + PD_EXT_BATT_CAP = 5, + PD_EXT_GET_MANUFACTURER_INFO = 6, + PD_EXT_MANUFACTURER_INFO = 7, + PD_EXT_SECURITY_REQUEST = 8, + PD_EXT_SECURITY_RESPONSE = 9, + PD_EXT_FW_UPDATE_REQUEST = 10, + PD_EXT_FW_UPDATE_RESPONSE = 11, + PD_EXT_PPS_STATUS = 12, + PD_EXT_COUNTRY_INFO = 13, + PD_EXT_COUNTRY_CODES = 14, + /* 15-31 Reserved */ }; #define PD_REV10 0x0 #define PD_REV20 0x1 +#define PD_REV30 0x2 +#define PD_MAX_REV PD_REV30 +#define PD_HEADER_EXT_HDR BIT(15) #define PD_HEADER_CNT_SHIFT 12 #define PD_HEADER_CNT_MASK 0x7 #define PD_HEADER_ID_SHIFT 9 @@ -59,18 +92,19 @@ enum pd_data_msg_type { #define PD_HEADER_REV_MASK 0x3 #define PD_HEADER_DATA_ROLE BIT(5) #define PD_HEADER_TYPE_SHIFT 0 -#define PD_HEADER_TYPE_MASK 0xf +#define PD_HEADER_TYPE_MASK 0x1f -#define PD_HEADER(type, pwr, data, id, cnt) \ +#define PD_HEADER(type, pwr, data, rev, id, cnt, ext_hdr) \ ((((type) & PD_HEADER_TYPE_MASK) << PD_HEADER_TYPE_SHIFT) | \ ((pwr) == TYPEC_SOURCE ? PD_HEADER_PWR_ROLE : 0) | \ ((data) == TYPEC_HOST ? PD_HEADER_DATA_ROLE : 0) | \ - (PD_REV20 << PD_HEADER_REV_SHIFT) | \ + (rev << PD_HEADER_REV_SHIFT) | \ (((id) & PD_HEADER_ID_MASK) << PD_HEADER_ID_SHIFT) | \ - (((cnt) & PD_HEADER_CNT_MASK) << PD_HEADER_CNT_SHIFT)) + (((cnt) & PD_HEADER_CNT_MASK) << PD_HEADER_CNT_SHIFT) | \ + ((ext_hdr) ? PD_HEADER_EXT_HDR : 0)) #define PD_HEADER_LE(type, pwr, data, id, cnt) \ - cpu_to_le16(PD_HEADER((type), (pwr), (data), (id), (cnt))) + cpu_to_le16(PD_HEADER((type), (pwr), (data), PD_REV20, (id), (cnt), (0))) static inline unsigned int pd_header_cnt(u16 header) { @@ -102,16 +136,75 @@ static inline unsigned int pd_header_msgid_le(__le16 header) return pd_header_msgid(le16_to_cpu(header)); } +static inline unsigned int pd_header_rev(u16 header) +{ + return (header >> PD_HEADER_REV_SHIFT) & PD_HEADER_REV_MASK; +} + +static inline unsigned int pd_header_rev_le(__le16 header) +{ + return pd_header_rev(le16_to_cpu(header)); +} + +#define PD_EXT_HDR_CHUNKED BIT(15) +#define PD_EXT_HDR_CHUNK_NUM_SHIFT 11 +#define PD_EXT_HDR_CHUNK_NUM_MASK 0xf +#define PD_EXT_HDR_REQ_CHUNK BIT(10) +#define PD_EXT_HDR_DATA_SIZE_SHIFT 0 +#define PD_EXT_HDR_DATA_SIZE_MASK 0x1ff + +#define PD_EXT_HDR(data_size, req_chunk, chunk_num, chunked) \ + ((((data_size) & PD_EXT_HDR_DATA_SIZE_MASK) << PD_EXT_HDR_DATA_SIZE_SHIFT) | \ + ((req_chunk) ? PD_EXT_HDR_REQ_CHUNK : 0) | \ + (((chunk_num) & PD_EXT_HDR_CHUNK_NUM_MASK) << PD_EXT_HDR_CHUNK_NUM_SHIFT) | \ + ((chunked) ? PD_EXT_HDR_CHUNKED : 0)) + +#define PD_EXT_HDR_LE(data_size, req_chunk, chunk_num, chunked) \ + cpu_to_le16(PD_EXT_HDR((data_size), (req_chunk), (chunk_num), (chunked))) + +static inline unsigned int pd_ext_header_chunk_num(u16 ext_header) +{ + return (ext_header >> PD_EXT_HDR_CHUNK_NUM_SHIFT) & + PD_EXT_HDR_CHUNK_NUM_MASK; +} + +static inline unsigned int pd_ext_header_data_size(u16 ext_header) +{ + return (ext_header >> PD_EXT_HDR_DATA_SIZE_SHIFT) & + PD_EXT_HDR_DATA_SIZE_MASK; +} + +static inline unsigned int pd_ext_header_data_size_le(__le16 ext_header) +{ + return pd_ext_header_data_size(le16_to_cpu(ext_header)); +} + #define PD_MAX_PAYLOAD 7 +#define PD_EXT_MAX_CHUNK_DATA 26 /** - * struct pd_message - PD message as seen on wire - * @header: PD message header - * @payload: PD message payload - */ + * struct pd_chunked_ext_message_data - PD chunked extended message data as + * seen on wire + * @header: PD extended message header + * @data: PD extended message data + */ +struct pd_chunked_ext_message_data { + __le16 header; + u8 data[PD_EXT_MAX_CHUNK_DATA]; +} __packed; + +/** + * struct pd_message - PD message as seen on wire + * @header: PD message header + * @payload: PD message payload + * @ext_msg: PD message chunked extended message data + */ struct pd_message { __le16 header; - __le32 payload[PD_MAX_PAYLOAD]; + union { + __le32 payload[PD_MAX_PAYLOAD]; + struct pd_chunked_ext_message_data ext_msg; + }; } __packed; /* PDO: Power Data Object */ @@ -121,6 +214,7 @@ enum pd_pdo_type { PDO_TYPE_FIXED = 0, PDO_TYPE_BATT = 1, PDO_TYPE_VAR = 2, + PDO_TYPE_APDO = 3, }; #define PDO_TYPE_SHIFT 30 @@ -174,6 +268,34 @@ enum pd_pdo_type { (PDO_TYPE(PDO_TYPE_VAR) | PDO_VAR_MIN_VOLT(min_mv) | \ PDO_VAR_MAX_VOLT(max_mv) | PDO_VAR_MAX_CURR(max_ma)) +enum pd_apdo_type { + APDO_TYPE_PPS = 0, +}; + +#define PDO_APDO_TYPE_SHIFT 28 /* Only valid value currently is 0x0 - PPS */ +#define PDO_APDO_TYPE_MASK 0x3 + +#define PDO_APDO_TYPE(t) ((t) << PDO_APDO_TYPE_SHIFT) + +#define PDO_PPS_APDO_MAX_VOLT_SHIFT 17 /* 100mV units */ +#define PDO_PPS_APDO_MIN_VOLT_SHIFT 8 /* 100mV units */ +#define PDO_PPS_APDO_MAX_CURR_SHIFT 0 /* 50mA units */ + +#define PDO_PPS_APDO_VOLT_MASK 0xff +#define PDO_PPS_APDO_CURR_MASK 0x7f + +#define PDO_PPS_APDO_MIN_VOLT(mv) \ + ((((mv) / 100) & PDO_PPS_APDO_VOLT_MASK) << PDO_PPS_APDO_MIN_VOLT_SHIFT) +#define PDO_PPS_APDO_MAX_VOLT(mv) \ + ((((mv) / 100) & PDO_PPS_APDO_VOLT_MASK) << PDO_PPS_APDO_MAX_VOLT_SHIFT) +#define PDO_PPS_APDO_MAX_CURR(ma) \ + ((((ma) / 50) & PDO_PPS_APDO_CURR_MASK) << PDO_PPS_APDO_MAX_CURR_SHIFT) + +#define PDO_PPS_APDO(min_mv, max_mv, max_ma) \ + (PDO_TYPE(PDO_TYPE_APDO) | PDO_APDO_TYPE(APDO_TYPE_PPS) | \ + PDO_PPS_APDO_MIN_VOLT(min_mv) | PDO_PPS_APDO_MAX_VOLT(max_mv) | \ + PDO_PPS_APDO_MAX_CURR(max_ma)) + static inline enum pd_pdo_type pdo_type(u32 pdo) { return (pdo >> PDO_TYPE_SHIFT) & PDO_TYPE_MASK; @@ -204,6 +326,29 @@ static inline unsigned int pdo_max_power(u32 pdo) return ((pdo >> PDO_BATT_MAX_PWR_SHIFT) & PDO_PWR_MASK) * 250; } +static inline enum pd_apdo_type pdo_apdo_type(u32 pdo) +{ + return (pdo >> PDO_APDO_TYPE_SHIFT) & PDO_APDO_TYPE_MASK; +} + +static inline unsigned int pdo_pps_apdo_min_voltage(u32 pdo) +{ + return ((pdo >> PDO_PPS_APDO_MIN_VOLT_SHIFT) & + PDO_PPS_APDO_VOLT_MASK) * 100; +} + +static inline unsigned int pdo_pps_apdo_max_voltage(u32 pdo) +{ + return ((pdo >> PDO_PPS_APDO_MAX_VOLT_SHIFT) & + PDO_PPS_APDO_VOLT_MASK) * 100; +} + +static inline unsigned int pdo_pps_apdo_max_current(u32 pdo) +{ + return ((pdo >> PDO_PPS_APDO_MAX_CURR_SHIFT) & + PDO_PPS_APDO_CURR_MASK) * 50; +} + /* RDO: Request Data Object */ #define RDO_OBJ_POS_SHIFT 28 #define RDO_OBJ_POS_MASK 0x7 @@ -237,6 +382,24 @@ static inline unsigned int pdo_max_power(u32 pdo) (RDO_OBJ(idx) | (flags) | \ RDO_BATT_OP_PWR(op_mw) | RDO_BATT_MAX_PWR(max_mw)) +#define RDO_PROG_VOLT_MASK 0x7ff +#define RDO_PROG_CURR_MASK 0x7f + +#define RDO_PROG_VOLT_SHIFT 9 +#define RDO_PROG_CURR_SHIFT 0 + +#define RDO_PROG_VOLT_MV_STEP 20 +#define RDO_PROG_CURR_MA_STEP 50 + +#define PDO_PROG_OUT_VOLT(mv) \ + ((((mv) / RDO_PROG_VOLT_MV_STEP) & RDO_PROG_VOLT_MASK) << RDO_PROG_VOLT_SHIFT) +#define PDO_PROG_OP_CURR(ma) \ + ((((ma) / RDO_PROG_CURR_MA_STEP) & RDO_PROG_CURR_MASK) << RDO_PROG_CURR_SHIFT) + +#define RDO_PROG(idx, out_mv, op_ma, flags) \ + (RDO_OBJ(idx) | (flags) | \ + PDO_PROG_OUT_VOLT(out_mv) | PDO_PROG_OP_CURR(op_ma)) + static inline unsigned int rdo_index(u32 rdo) { return (rdo >> RDO_OBJ_POS_SHIFT) & RDO_OBJ_POS_MASK; -- cgit v1.2.3-71-gd317 From 456ebb4f221e98f507cc945690a670a79682c029 Mon Sep 17 00:00:00 2001 From: Adam Thomson Date: Tue, 2 Jan 2018 15:50:50 +0000 Subject: typec: tcpm: Add ADO header for Alert message handling This commit adds a header providing definitions for handling Alert messages. Currently the header only focuses on handling incoming alerts. Signed-off-by: Adam Thomson Acked-by: Heikki Krogerus Signed-off-by: Greg Kroah-Hartman --- include/linux/usb/pd_ado.h | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 include/linux/usb/pd_ado.h (limited to 'include/linux') diff --git a/include/linux/usb/pd_ado.h b/include/linux/usb/pd_ado.h new file mode 100644 index 000000000000..9aa1cf31c93c --- /dev/null +++ b/include/linux/usb/pd_ado.h @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2017 Dialog Semiconductor + * + * Author: Adam Thomson + */ + +#ifndef __LINUX_USB_PD_ADO_H +#define __LINUX_USB_PD_ADO_H + +/* ADO : Alert Data Object */ +#define USB_PD_ADO_TYPE_SHIFT 24 +#define USB_PD_ADO_TYPE_MASK 0xff +#define USB_PD_ADO_FIXED_BATT_SHIFT 20 +#define USB_PD_ADO_FIXED_BATT_MASK 0xf +#define USB_PD_ADO_HOT_SWAP_BATT_SHIFT 16 +#define USB_PD_ADO_HOT_SWAP_BATT_MASK 0xf + +#define USB_PD_ADO_TYPE_BATT_STATUS_CHANGE BIT(1) +#define USB_PD_ADO_TYPE_OCP BIT(2) +#define USB_PD_ADO_TYPE_OTP BIT(3) +#define USB_PD_ADO_TYPE_OP_COND_CHANGE BIT(4) +#define USB_PD_ADO_TYPE_SRC_INPUT_CHANGE BIT(5) +#define USB_PD_ADO_TYPE_OVP BIT(6) + +static inline unsigned int usb_pd_ado_type(u32 ado) +{ + return (ado >> USB_PD_ADO_TYPE_SHIFT) & USB_PD_ADO_TYPE_MASK; +} + +static inline unsigned int usb_pd_ado_fixed_batt(u32 ado) +{ + return (ado >> USB_PD_ADO_FIXED_BATT_SHIFT) & + USB_PD_ADO_FIXED_BATT_MASK; +} + +static inline unsigned int usb_pd_ado_hot_swap_batt(u32 ado) +{ + return (ado >> USB_PD_ADO_HOT_SWAP_BATT_SHIFT) & + USB_PD_ADO_HOT_SWAP_BATT_MASK; +} +#endif /* __LINUX_USB_PD_ADO_H */ -- cgit v1.2.3-71-gd317 From 02cad961cae556357e4a63b11f849e80418c1ffc Mon Sep 17 00:00:00 2001 From: Adam Thomson Date: Tue, 2 Jan 2018 15:50:51 +0000 Subject: typec: tcpm: Add SDB header for Status message handling This commit adds a header providing definitions for handling Status messages. Currently the header only focuses on handling incoming Status messages. Signed-off-by: Adam Thomson Acked-by: Heikki Krogerus Signed-off-by: Greg Kroah-Hartman --- include/linux/usb/pd_ext_sdb.h | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 include/linux/usb/pd_ext_sdb.h (limited to 'include/linux') diff --git a/include/linux/usb/pd_ext_sdb.h b/include/linux/usb/pd_ext_sdb.h new file mode 100644 index 000000000000..0eb83ce19597 --- /dev/null +++ b/include/linux/usb/pd_ext_sdb.h @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2017 Dialog Semiconductor + * + * Author: Adam Thomson + */ + +#ifndef __LINUX_USB_PD_EXT_SDB_H +#define __LINUX_USB_PD_EXT_SDB_H + +/* SDB : Status Data Block */ +enum usb_pd_ext_sdb_fields { + USB_PD_EXT_SDB_INTERNAL_TEMP = 0, + USB_PD_EXT_SDB_PRESENT_INPUT, + USB_PD_EXT_SDB_PRESENT_BATT_INPUT, + USB_PD_EXT_SDB_EVENT_FLAGS, + USB_PD_EXT_SDB_TEMP_STATUS, + USB_PD_EXT_SDB_DATA_SIZE, +}; + +/* Event Flags */ +#define USB_PD_EXT_SDB_EVENT_OCP BIT(1) +#define USB_PD_EXT_SDB_EVENT_OTP BIT(2) +#define USB_PD_EXT_SDB_EVENT_OVP BIT(3) +#define USB_PD_EXT_SDB_EVENT_CF_CV_MODE BIT(4) + +#define USB_PD_EXT_SDB_PPS_EVENTS (USB_PD_EXT_SDB_EVENT_OCP | \ + USB_PD_EXT_SDB_EVENT_OTP | \ + USB_PD_EXT_SDB_EVENT_OVP) + +#endif /* __LINUX_USB_PD_EXT_SDB_H */ -- cgit v1.2.3-71-gd317 From 4e88d4c083016454f179686529ae65d70b933b58 Mon Sep 17 00:00:00 2001 From: Martin Blumenstingl Date: Sat, 3 Mar 2018 22:43:03 +0100 Subject: usb: add a flag to skip PHY initialization to struct usb_hcd The USB HCD core driver parses the device-tree node for "phys" and "usb-phys" properties. It also manages the power state of these PHYs automatically. However, drivers may opt-out of this behavior by setting "phy" or "usb_phy" in struct usb_hcd to a non-null value. An example where this is required is the "Qualcomm USB2 controller", implemented by the chipidea driver. The hardware requires that the PHY is only powered on after the "reset completed" event from the controller is received. A follow-up patch will allow the USB HCD core driver to manage more than one PHY. Add a new "skip_phy_initialization" bitflag to struct usb_hcd so drivers can opt-out of any PHY management provided by the USB HCD core driver. This also updates the existing drivers so they use the new flag if they want to opt out of the PHY management provided by the USB HCD core driver. This means that for these drivers the new "multiple PHY" handling (which will be added in a follow-up patch) will be disabled as well. Signed-off-by: Martin Blumenstingl Acked-by: Peter Chen Tested-by: Neil Armstrong Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/host.c | 6 ++---- drivers/usb/core/hcd.c | 4 ++-- drivers/usb/host/ehci-fsl.c | 2 ++ drivers/usb/host/ehci-platform.c | 4 ++-- drivers/usb/host/ehci-tegra.c | 1 + drivers/usb/host/ohci-omap.c | 1 + drivers/usb/host/ohci-platform.c | 4 ++-- drivers/usb/host/xhci-plat.c | 1 + include/linux/usb/hcd.h | 6 ++++++ 9 files changed, 19 insertions(+), 10 deletions(-) (limited to 'include/linux') diff --git a/drivers/usb/chipidea/host.c b/drivers/usb/chipidea/host.c index 19d60ed7e41f..af45aa3222b5 100644 --- a/drivers/usb/chipidea/host.c +++ b/drivers/usb/chipidea/host.c @@ -124,10 +124,8 @@ static int host_start(struct ci_hdrc *ci) hcd->power_budget = ci->platdata->power_budget; hcd->tpl_support = ci->platdata->tpl_support; - if (ci->phy) - hcd->phy = ci->phy; - else - hcd->usb_phy = ci->usb_phy; + if (ci->phy || ci->usb_phy) + hcd->skip_phy_initialization = 1; ehci = hcd_to_ehci(hcd); ehci->caps = ci->hw_bank.cap; diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index fc32391a34d5..f2307470a31e 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -2727,7 +2727,7 @@ int usb_add_hcd(struct usb_hcd *hcd, int retval; struct usb_device *rhdev; - if (IS_ENABLED(CONFIG_USB_PHY) && !hcd->usb_phy) { + if (IS_ENABLED(CONFIG_USB_PHY) && !hcd->skip_phy_initialization) { struct usb_phy *phy = usb_get_phy_dev(hcd->self.sysdev, 0); if (IS_ERR(phy)) { @@ -2745,7 +2745,7 @@ int usb_add_hcd(struct usb_hcd *hcd, } } - if (IS_ENABLED(CONFIG_GENERIC_PHY) && !hcd->phy) { + if (IS_ENABLED(CONFIG_GENERIC_PHY) && !hcd->skip_phy_initialization) { struct phy *phy = phy_get(hcd->self.sysdev, "usb"); if (IS_ERR(phy)) { diff --git a/drivers/usb/host/ehci-fsl.c b/drivers/usb/host/ehci-fsl.c index c5094cb88cd5..0a9fd2022acf 100644 --- a/drivers/usb/host/ehci-fsl.c +++ b/drivers/usb/host/ehci-fsl.c @@ -155,6 +155,8 @@ static int fsl_ehci_drv_probe(struct platform_device *pdev) retval = -ENODEV; goto err2; } + + hcd->skip_phy_initialization = 1; } #endif return retval; diff --git a/drivers/usb/host/ehci-platform.c b/drivers/usb/host/ehci-platform.c index b065a960adc2..b91eea8c73ae 100644 --- a/drivers/usb/host/ehci-platform.c +++ b/drivers/usb/host/ehci-platform.c @@ -219,9 +219,9 @@ static int ehci_platform_probe(struct platform_device *dev) if (IS_ERR(priv->phys[phy_num])) { err = PTR_ERR(priv->phys[phy_num]); goto err_put_hcd; - } else if (!hcd->phy) { + } else { /* Avoiding phy_get() in usb_add_hcd() */ - hcd->phy = priv->phys[phy_num]; + hcd->skip_phy_initialization = 1; } } diff --git a/drivers/usb/host/ehci-tegra.c b/drivers/usb/host/ehci-tegra.c index c809f7d2f08f..a6f4389f7e88 100644 --- a/drivers/usb/host/ehci-tegra.c +++ b/drivers/usb/host/ehci-tegra.c @@ -461,6 +461,7 @@ static int tegra_ehci_probe(struct platform_device *pdev) goto cleanup_clk_en; } hcd->usb_phy = u_phy; + hcd->skip_phy_initialization = 1; tegra->needs_double_reset = of_property_read_bool(pdev->dev.of_node, "nvidia,needs-double-reset"); diff --git a/drivers/usb/host/ohci-omap.c b/drivers/usb/host/ohci-omap.c index 0201c49bc4fc..d8d35d456456 100644 --- a/drivers/usb/host/ohci-omap.c +++ b/drivers/usb/host/ohci-omap.c @@ -230,6 +230,7 @@ static int ohci_omap_reset(struct usb_hcd *hcd) } else { return -EPROBE_DEFER; } + hcd->skip_phy_initialization = 1; ohci->start_hnp = start_hnp; } #endif diff --git a/drivers/usb/host/ohci-platform.c b/drivers/usb/host/ohci-platform.c index 1e6c954f4b3f..62ef36a9333f 100644 --- a/drivers/usb/host/ohci-platform.c +++ b/drivers/usb/host/ohci-platform.c @@ -186,9 +186,9 @@ static int ohci_platform_probe(struct platform_device *dev) if (IS_ERR(priv->phys[phy_num])) { err = PTR_ERR(priv->phys[phy_num]); goto err_put_hcd; - } else if (!hcd->phy) { + } else { /* Avoiding phy_get() in usb_add_hcd() */ - hcd->phy = priv->phys[phy_num]; + hcd->skip_phy_initialization = 1; } } diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c index 6f038306c14d..6700e5ee82ad 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -284,6 +284,7 @@ static int xhci_plat_probe(struct platform_device *pdev) ret = usb_phy_init(hcd->usb_phy); if (ret) goto put_usb3_hcd; + hcd->skip_phy_initialization = 1; } ret = usb_add_hcd(hcd, irq, IRQF_SHARED); diff --git a/include/linux/usb/hcd.h b/include/linux/usb/hcd.h index 176900528822..693502c84c04 100644 --- a/include/linux/usb/hcd.h +++ b/include/linux/usb/hcd.h @@ -151,6 +151,12 @@ struct usb_hcd { unsigned msix_enabled:1; /* driver has MSI-X enabled? */ unsigned msi_enabled:1; /* driver has MSI enabled? */ unsigned remove_phy:1; /* auto-remove USB phy */ + /* + * do not manage the PHY state in the HCD core, instead let the driver + * handle this (for example if the PHY can only be turned on after a + * specific event) + */ + unsigned skip_phy_initialization:1; /* The next flag is a stopgap, to be removed when all the HCDs * support the new root-hub polling mechanism. */ -- cgit v1.2.3-71-gd317 From 178a0bce05cbc17a27f9cba78258c5d12adc980c Mon Sep 17 00:00:00 2001 From: Martin Blumenstingl Date: Sat, 3 Mar 2018 22:43:05 +0100 Subject: usb: core: hcd: integrate the PHY wrapper into the HCD core This integrates the PHY wrapper into the core hcd infrastructure. Multiple PHYs which are part of the HCD's device tree node are now managed (= powered on/off when needed), by the new usb_phy_roothub code. Suspend and resume is also supported, however not for runtime/auto-suspend (which is triggered for example when no devices are connected to the USB bus). This is needed on some SoCs (for example Amlogic Meson GXL) because if the PHYs are disabled during auto-suspend then devices which are plugged in afterwards are not seen by the host. One example where this is required is the Amlogic GXL and GXM SoCs: They are using a dwc3 USB controller with up to three ports enabled on the internal roothub. Each port has it's own PHY which must be enabled (if one of the PHYs is left disabled then none of the USB ports works at all). The new logic works on the Amlogic GXL and GXM SoCs because the dwc3 driver internally creates a xhci-hcd which then registers a HCD which then triggers our new PHY wrapper. USB controller drivers can opt out of this by setting "skip_phy_initialization" in struct usb_hcd to true. This is identical to how it works for a single USB PHY, so the "multiple PHY" handling is disabled for drivers that opted out of the management logic of a single PHY. Signed-off-by: Martin Blumenstingl Acked-by: Alan Stern Acked-by: Chunfeng Yun Tested-by: Yixun Lan Tested-by: Neil Armstrong Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hcd.c | 31 +++++++++++++++++++++++++++++++ include/linux/usb/hcd.h | 1 + 2 files changed, 32 insertions(+) (limited to 'include/linux') diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index f2307470a31e..32797c25ac3b 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -37,6 +37,7 @@ #include #include "usb.h" +#include "phy.h" /*-------------------------------------------------------------------------*/ @@ -2260,6 +2261,9 @@ int hcd_bus_suspend(struct usb_device *rhdev, pm_message_t msg) usb_set_device_state(rhdev, USB_STATE_SUSPENDED); hcd->state = HC_STATE_SUSPENDED; + if (!PMSG_IS_AUTO(msg)) + usb_phy_roothub_power_off(hcd->phy_roothub); + /* Did we race with a root-hub wakeup event? */ if (rhdev->do_remote_wakeup) { char buffer[6]; @@ -2296,6 +2300,13 @@ int hcd_bus_resume(struct usb_device *rhdev, pm_message_t msg) dev_dbg(&rhdev->dev, "skipped %s of dead bus\n", "resume"); return 0; } + + if (!PMSG_IS_AUTO(msg)) { + status = usb_phy_roothub_power_on(hcd->phy_roothub); + if (status) + return status; + } + if (!hcd->driver->bus_resume) return -ENOENT; if (HCD_RH_RUNNING(hcd)) @@ -2333,6 +2344,7 @@ int hcd_bus_resume(struct usb_device *rhdev, pm_message_t msg) } } else { hcd->state = old_state; + usb_phy_roothub_power_off(hcd->phy_roothub); dev_dbg(&rhdev->dev, "bus %s fail, err %d\n", "resume", status); if (status != -ESHUTDOWN) @@ -2769,6 +2781,18 @@ int usb_add_hcd(struct usb_hcd *hcd, } } + if (!hcd->skip_phy_initialization) { + hcd->phy_roothub = usb_phy_roothub_init(hcd->self.sysdev); + if (IS_ERR(hcd->phy_roothub)) { + retval = PTR_ERR(hcd->phy_roothub); + goto err_phy_roothub_init; + } + + retval = usb_phy_roothub_power_on(hcd->phy_roothub); + if (retval) + goto err_usb_phy_roothub_power_on; + } + dev_info(hcd->self.controller, "%s\n", hcd->product_desc); /* Keep old behaviour if authorized_default is not in [0, 1]. */ @@ -2933,6 +2957,10 @@ err_allocate_root_hub: err_register_bus: hcd_buffer_destroy(hcd); err_create_buf: + usb_phy_roothub_power_off(hcd->phy_roothub); +err_usb_phy_roothub_power_on: + usb_phy_roothub_exit(hcd->phy_roothub); +err_phy_roothub_init: if (IS_ENABLED(CONFIG_GENERIC_PHY) && hcd->remove_phy && hcd->phy) { phy_power_off(hcd->phy); phy_exit(hcd->phy); @@ -3017,6 +3045,9 @@ void usb_remove_hcd(struct usb_hcd *hcd) usb_deregister_bus(&hcd->self); hcd_buffer_destroy(hcd); + usb_phy_roothub_power_off(hcd->phy_roothub); + usb_phy_roothub_exit(hcd->phy_roothub); + if (IS_ENABLED(CONFIG_GENERIC_PHY) && hcd->remove_phy && hcd->phy) { phy_power_off(hcd->phy); phy_exit(hcd->phy); diff --git a/include/linux/usb/hcd.h b/include/linux/usb/hcd.h index 693502c84c04..a042675e03ba 100644 --- a/include/linux/usb/hcd.h +++ b/include/linux/usb/hcd.h @@ -104,6 +104,7 @@ struct usb_hcd { */ struct usb_phy *usb_phy; struct phy *phy; + struct usb_phy_roothub *phy_roothub; /* Flags that need to be manipulated atomically because they can * change while the host controller is running. Always use -- cgit v1.2.3-71-gd317 From ad70f937e9d0bdc580e390db3a047f9e58863b6e Mon Sep 17 00:00:00 2001 From: Martin Blumenstingl Date: Sat, 3 Mar 2018 22:43:09 +0100 Subject: usb: core: hcd: remove support for initializing a single PHY With the new PHY wrapper in place we can now handle multiple PHYs. Remove the code which handles only one generic PHY as this is now covered (with support for multiple PHYs as well as suspend/resume support) by the new PHY wrapper. Signed-off-by: Martin Blumenstingl Tested-by: Neil Armstrong Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hcd.c | 37 ------------------------------------- include/linux/usb/hcd.h | 1 - 2 files changed, 38 deletions(-) (limited to 'include/linux') diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 32797c25ac3b..5a92d8f7c484 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -2757,30 +2757,6 @@ int usb_add_hcd(struct usb_hcd *hcd, } } - if (IS_ENABLED(CONFIG_GENERIC_PHY) && !hcd->skip_phy_initialization) { - struct phy *phy = phy_get(hcd->self.sysdev, "usb"); - - if (IS_ERR(phy)) { - retval = PTR_ERR(phy); - if (retval == -EPROBE_DEFER) - goto err_phy; - } else { - retval = phy_init(phy); - if (retval) { - phy_put(phy); - goto err_phy; - } - retval = phy_power_on(phy); - if (retval) { - phy_exit(phy); - phy_put(phy); - goto err_phy; - } - hcd->phy = phy; - hcd->remove_phy = 1; - } - } - if (!hcd->skip_phy_initialization) { hcd->phy_roothub = usb_phy_roothub_init(hcd->self.sysdev); if (IS_ERR(hcd->phy_roothub)) { @@ -2961,13 +2937,6 @@ err_create_buf: err_usb_phy_roothub_power_on: usb_phy_roothub_exit(hcd->phy_roothub); err_phy_roothub_init: - if (IS_ENABLED(CONFIG_GENERIC_PHY) && hcd->remove_phy && hcd->phy) { - phy_power_off(hcd->phy); - phy_exit(hcd->phy); - phy_put(hcd->phy); - hcd->phy = NULL; - } -err_phy: if (hcd->remove_phy && hcd->usb_phy) { usb_phy_shutdown(hcd->usb_phy); usb_put_phy(hcd->usb_phy); @@ -3048,12 +3017,6 @@ void usb_remove_hcd(struct usb_hcd *hcd) usb_phy_roothub_power_off(hcd->phy_roothub); usb_phy_roothub_exit(hcd->phy_roothub); - if (IS_ENABLED(CONFIG_GENERIC_PHY) && hcd->remove_phy && hcd->phy) { - phy_power_off(hcd->phy); - phy_exit(hcd->phy); - phy_put(hcd->phy); - hcd->phy = NULL; - } if (hcd->remove_phy && hcd->usb_phy) { usb_phy_shutdown(hcd->usb_phy); usb_put_phy(hcd->usb_phy); diff --git a/include/linux/usb/hcd.h b/include/linux/usb/hcd.h index a042675e03ba..aef50cb2ed1b 100644 --- a/include/linux/usb/hcd.h +++ b/include/linux/usb/hcd.h @@ -103,7 +103,6 @@ struct usb_hcd { * other external phys should be software-transparent */ struct usb_phy *usb_phy; - struct phy *phy; struct usb_phy_roothub *phy_roothub; /* Flags that need to be manipulated atomically because they can -- cgit v1.2.3-71-gd317 From f2d9b66d84f3ff5ea3aff111e6a403e04fa8bf37 Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Tue, 20 Mar 2018 15:57:02 +0300 Subject: drivers: base: Unified device connection lookup Several frameworks - clk, gpio, phy, pmw, etc. - maintain lookup tables for describing connections and provide custom API for handling them. This introduces a single generic lookup table and API for the connections. The motivation for this commit is centralizing the connection lookup, but the goal is to ultimately extract the connection descriptions also from firmware by using the fwnode_graph_* functions and other mechanisms that are available. Reviewed-by: Hans de Goede Reviewed-by: Andy Shevchenko Signed-off-by: Heikki Krogerus Signed-off-by: Greg Kroah-Hartman --- Documentation/driver-api/device_connection.rst | 43 ++++++++ drivers/base/Makefile | 3 +- drivers/base/devcon.c | 136 +++++++++++++++++++++++++ include/linux/device.h | 22 ++++ 4 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 Documentation/driver-api/device_connection.rst create mode 100644 drivers/base/devcon.c (limited to 'include/linux') diff --git a/Documentation/driver-api/device_connection.rst b/Documentation/driver-api/device_connection.rst new file mode 100644 index 000000000000..affbc5566ab0 --- /dev/null +++ b/Documentation/driver-api/device_connection.rst @@ -0,0 +1,43 @@ +================== +Device connections +================== + +Introduction +------------ + +Devices often have connections to other devices that are outside of the direct +child/parent relationship. A serial or network communication controller, which +could be a PCI device, may need to be able to get a reference to its PHY +component, which could be attached for example to the I2C bus. Some device +drivers need to be able to control the clocks or the GPIOs for their devices, +and so on. + +Device connections are generic descriptions of any type of connection between +two separate devices. + +Device connections alone do not create a dependency between the two devices. +They are only descriptions which are not tied to either of the devices directly. +A dependency between the two devices exists only if one of the two endpoint +devices requests a reference to the other. The descriptions themselves can be +defined in firmware (not yet supported) or they can be built-in. + +Usage +----- + +Device connections should exist before device ``->probe`` callback is called for +either endpoint device in the description. If the connections are defined in +firmware, this is not a problem. It should be considered if the connection +descriptions are "built-in", and need to be added separately. + +The connection description consists of the names of the two devices with the +connection, i.e. the endpoints, and unique identifier for the connection which +is needed if there are multiple connections between the two devices. + +After a description exists, the devices in it can request reference to the other +endpoint device, or they can request the description itself. + +API +--- + +.. kernel-doc:: drivers/base/devcon.c + : functions: device_connection_find_match device_connection_find device_connection_add device_connection_remove diff --git a/drivers/base/Makefile b/drivers/base/Makefile index e32a52490051..12a7f64d35a9 100644 --- a/drivers/base/Makefile +++ b/drivers/base/Makefile @@ -5,7 +5,8 @@ obj-y := component.o core.o bus.o dd.o syscore.o \ driver.o class.o platform.o \ cpu.o firmware.o init.o map.o devres.o \ attribute_container.o transport_class.o \ - topology.o container.o property.o cacheinfo.o + topology.o container.o property.o cacheinfo.o \ + devcon.o obj-$(CONFIG_DEVTMPFS) += devtmpfs.o obj-$(CONFIG_DMA_CMA) += dma-contiguous.o obj-y += power/ diff --git a/drivers/base/devcon.c b/drivers/base/devcon.c new file mode 100644 index 000000000000..d427e806cd73 --- /dev/null +++ b/drivers/base/devcon.c @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * Device connections + * + * Copyright (C) 2018 Intel Corporation + * Author: Heikki Krogerus + */ + +#include + +static DEFINE_MUTEX(devcon_lock); +static LIST_HEAD(devcon_list); + +/** + * device_connection_find_match - Find physical connection to a device + * @dev: Device with the connection + * @con_id: Identifier for the connection + * @data: Data for the match function + * @match: Function to check and convert the connection description + * + * Find a connection with unique identifier @con_id between @dev and another + * device. @match will be used to convert the connection description to data the + * caller is expecting to be returned. + */ +void *device_connection_find_match(struct device *dev, const char *con_id, + void *data, + void *(*match)(struct device_connection *con, + int ep, void *data)) +{ + const char *devname = dev_name(dev); + struct device_connection *con; + void *ret = NULL; + int ep; + + if (!match) + return NULL; + + mutex_lock(&devcon_lock); + + list_for_each_entry(con, &devcon_list, list) { + ep = match_string(con->endpoint, 2, devname); + if (ep < 0) + continue; + + if (con_id && strcmp(con->id, con_id)) + continue; + + ret = match(con, !ep, data); + if (ret) + break; + } + + mutex_unlock(&devcon_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(device_connection_find_match); + +extern struct bus_type platform_bus_type; +extern struct bus_type pci_bus_type; +extern struct bus_type i2c_bus_type; +extern struct bus_type spi_bus_type; + +static struct bus_type *generic_match_buses[] = { + &platform_bus_type, +#ifdef CONFIG_PCI + &pci_bus_type, +#endif +#ifdef CONFIG_I2C + &i2c_bus_type, +#endif +#ifdef CONFIG_SPI_MASTER + &spi_bus_type, +#endif + NULL, +}; + +/* This tries to find the device from the most common bus types by name. */ +static void *generic_match(struct device_connection *con, int ep, void *data) +{ + struct bus_type *bus; + struct device *dev; + + for (bus = generic_match_buses[0]; bus; bus++) { + dev = bus_find_device_by_name(bus, NULL, con->endpoint[ep]); + if (dev) + return dev; + } + + /* + * We only get called if a connection was found, tell the caller to + * wait for the other device to show up. + */ + return ERR_PTR(-EPROBE_DEFER); +} + +/** + * device_connection_find - Find two devices connected together + * @dev: Device with the connection + * @con_id: Identifier for the connection + * + * Find a connection with unique identifier @con_id between @dev and + * another device. On success returns handle to the device that is connected + * to @dev, with the reference count for the found device incremented. Returns + * NULL if no matching connection was found, or ERR_PTR(-EPROBE_DEFER) when a + * connection was found but the other device has not been enumerated yet. + */ +struct device *device_connection_find(struct device *dev, const char *con_id) +{ + return device_connection_find_match(dev, con_id, NULL, generic_match); +} +EXPORT_SYMBOL_GPL(device_connection_find); + +/** + * device_connection_add - Register a connection description + * @con: The connection description to be registered + */ +void device_connection_add(struct device_connection *con) +{ + mutex_lock(&devcon_lock); + list_add_tail(&con->list, &devcon_list); + mutex_unlock(&devcon_lock); +} +EXPORT_SYMBOL_GPL(device_connection_add); + +/** + * device_connections_remove - Unregister connection description + * @con: The connection description to be unregistered + */ +void device_connection_remove(struct device_connection *con) +{ + mutex_lock(&devcon_lock); + list_del(&con->list); + mutex_unlock(&devcon_lock); +} +EXPORT_SYMBOL_GPL(device_connection_remove); diff --git a/include/linux/device.h b/include/linux/device.h index b093405ed525..204ff64279fd 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -729,6 +729,28 @@ struct device_dma_parameters { unsigned long segment_boundary_mask; }; +/** + * struct device_connection - Device Connection Descriptor + * @endpoint: The names of the two devices connected together + * @id: Unique identifier for the connection + * @list: List head, private, for internal use only + */ +struct device_connection { + const char *endpoint[2]; + const char *id; + struct list_head list; +}; + +void *device_connection_find_match(struct device *dev, const char *con_id, + void *data, + void *(*match)(struct device_connection *con, + int ep, void *data)); + +struct device *device_connection_find(struct device *dev, const char *con_id); + +void device_connection_add(struct device_connection *con); +void device_connection_remove(struct device_connection *con); + /** * enum device_link_state - Device link states. * @DL_STATE_NONE: The presence of the drivers is not being tracked. -- cgit v1.2.3-71-gd317 From bdecb33af34f79cbfbb656661210f77c8b8b5b5f Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Tue, 20 Mar 2018 15:57:03 +0300 Subject: usb: typec: API for controlling USB Type-C Multiplexers USB Type-C connectors consist of various muxes and switches that route the pins on the connector to the right locations. The USB Type-C drivers need to be able to control the muxes, as they are the ones that know things like the cable plug orientation, and the current mode that was negotiated with the partner. This introduces a small API for registering and controlling cable plug orientation switches, and separate small API for registering and controlling pin multiplexer/demultiplexer switches that are needed with Accessory/Alternate Modes. Reviewed-by: Hans de Goede Reviewed-by: Andy Shevchenko Signed-off-by: Heikki Krogerus Signed-off-by: Greg Kroah-Hartman --- Documentation/driver-api/usb/typec.rst | 73 +- drivers/usb/typec/Makefile | 1 + drivers/usb/typec/class.c | 1435 ++++++++++++++++++++++++++++++++ drivers/usb/typec/mux.c | 191 +++++ drivers/usb/typec/typec.c | 1365 ------------------------------ include/linux/usb/typec.h | 14 + include/linux/usb/typec_mux.h | 55 ++ 7 files changed, 1758 insertions(+), 1376 deletions(-) create mode 100644 drivers/usb/typec/class.c create mode 100644 drivers/usb/typec/mux.c delete mode 100644 drivers/usb/typec/typec.c create mode 100644 include/linux/usb/typec_mux.h (limited to 'include/linux') diff --git a/Documentation/driver-api/usb/typec.rst b/Documentation/driver-api/usb/typec.rst index 8a7249f2ff04..feb31946490b 100644 --- a/Documentation/driver-api/usb/typec.rst +++ b/Documentation/driver-api/usb/typec.rst @@ -61,7 +61,7 @@ Registering the ports The port drivers will describe every Type-C port they control with struct typec_capability data structure, and register them with the following API: -.. kernel-doc:: drivers/usb/typec/typec.c +.. kernel-doc:: drivers/usb/typec/class.c :functions: typec_register_port typec_unregister_port When registering the ports, the prefer_role member in struct typec_capability @@ -80,7 +80,7 @@ typec_partner_desc. The class copies the details of the partner during registration. The class offers the following API for registering/unregistering partners. -.. kernel-doc:: drivers/usb/typec/typec.c +.. kernel-doc:: drivers/usb/typec/class.c :functions: typec_register_partner typec_unregister_partner The class will provide a handle to struct typec_partner if the registration was @@ -92,7 +92,7 @@ should include handle to struct usb_pd_identity instance. The class will then create a sysfs directory for the identity under the partner device. The result of Discover Identity command can then be reported with the following API: -.. kernel-doc:: drivers/usb/typec/typec.c +.. kernel-doc:: drivers/usb/typec/class.c :functions: typec_partner_set_identity Registering Cables @@ -113,7 +113,7 @@ typec_cable_desc and about a plug in struct typec_plug_desc. The class copies the details during registration. The class offers the following API for registering/unregistering cables and their plugs: -.. kernel-doc:: drivers/usb/typec/typec.c +.. kernel-doc:: drivers/usb/typec/class.c :functions: typec_register_cable typec_unregister_cable typec_register_plug typec_unregister_plug The class will provide a handle to struct typec_cable and struct typec_plug if @@ -125,7 +125,7 @@ include handle to struct usb_pd_identity instance. The class will then create a sysfs directory for the identity under the cable device. The result of Discover Identity command can then be reported with the following API: -.. kernel-doc:: drivers/usb/typec/typec.c +.. kernel-doc:: drivers/usb/typec/class.c :functions: typec_cable_set_identity Notifications @@ -135,7 +135,7 @@ When the partner has executed a role change, or when the default roles change during connection of a partner or cable, the port driver must use the following APIs to report it to the class: -.. kernel-doc:: drivers/usb/typec/typec.c +.. kernel-doc:: drivers/usb/typec/class.c :functions: typec_set_data_role typec_set_pwr_role typec_set_vconn_role typec_set_pwr_opmode Alternate Modes @@ -150,7 +150,7 @@ and struct typec_altmode_desc which is a container for all the supported modes. Ports that support Alternate Modes need to register each SVID they support with the following API: -.. kernel-doc:: drivers/usb/typec/typec.c +.. kernel-doc:: drivers/usb/typec/class.c :functions: typec_port_register_altmode If a partner or cable plug provides a list of SVIDs as response to USB Power @@ -159,12 +159,12 @@ registered. API for the partners: -.. kernel-doc:: drivers/usb/typec/typec.c +.. kernel-doc:: drivers/usb/typec/class.c :functions: typec_partner_register_altmode API for the Cable Plugs: -.. kernel-doc:: drivers/usb/typec/typec.c +.. kernel-doc:: drivers/usb/typec/class.c :functions: typec_plug_register_altmode So ports, partners and cable plugs will register the alternate modes with their @@ -172,11 +172,62 @@ own functions, but the registration will always return a handle to struct typec_altmode on success, or NULL. The unregistration will happen with the same function: -.. kernel-doc:: drivers/usb/typec/typec.c +.. kernel-doc:: drivers/usb/typec/class.c :functions: typec_unregister_altmode If a partner or cable plug enters or exits a mode, the port driver needs to notify the class with the following API: -.. kernel-doc:: drivers/usb/typec/typec.c +.. kernel-doc:: drivers/usb/typec/class.c :functions: typec_altmode_update_active + +Multiplexer/DeMultiplexer Switches +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +USB Type-C connectors may have one or more mux/demux switches behind them. Since +the plugs can be inserted right-side-up or upside-down, a switch is needed to +route the correct data pairs from the connector to the USB controllers. If +Alternate or Accessory Modes are supported, another switch is needed that can +route the pins on the connector to some other component besides USB. USB Type-C +Connector Class supplies an API for registering those switches. + +.. kernel-doc:: drivers/usb/typec/mux.c + :functions: typec_switch_register typec_switch_unregister typec_mux_register typec_mux_unregister + +In most cases the same physical mux will handle both the orientation and mode. +However, as the port drivers will be responsible for the orientation, and the +alternate mode drivers for the mode, the two are always separated into their +own logical components: "mux" for the mode and "switch" for the orientation. + +When a port is registered, USB Type-C Connector Class requests both the mux and +the switch for the port. The drivers can then use the following API for +controlling them: + +.. kernel-doc:: drivers/usb/typec/class.c + :functions: typec_set_orientation typec_set_mode + +If the connector is dual-role capable, there may also be a switch for the data +role. USB Type-C Connector Class does not supply separate API for them. The +port drivers can use USB Role Class API with those. + +Illustration of the muxes behind a connector that supports an alternate mode: + + ------------------------ + | Connector | + ------------------------ + | | + ------------------------ + \ Orientation / + -------------------- + | + -------------------- + / Mode \ + ------------------------ + / \ + ------------------------ -------------------- + | Alt Mode | / USB Role \ + ------------------------ ------------------------ + / \ + ------------------------ ------------------------ + | USB Host | | USB Device | + ------------------------ ------------------------ diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile index bb3138a6eaac..56b2e9516ec1 100644 --- a/drivers/usb/typec/Makefile +++ b/drivers/usb/typec/Makefile @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_TYPEC) += typec.o +typec-y := class.o mux.o obj-$(CONFIG_TYPEC_TCPM) += tcpm.o obj-y += fusb302/ obj-$(CONFIG_TYPEC_WCOVE) += typec_wcove.o diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c new file mode 100644 index 000000000000..2620a694704f --- /dev/null +++ b/drivers/usb/typec/class.c @@ -0,0 +1,1435 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * USB Type-C Connector Class + * + * Copyright (C) 2017, Intel Corporation + * Author: Heikki Krogerus + */ + +#include +#include +#include +#include +#include +#include + +struct typec_mode { + int index; + u32 vdo; + char *desc; + enum typec_port_type roles; + + struct typec_altmode *alt_mode; + + unsigned int active:1; + + char group_name[6]; + struct attribute_group group; + struct attribute *attrs[5]; + struct device_attribute vdo_attr; + struct device_attribute desc_attr; + struct device_attribute active_attr; + struct device_attribute roles_attr; +}; + +struct typec_altmode { + struct device dev; + u16 svid; + int n_modes; + struct typec_mode modes[ALTMODE_MAX_MODES]; + const struct attribute_group *mode_groups[ALTMODE_MAX_MODES]; +}; + +struct typec_plug { + struct device dev; + enum typec_plug_index index; +}; + +struct typec_cable { + struct device dev; + enum typec_plug_type type; + struct usb_pd_identity *identity; + unsigned int active:1; +}; + +struct typec_partner { + struct device dev; + unsigned int usb_pd:1; + struct usb_pd_identity *identity; + enum typec_accessory accessory; +}; + +struct typec_port { + unsigned int id; + struct device dev; + + int prefer_role; + enum typec_data_role data_role; + enum typec_role pwr_role; + enum typec_role vconn_role; + enum typec_pwr_opmode pwr_opmode; + enum typec_port_type port_type; + struct mutex port_type_lock; + + enum typec_orientation orientation; + struct typec_switch *sw; + struct typec_mux *mux; + + const struct typec_capability *cap; +}; + +#define to_typec_port(_dev_) container_of(_dev_, struct typec_port, 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_partner(_dev_) container_of(_dev_, struct typec_partner, dev) +#define to_altmode(_dev_) container_of(_dev_, struct typec_altmode, dev) + +static const struct device_type typec_partner_dev_type; +static const struct device_type typec_cable_dev_type; +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_cable(_dev_) (_dev_->type == &typec_cable_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 struct class *typec_class; + +/* ------------------------------------------------------------------------- */ +/* Common attributes */ + +static const char * const typec_accessory_modes[] = { + [TYPEC_ACCESSORY_NONE] = "none", + [TYPEC_ACCESSORY_AUDIO] = "analog_audio", + [TYPEC_ACCESSORY_DEBUG] = "debug", +}; + +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 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 struct attribute *usb_pd_id_attrs[] = { + &dev_attr_id_header.attr, + &dev_attr_cert_stat.attr, + &dev_attr_product.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_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"); +} + +/* ------------------------------------------------------------------------- */ +/* Alternate Modes */ + +/** + * typec_altmode_update_active - Report Enter/Exit mode + * @alt: Handle to the alternate mode + * @mode: Mode index + * @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 *alt, int mode, + bool active) +{ + struct typec_mode *m = &alt->modes[mode]; + char dir[6]; + + if (m->active == active) + return; + + m->active = active; + snprintf(dir, sizeof(dir), "mode%d", mode); + sysfs_notify(&alt->dev.kobj, dir, "active"); + kobject_uevent(&alt->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 +typec_altmode_vdo_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct typec_mode *mode = container_of(attr, struct typec_mode, + vdo_attr); + + return sprintf(buf, "0x%08x\n", mode->vdo); +} + +static ssize_t +typec_altmode_desc_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct typec_mode *mode = container_of(attr, struct typec_mode, + desc_attr); + + return sprintf(buf, "%s\n", mode->desc ? mode->desc : ""); +} + +static ssize_t +typec_altmode_active_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct typec_mode *mode = container_of(attr, struct typec_mode, + active_attr); + + return sprintf(buf, "%s\n", mode->active ? "yes" : "no"); +} + +static ssize_t +typec_altmode_active_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct typec_mode *mode = container_of(attr, struct typec_mode, + active_attr); + struct typec_port *port = typec_altmode2port(mode->alt_mode); + bool activate; + int ret; + + if (!port->cap->activate_mode) + return -EOPNOTSUPP; + + ret = kstrtobool(buf, &activate); + if (ret) + return ret; + + ret = port->cap->activate_mode(port->cap, mode->index, activate); + if (ret) + return ret; + + return size; +} + +static ssize_t +typec_altmode_roles_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct typec_mode *mode = container_of(attr, struct typec_mode, + roles_attr); + ssize_t ret; + + switch (mode->roles) { + case TYPEC_PORT_DFP: + ret = sprintf(buf, "source\n"); + break; + case TYPEC_PORT_UFP: + ret = sprintf(buf, "sink\n"); + break; + case TYPEC_PORT_DRP: + default: + ret = sprintf(buf, "source sink\n"); + break; + } + return ret; +} + +static void typec_init_modes(struct typec_altmode *alt, + const struct typec_mode_desc *desc, bool is_port) +{ + int i; + + for (i = 0; i < alt->n_modes; i++, desc++) { + struct typec_mode *mode = &alt->modes[i]; + + /* Not considering the human readable description critical */ + mode->desc = kstrdup(desc->desc, GFP_KERNEL); + if (desc->desc && !mode->desc) + dev_err(&alt->dev, "failed to copy mode%d desc\n", i); + + mode->alt_mode = alt; + mode->vdo = desc->vdo; + mode->roles = desc->roles; + mode->index = desc->index; + sprintf(mode->group_name, "mode%d", desc->index); + + sysfs_attr_init(&mode->vdo_attr.attr); + mode->vdo_attr.attr.name = "vdo"; + mode->vdo_attr.attr.mode = 0444; + mode->vdo_attr.show = typec_altmode_vdo_show; + + sysfs_attr_init(&mode->desc_attr.attr); + mode->desc_attr.attr.name = "description"; + mode->desc_attr.attr.mode = 0444; + mode->desc_attr.show = typec_altmode_desc_show; + + sysfs_attr_init(&mode->active_attr.attr); + mode->active_attr.attr.name = "active"; + mode->active_attr.attr.mode = 0644; + mode->active_attr.show = typec_altmode_active_show; + mode->active_attr.store = typec_altmode_active_store; + + mode->attrs[0] = &mode->vdo_attr.attr; + mode->attrs[1] = &mode->desc_attr.attr; + mode->attrs[2] = &mode->active_attr.attr; + + /* With ports, list the roles that the mode is supported with */ + if (is_port) { + sysfs_attr_init(&mode->roles_attr.attr); + mode->roles_attr.attr.name = "supported_roles"; + mode->roles_attr.attr.mode = 0444; + mode->roles_attr.show = typec_altmode_roles_show; + + mode->attrs[3] = &mode->roles_attr.attr; + } + + mode->group.attrs = mode->attrs; + mode->group.name = mode->group_name; + + alt->mode_groups[i] = &mode->group; + } +} + +static ssize_t svid_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct typec_altmode *alt = to_altmode(dev); + + return sprintf(buf, "%04x\n", alt->svid); +} +static DEVICE_ATTR_RO(svid); + +static struct attribute *typec_altmode_attrs[] = { + &dev_attr_svid.attr, + NULL +}; +ATTRIBUTE_GROUPS(typec_altmode); + +static void typec_altmode_release(struct device *dev) +{ + struct typec_altmode *alt = to_altmode(dev); + int i; + + for (i = 0; i < alt->n_modes; i++) + kfree(alt->modes[i].desc); + kfree(alt); +} + +static 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) +{ + struct typec_altmode *alt; + int ret; + + alt = kzalloc(sizeof(*alt), GFP_KERNEL); + if (!alt) + return ERR_PTR(-ENOMEM); + + alt->svid = desc->svid; + alt->n_modes = desc->n_modes; + typec_init_modes(alt, desc->modes, is_typec_port(parent)); + + alt->dev.parent = parent; + alt->dev.groups = alt->mode_groups; + alt->dev.type = &typec_altmode_dev_type; + dev_set_name(&alt->dev, "svid-%04x", alt->svid); + + ret = device_register(&alt->dev); + if (ret) { + dev_err(parent, "failed to register alternate mode (%d)\n", + ret); + put_device(&alt->dev); + return ERR_PTR(ret); + } + + return alt; +} + +/** + * typec_unregister_altmode - Unregister Alternate Mode + * @alt: 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 *alt) +{ + if (!IS_ERR_OR_NULL(alt)) + device_unregister(&alt->dev); +} +EXPORT_SYMBOL_GPL(typec_unregister_altmode); + +/* ------------------------------------------------------------------------- */ +/* Type-C Partners */ + +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 struct attribute *typec_partner_attrs[] = { + &dev_attr_accessory_mode.attr, + &dev_attr_supports_usb_power_delivery.attr, + NULL +}; +ATTRIBUTE_GROUPS(typec_partner); + +static void typec_partner_release(struct device *dev) +{ + struct typec_partner *partner = to_typec_partner(dev); + + kfree(partner); +} + +static const struct device_type typec_partner_dev_type = { + .name = "typec_partner", + .groups = typec_partner_groups, + .release = typec_partner_release, +}; + +/** + * 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) +{ + if (!partner->identity) + return -EINVAL; + + typec_report_identity(&partner->dev); + return 0; +} +EXPORT_SYMBOL_GPL(typec_partner_set_identity); + +/** + * 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 NULL 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_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); + + partner->usb_pd = desc->usb_pd; + partner->accessory = desc->accessory; + + 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)); + + 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); + } + + 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) +{ + if (!IS_ERR_OR_NULL(partner)) + 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); + + kfree(plug); +} + +static const struct device_type typec_plug_dev_type = { + .name = "typec_plug", + .release = typec_plug_release, +}; + +/** + * 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); + + 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 ssize_t +type_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct typec_cable *cable = to_typec_cable(dev); + + return sprintf(buf, "%s\n", cable->active ? "active" : "passive"); +} +static DEVICE_ATTR_RO(type); + +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, + NULL +}; +ATTRIBUTE_GROUPS(typec_cable); + +static void typec_cable_release(struct device *dev) +{ + struct typec_cable *cable = to_typec_cable(dev); + + kfree(cable); +} + +static const struct device_type typec_cable_dev_type = { + .name = "typec_cable", + .groups = typec_cable_groups, + .release = typec_cable_release, +}; + +/** + * 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; + + 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 */ + +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_types[] = { + [TYPEC_PORT_DFP] = "source", + [TYPEC_PORT_UFP] = "sink", + [TYPEC_PORT_DRP] = "dual", +}; + +static const char * const typec_port_types_drp[] = { + [TYPEC_PORT_DFP] = "dual [source] sink", + [TYPEC_PORT_UFP] = "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->cap->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->cap->try_role(port->cap, 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->cap->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->port_type != TYPEC_PORT_DRP) { + dev_dbg(dev, "port type fixed at \"%s\"", + typec_port_types[port->port_type]); + ret = -EOPNOTSUPP; + goto unlock_and_ret; + } + + ret = port->cap->dr_set(port->cap, 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->type == TYPEC_PORT_DRP) + 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->cap->pd_revision) { + dev_dbg(dev, "USB Power Delivery not supported\n"); + return -EOPNOTSUPP; + } + + if (!port->cap->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_types[port->port_type]); + ret = -EOPNOTSUPP; + goto unlock_and_ret; + } + + ret = port->cap->pr_set(port->cap, 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->port_type_set || port->cap->type != TYPEC_PORT_DRP) { + dev_dbg(dev, "changing port type not supported\n"); + return -EOPNOTSUPP; + } + + ret = sysfs_match_string(typec_port_types, 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->cap->port_type_set(port->cap, 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_types[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->cap->vconn_set) { + dev_dbg(dev, "VCONN swapping not supported\n"); + return -EOPNOTSUPP; + } + + ret = kstrtobool(buf, &source); + if (ret) + return ret; + + ret = port->cap->vconn_set(port->cap, (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) +{ + struct typec_port *p = to_typec_port(dev); + + return sprintf(buf, "%d\n", (p->cap->pd_revision >> 8) & 0xff); +} +static DEVICE_ATTR_RO(usb_power_delivery_revision); + +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, + NULL, +}; +ATTRIBUTE_GROUPS(typec); + +static int typec_uevent(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_simple_remove(&typec_index_ida, port->id); + typec_switch_put(port->sw); + typec_mux_put(port->mux); + kfree(port); +} + +static 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 */ + +/** + * 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) +{ + 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); +} +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_pwr_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); + +static int partner_match(struct device *dev, void *data) +{ + return is_typec_partner(dev); +} + +/** + * 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"); + } + put_device(partner_dev); + } +} +EXPORT_SYMBOL_GPL(typec_set_pwr_opmode); + +/* ------------------------------------------ */ +/* 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; + + if (port->sw) { + ret = port->sw->set(port->sw, orientation); + if (ret) + return ret; + } + + port->orientation = orientation; + + return 0; +} +EXPORT_SYMBOL_GPL(typec_set_orientation); + +/** + * typec_set_mode - Set mode of operation for USB Type-C connector + * @port: USB Type-C port for the connector + * @mode: Operation mode for the connector + * + * Set mode @mode for @port. This function will configure the muxes needed to + * enter @mode. + */ +int typec_set_mode(struct typec_port *port, int mode) +{ + return port->mux ? port->mux->set(port->mux, mode) : 0; +} +EXPORT_SYMBOL_GPL(typec_set_mode); + +/* --------------------------------------- */ + +/** + * 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) +{ + return typec_register_altmode(&port->dev, desc); +} +EXPORT_SYMBOL_GPL(typec_port_register_altmode); + +/** + * 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 role; + int ret; + int id; + + port = kzalloc(sizeof(*port), GFP_KERNEL); + if (!port) + return ERR_PTR(-ENOMEM); + + id = ida_simple_get(&typec_index_ida, 0, 0, GFP_KERNEL); + if (id < 0) { + kfree(port); + return ERR_PTR(id); + } + + port->sw = typec_switch_get(cap->fwnode ? &port->dev : parent); + if (IS_ERR(port->sw)) { + ret = PTR_ERR(port->sw); + goto err_switch; + } + + port->mux = typec_mux_get(cap->fwnode ? &port->dev : parent); + if (IS_ERR(port->mux)) { + ret = PTR_ERR(port->mux); + goto err_mux; + } + + if (cap->type == TYPEC_PORT_DFP) + role = TYPEC_SOURCE; + else if (cap->type == TYPEC_PORT_UFP) + role = TYPEC_SINK; + else + role = cap->prefer_role; + + if (role == TYPEC_SOURCE) { + port->data_role = TYPEC_HOST; + port->pwr_role = TYPEC_SOURCE; + port->vconn_role = TYPEC_SOURCE; + } else { + port->data_role = TYPEC_DEVICE; + port->pwr_role = TYPEC_SINK; + port->vconn_role = TYPEC_SINK; + } + + port->id = id; + port->cap = cap; + port->port_type = cap->type; + mutex_init(&port->port_type_lock); + port->prefer_role = cap->prefer_role; + + 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); + + ret = device_register(&port->dev); + if (ret) { + dev_err(parent, "failed to register port (%d)\n", ret); + put_device(&port->dev); + return ERR_PTR(ret); + } + + return port; + +err_mux: + typec_switch_put(port->sw); + +err_switch: + ida_simple_remove(&typec_index_ida, port->id); + kfree(port); + + return ERR_PTR(ret); +} +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)) + device_unregister(&port->dev); +} +EXPORT_SYMBOL_GPL(typec_unregister_port); + +static int __init typec_init(void) +{ + typec_class = class_create(THIS_MODULE, "typec"); + return PTR_ERR_OR_ZERO(typec_class); +} +subsys_initcall(typec_init); + +static void __exit typec_exit(void) +{ + class_destroy(typec_class); + ida_destroy(&typec_index_ida); +} +module_exit(typec_exit); + +MODULE_AUTHOR("Heikki Krogerus "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("USB Type-C Connector Class"); diff --git a/drivers/usb/typec/mux.c b/drivers/usb/typec/mux.c new file mode 100644 index 000000000000..f89093bd7185 --- /dev/null +++ b/drivers/usb/typec/mux.c @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * USB Type-C Multiplexer/DeMultiplexer Switch support + * + * Copyright (C) 2018 Intel Corporation + * Author: Heikki Krogerus + * Hans de Goede + */ + +#include +#include +#include +#include + +static DEFINE_MUTEX(switch_lock); +static DEFINE_MUTEX(mux_lock); +static LIST_HEAD(switch_list); +static LIST_HEAD(mux_list); + +static void *typec_switch_match(struct device_connection *con, int ep, + void *data) +{ + struct typec_switch *sw; + + list_for_each_entry(sw, &switch_list, entry) + if (!strcmp(con->endpoint[ep], dev_name(sw->dev))) + return sw; + + /* + * We only get called if a connection was found, tell the caller to + * wait for the switch to show up. + */ + return ERR_PTR(-EPROBE_DEFER); +} + +/** + * typec_switch_get - Find USB Type-C orientation switch + * @dev: The caller device + * + * Finds a switch linked with @dev. Returns a reference to the switch on + * success, NULL if no matching connection was found, or + * ERR_PTR(-EPROBE_DEFER) when a connection was found but the switch + * has not been enumerated yet. + */ +struct typec_switch *typec_switch_get(struct device *dev) +{ + struct typec_switch *sw; + + mutex_lock(&switch_lock); + sw = device_connection_find_match(dev, "typec-switch", NULL, + typec_switch_match); + if (!IS_ERR_OR_NULL(sw)) + get_device(sw->dev); + mutex_unlock(&switch_lock); + + return sw; +} +EXPORT_SYMBOL_GPL(typec_switch_get); + +/** + * typec_put_switch - Release USB Type-C orientation switch + * @sw: USB Type-C orientation switch + * + * Decrement reference count for @sw. + */ +void typec_switch_put(struct typec_switch *sw) +{ + if (!IS_ERR_OR_NULL(sw)) + put_device(sw->dev); +} +EXPORT_SYMBOL_GPL(typec_switch_put); + +/** + * typec_switch_register - Register USB Type-C orientation switch + * @sw: USB Type-C orientation switch + * + * This function registers a switch that can be used for routing the correct + * data pairs depending on the cable plug orientation from the USB Type-C + * connector to the USB controllers. USB Type-C plugs can be inserted + * right-side-up or upside-down. + */ +int typec_switch_register(struct typec_switch *sw) +{ + mutex_lock(&switch_lock); + list_add_tail(&sw->entry, &switch_list); + mutex_unlock(&switch_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(typec_switch_register); + +/** + * typec_switch_unregister - Unregister USB Type-C orientation switch + * @sw: USB Type-C orientation switch + * + * Unregister switch that was registered with typec_switch_register(). + */ +void typec_switch_unregister(struct typec_switch *sw) +{ + mutex_lock(&switch_lock); + list_del(&sw->entry); + mutex_unlock(&switch_lock); +} +EXPORT_SYMBOL_GPL(typec_switch_unregister); + +/* ------------------------------------------------------------------------- */ + +static void *typec_mux_match(struct device_connection *con, int ep, void *data) +{ + struct typec_mux *mux; + + list_for_each_entry(mux, &mux_list, entry) + if (!strcmp(con->endpoint[ep], dev_name(mux->dev))) + return mux; + + /* + * We only get called if a connection was found, tell the caller to + * wait for the switch to show up. + */ + return ERR_PTR(-EPROBE_DEFER); +} + +/** + * typec_mux_get - Find USB Type-C Multiplexer + * @dev: The caller device + * + * Finds a mux linked to the caller. This function is primarily meant for the + * Type-C drivers. Returns a reference to the mux on success, NULL if no + * matching connection was found, or ERR_PTR(-EPROBE_DEFER) when a connection + * was found but the mux has not been enumerated yet. + */ +struct typec_mux *typec_mux_get(struct device *dev) +{ + struct typec_mux *mux; + + mutex_lock(&mux_lock); + mux = device_connection_find_match(dev, "typec-mux", NULL, + typec_mux_match); + if (!IS_ERR_OR_NULL(mux)) + get_device(mux->dev); + mutex_unlock(&mux_lock); + + return mux; +} +EXPORT_SYMBOL_GPL(typec_mux_get); + +/** + * typec_mux_put - Release handle to a Multiplexer + * @mux: USB Type-C Connector Multiplexer/DeMultiplexer + * + * Decrements reference count for @mux. + */ +void typec_mux_put(struct typec_mux *mux) +{ + if (!IS_ERR_OR_NULL(mux)) + put_device(mux->dev); +} +EXPORT_SYMBOL_GPL(typec_mux_put); + +/** + * typec_mux_register - Register Multiplexer routing USB Type-C pins + * @mux: USB Type-C Connector Multiplexer/DeMultiplexer + * + * USB Type-C connectors can be used for alternate modes of operation besides + * USB when Accessory/Alternate Modes are supported. With some of those modes, + * the pins on the connector need to be reconfigured. This function registers + * multiplexer switches routing the pins on the connector. + */ +int typec_mux_register(struct typec_mux *mux) +{ + mutex_lock(&mux_lock); + list_add_tail(&mux->entry, &mux_list); + mutex_unlock(&mux_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(typec_mux_register); + +/** + * typec_mux_unregister - Unregister Multiplexer Switch + * @sw: USB Type-C Connector Multiplexer/DeMultiplexer + * + * Unregister mux that was registered with typec_mux_register(). + */ +void typec_mux_unregister(struct typec_mux *mux) +{ + mutex_lock(&mux_lock); + list_del(&mux->entry); + mutex_unlock(&mux_lock); +} +EXPORT_SYMBOL_GPL(typec_mux_unregister); diff --git a/drivers/usb/typec/typec.c b/drivers/usb/typec/typec.c deleted file mode 100644 index dc28ad868d93..000000000000 --- a/drivers/usb/typec/typec.c +++ /dev/null @@ -1,1365 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * USB Type-C Connector Class - * - * Copyright (C) 2017, Intel Corporation - * Author: Heikki Krogerus - */ - -#include -#include -#include -#include -#include - -struct typec_mode { - int index; - u32 vdo; - char *desc; - enum typec_port_type roles; - - struct typec_altmode *alt_mode; - - unsigned int active:1; - - char group_name[6]; - struct attribute_group group; - struct attribute *attrs[5]; - struct device_attribute vdo_attr; - struct device_attribute desc_attr; - struct device_attribute active_attr; - struct device_attribute roles_attr; -}; - -struct typec_altmode { - struct device dev; - u16 svid; - int n_modes; - struct typec_mode modes[ALTMODE_MAX_MODES]; - const struct attribute_group *mode_groups[ALTMODE_MAX_MODES]; -}; - -struct typec_plug { - struct device dev; - enum typec_plug_index index; -}; - -struct typec_cable { - struct device dev; - enum typec_plug_type type; - struct usb_pd_identity *identity; - unsigned int active:1; -}; - -struct typec_partner { - struct device dev; - unsigned int usb_pd:1; - struct usb_pd_identity *identity; - enum typec_accessory accessory; -}; - -struct typec_port { - unsigned int id; - struct device dev; - - int prefer_role; - enum typec_data_role data_role; - enum typec_role pwr_role; - enum typec_role vconn_role; - enum typec_pwr_opmode pwr_opmode; - enum typec_port_type port_type; - struct mutex port_type_lock; - - const struct typec_capability *cap; -}; - -#define to_typec_port(_dev_) container_of(_dev_, struct typec_port, 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_partner(_dev_) container_of(_dev_, struct typec_partner, dev) -#define to_altmode(_dev_) container_of(_dev_, struct typec_altmode, dev) - -static const struct device_type typec_partner_dev_type; -static const struct device_type typec_cable_dev_type; -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_cable(_dev_) (_dev_->type == &typec_cable_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 struct class *typec_class; - -/* Common attributes */ - -static const char * const typec_accessory_modes[] = { - [TYPEC_ACCESSORY_NONE] = "none", - [TYPEC_ACCESSORY_AUDIO] = "analog_audio", - [TYPEC_ACCESSORY_DEBUG] = "debug", -}; - -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 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 struct attribute *usb_pd_id_attrs[] = { - &dev_attr_id_header.attr, - &dev_attr_cert_stat.attr, - &dev_attr_product.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_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"); -} - -/* ------------------------------------------------------------------------- */ -/* Alternate Modes */ - -/** - * typec_altmode_update_active - Report Enter/Exit mode - * @alt: Handle to the alternate mode - * @mode: Mode index - * @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 *alt, int mode, - bool active) -{ - struct typec_mode *m = &alt->modes[mode]; - char dir[6]; - - if (m->active == active) - return; - - m->active = active; - snprintf(dir, sizeof(dir), "mode%d", mode); - sysfs_notify(&alt->dev.kobj, dir, "active"); - kobject_uevent(&alt->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 -typec_altmode_vdo_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct typec_mode *mode = container_of(attr, struct typec_mode, - vdo_attr); - - return sprintf(buf, "0x%08x\n", mode->vdo); -} - -static ssize_t -typec_altmode_desc_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct typec_mode *mode = container_of(attr, struct typec_mode, - desc_attr); - - return sprintf(buf, "%s\n", mode->desc ? mode->desc : ""); -} - -static ssize_t -typec_altmode_active_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct typec_mode *mode = container_of(attr, struct typec_mode, - active_attr); - - return sprintf(buf, "%s\n", mode->active ? "yes" : "no"); -} - -static ssize_t -typec_altmode_active_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t size) -{ - struct typec_mode *mode = container_of(attr, struct typec_mode, - active_attr); - struct typec_port *port = typec_altmode2port(mode->alt_mode); - bool activate; - int ret; - - if (!port->cap->activate_mode) - return -EOPNOTSUPP; - - ret = kstrtobool(buf, &activate); - if (ret) - return ret; - - ret = port->cap->activate_mode(port->cap, mode->index, activate); - if (ret) - return ret; - - return size; -} - -static ssize_t -typec_altmode_roles_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct typec_mode *mode = container_of(attr, struct typec_mode, - roles_attr); - ssize_t ret; - - switch (mode->roles) { - case TYPEC_PORT_DFP: - ret = sprintf(buf, "source\n"); - break; - case TYPEC_PORT_UFP: - ret = sprintf(buf, "sink\n"); - break; - case TYPEC_PORT_DRP: - default: - ret = sprintf(buf, "source sink\n"); - break; - } - return ret; -} - -static void typec_init_modes(struct typec_altmode *alt, - const struct typec_mode_desc *desc, bool is_port) -{ - int i; - - for (i = 0; i < alt->n_modes; i++, desc++) { - struct typec_mode *mode = &alt->modes[i]; - - /* Not considering the human readable description critical */ - mode->desc = kstrdup(desc->desc, GFP_KERNEL); - if (desc->desc && !mode->desc) - dev_err(&alt->dev, "failed to copy mode%d desc\n", i); - - mode->alt_mode = alt; - mode->vdo = desc->vdo; - mode->roles = desc->roles; - mode->index = desc->index; - sprintf(mode->group_name, "mode%d", desc->index); - - sysfs_attr_init(&mode->vdo_attr.attr); - mode->vdo_attr.attr.name = "vdo"; - mode->vdo_attr.attr.mode = 0444; - mode->vdo_attr.show = typec_altmode_vdo_show; - - sysfs_attr_init(&mode->desc_attr.attr); - mode->desc_attr.attr.name = "description"; - mode->desc_attr.attr.mode = 0444; - mode->desc_attr.show = typec_altmode_desc_show; - - sysfs_attr_init(&mode->active_attr.attr); - mode->active_attr.attr.name = "active"; - mode->active_attr.attr.mode = 0644; - mode->active_attr.show = typec_altmode_active_show; - mode->active_attr.store = typec_altmode_active_store; - - mode->attrs[0] = &mode->vdo_attr.attr; - mode->attrs[1] = &mode->desc_attr.attr; - mode->attrs[2] = &mode->active_attr.attr; - - /* With ports, list the roles that the mode is supported with */ - if (is_port) { - sysfs_attr_init(&mode->roles_attr.attr); - mode->roles_attr.attr.name = "supported_roles"; - mode->roles_attr.attr.mode = 0444; - mode->roles_attr.show = typec_altmode_roles_show; - - mode->attrs[3] = &mode->roles_attr.attr; - } - - mode->group.attrs = mode->attrs; - mode->group.name = mode->group_name; - - alt->mode_groups[i] = &mode->group; - } -} - -static ssize_t svid_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct typec_altmode *alt = to_altmode(dev); - - return sprintf(buf, "%04x\n", alt->svid); -} -static DEVICE_ATTR_RO(svid); - -static struct attribute *typec_altmode_attrs[] = { - &dev_attr_svid.attr, - NULL -}; -ATTRIBUTE_GROUPS(typec_altmode); - -static void typec_altmode_release(struct device *dev) -{ - struct typec_altmode *alt = to_altmode(dev); - int i; - - for (i = 0; i < alt->n_modes; i++) - kfree(alt->modes[i].desc); - kfree(alt); -} - -static 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) -{ - struct typec_altmode *alt; - int ret; - - alt = kzalloc(sizeof(*alt), GFP_KERNEL); - if (!alt) - return ERR_PTR(-ENOMEM); - - alt->svid = desc->svid; - alt->n_modes = desc->n_modes; - typec_init_modes(alt, desc->modes, is_typec_port(parent)); - - alt->dev.parent = parent; - alt->dev.groups = alt->mode_groups; - alt->dev.type = &typec_altmode_dev_type; - dev_set_name(&alt->dev, "svid-%04x", alt->svid); - - ret = device_register(&alt->dev); - if (ret) { - dev_err(parent, "failed to register alternate mode (%d)\n", - ret); - put_device(&alt->dev); - return ERR_PTR(ret); - } - - return alt; -} - -/** - * typec_unregister_altmode - Unregister Alternate Mode - * @alt: 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 *alt) -{ - if (!IS_ERR_OR_NULL(alt)) - device_unregister(&alt->dev); -} -EXPORT_SYMBOL_GPL(typec_unregister_altmode); - -/* ------------------------------------------------------------------------- */ -/* Type-C Partners */ - -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 struct attribute *typec_partner_attrs[] = { - &dev_attr_accessory_mode.attr, - &dev_attr_supports_usb_power_delivery.attr, - NULL -}; -ATTRIBUTE_GROUPS(typec_partner); - -static void typec_partner_release(struct device *dev) -{ - struct typec_partner *partner = to_typec_partner(dev); - - kfree(partner); -} - -static const struct device_type typec_partner_dev_type = { - .name = "typec_partner", - .groups = typec_partner_groups, - .release = typec_partner_release, -}; - -/** - * 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) -{ - if (!partner->identity) - return -EINVAL; - - typec_report_identity(&partner->dev); - return 0; -} -EXPORT_SYMBOL_GPL(typec_partner_set_identity); - -/** - * 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 NULL 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_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); - - partner->usb_pd = desc->usb_pd; - partner->accessory = desc->accessory; - - 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)); - - 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); - } - - 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) -{ - if (!IS_ERR_OR_NULL(partner)) - 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); - - kfree(plug); -} - -static const struct device_type typec_plug_dev_type = { - .name = "typec_plug", - .release = typec_plug_release, -}; - -/** - * 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); - - 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 ssize_t -type_show(struct device *dev, struct device_attribute *attr, char *buf) -{ - struct typec_cable *cable = to_typec_cable(dev); - - return sprintf(buf, "%s\n", cable->active ? "active" : "passive"); -} -static DEVICE_ATTR_RO(type); - -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, - NULL -}; -ATTRIBUTE_GROUPS(typec_cable); - -static void typec_cable_release(struct device *dev) -{ - struct typec_cable *cable = to_typec_cable(dev); - - kfree(cable); -} - -static const struct device_type typec_cable_dev_type = { - .name = "typec_cable", - .groups = typec_cable_groups, - .release = typec_cable_release, -}; - -/** - * 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; - - 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 */ - -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_types[] = { - [TYPEC_PORT_DFP] = "source", - [TYPEC_PORT_UFP] = "sink", - [TYPEC_PORT_DRP] = "dual", -}; - -static const char * const typec_port_types_drp[] = { - [TYPEC_PORT_DFP] = "dual [source] sink", - [TYPEC_PORT_UFP] = "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->cap->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->cap->try_role(port->cap, 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->cap->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->port_type != TYPEC_PORT_DRP) { - dev_dbg(dev, "port type fixed at \"%s\"", - typec_port_types[port->port_type]); - ret = -EOPNOTSUPP; - goto unlock_and_ret; - } - - ret = port->cap->dr_set(port->cap, 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->type == TYPEC_PORT_DRP) - 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->cap->pd_revision) { - dev_dbg(dev, "USB Power Delivery not supported\n"); - return -EOPNOTSUPP; - } - - if (!port->cap->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_types[port->port_type]); - ret = -EOPNOTSUPP; - goto unlock_and_ret; - } - - ret = port->cap->pr_set(port->cap, 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->port_type_set || port->cap->type != TYPEC_PORT_DRP) { - dev_dbg(dev, "changing port type not supported\n"); - return -EOPNOTSUPP; - } - - ret = sysfs_match_string(typec_port_types, 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->cap->port_type_set(port->cap, 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_types[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->cap->vconn_set) { - dev_dbg(dev, "VCONN swapping not supported\n"); - return -EOPNOTSUPP; - } - - ret = kstrtobool(buf, &source); - if (ret) - return ret; - - ret = port->cap->vconn_set(port->cap, (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) -{ - struct typec_port *p = to_typec_port(dev); - - return sprintf(buf, "%d\n", (p->cap->pd_revision >> 8) & 0xff); -} -static DEVICE_ATTR_RO(usb_power_delivery_revision); - -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, - NULL, -}; -ATTRIBUTE_GROUPS(typec); - -static int typec_uevent(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_simple_remove(&typec_index_ida, port->id); - kfree(port); -} - -static 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 */ - -/** - * 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) -{ - 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); -} -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_pwr_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); - -static int partner_match(struct device *dev, void *data) -{ - return is_typec_partner(dev); -} - -/** - * 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"); - } - put_device(partner_dev); - } -} -EXPORT_SYMBOL_GPL(typec_set_pwr_opmode); - -/* --------------------------------------- */ - -/** - * typec_port_register_altmode - Register USB Type-C Port Alternate Mode - * @port: USB Type-C Port that supports the alternate mode - * @desc: Description of the alternate mode - * - * 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) -{ - return typec_register_altmode(&port->dev, desc); -} -EXPORT_SYMBOL_GPL(typec_port_register_altmode); - -/** - * 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 role; - int ret; - int id; - - port = kzalloc(sizeof(*port), GFP_KERNEL); - if (!port) - return ERR_PTR(-ENOMEM); - - id = ida_simple_get(&typec_index_ida, 0, 0, GFP_KERNEL); - if (id < 0) { - kfree(port); - return ERR_PTR(id); - } - - if (cap->type == TYPEC_PORT_DFP) - role = TYPEC_SOURCE; - else if (cap->type == TYPEC_PORT_UFP) - role = TYPEC_SINK; - else - role = cap->prefer_role; - - if (role == TYPEC_SOURCE) { - port->data_role = TYPEC_HOST; - port->pwr_role = TYPEC_SOURCE; - port->vconn_role = TYPEC_SOURCE; - } else { - port->data_role = TYPEC_DEVICE; - port->pwr_role = TYPEC_SINK; - port->vconn_role = TYPEC_SINK; - } - - port->id = id; - port->cap = cap; - port->port_type = cap->type; - mutex_init(&port->port_type_lock); - port->prefer_role = cap->prefer_role; - - 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); - - ret = device_register(&port->dev); - if (ret) { - dev_err(parent, "failed to register port (%d)\n", ret); - put_device(&port->dev); - return ERR_PTR(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)) - device_unregister(&port->dev); -} -EXPORT_SYMBOL_GPL(typec_unregister_port); - -static int __init typec_init(void) -{ - typec_class = class_create(THIS_MODULE, "typec"); - return PTR_ERR_OR_ZERO(typec_class); -} -subsys_initcall(typec_init); - -static void __exit typec_exit(void) -{ - class_destroy(typec_class); - ida_destroy(&typec_index_ida); -} -module_exit(typec_exit); - -MODULE_AUTHOR("Heikki Krogerus "); -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("USB Type-C Connector Class"); diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h index 0d44ce6af08f..2408e5c2ed91 100644 --- a/include/linux/usb/typec.h +++ b/include/linux/usb/typec.h @@ -60,6 +60,12 @@ enum typec_accessory { #define TYPEC_MAX_ACCESSORY 3 +enum typec_orientation { + TYPEC_ORIENTATION_NONE, + TYPEC_ORIENTATION_NORMAL, + TYPEC_ORIENTATION_REVERSE, +}; + /* * struct usb_pd_identity - USB Power Delivery identity data * @id_header: ID Header VDO @@ -185,6 +191,8 @@ struct typec_partner_desc { * @pd_revision: USB Power Delivery Specification revision if supported * @prefer_role: Initial role preference * @accessory: Supported Accessory Modes + * @sw: Cable plug orientation switch + * @mux: Multiplexer switch for Alternate/Accessory Modes * @fwnode: Optional fwnode of the port * @try_role: Set data role preference for DRP port * @dr_set: Set Data Role @@ -202,6 +210,8 @@ struct typec_capability { int prefer_role; enum typec_accessory accessory[TYPEC_MAX_ACCESSORY]; + struct typec_switch *sw; + struct typec_mux *mux; struct fwnode_handle *fwnode; int (*try_role)(const struct typec_capability *, @@ -245,4 +255,8 @@ void typec_set_pwr_role(struct typec_port *port, enum typec_role role); void typec_set_vconn_role(struct typec_port *port, enum typec_role role); void typec_set_pwr_opmode(struct typec_port *port, enum typec_pwr_opmode mode); +int typec_set_orientation(struct typec_port *port, + enum typec_orientation orientation); +int typec_set_mode(struct typec_port *port, int mode); + #endif /* __LINUX_USB_TYPEC_H */ diff --git a/include/linux/usb/typec_mux.h b/include/linux/usb/typec_mux.h new file mode 100644 index 000000000000..12c1b057834b --- /dev/null +++ b/include/linux/usb/typec_mux.h @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-2.0 + +#ifndef __USB_TYPEC_MUX +#define __USB_TYPEC_MUX + +#include +#include + +struct device; + +/** + * struct typec_switch - USB Type-C cable orientation switch + * @dev: Switch device + * @entry: List entry + * @set: Callback to the driver for setting the orientation + * + * USB Type-C pin flipper switch routing the correct data pairs from the + * connector to the USB controller depending on the orientation of the cable + * plug. + */ +struct typec_switch { + struct device *dev; + struct list_head entry; + + int (*set)(struct typec_switch *sw, enum typec_orientation orientation); +}; + +/** + * struct typec_switch - USB Type-C connector pin mux + * @dev: Mux device + * @entry: List entry + * @set: Callback to the driver for setting the state of the mux + * + * Pin Multiplexer/DeMultiplexer switch routing the USB Type-C connector pins to + * different components depending on the requested mode of operation. Used with + * Accessory/Alternate modes. + */ +struct typec_mux { + struct device *dev; + struct list_head entry; + + int (*set)(struct typec_mux *mux, int state); +}; + +struct typec_switch *typec_switch_get(struct device *dev); +void typec_switch_put(struct typec_switch *sw); +int typec_switch_register(struct typec_switch *sw); +void typec_switch_unregister(struct typec_switch *sw); + +struct typec_mux *typec_mux_get(struct device *dev); +void typec_mux_put(struct typec_mux *mux); +int typec_mux_register(struct typec_mux *mux); +void typec_mux_unregister(struct typec_mux *mux); + +#endif /* __USB_TYPEC_MUX */ -- cgit v1.2.3-71-gd317 From fde0aa6c175a4d8aa19e82b86ae0f9278bc8563b Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Tue, 20 Mar 2018 15:57:04 +0300 Subject: usb: common: Small class for USB role switches USB role switch is a device that can be used to choose the data role for USB connector. With dual-role capable USB controllers, the controller itself will be the switch, but on some platforms the USB host and device controllers are separate IPs and there is a mux between them and the connector. On those platforms the mux driver will need to register the switch. With USB Type-C connectors, the host-to-device relationship is negotiated over the Configuration Channel (CC). That means the USB Type-C drivers need to be in control of the role switch. The class provides a simple API for the USB Type-C drivers for the control. For other types of USB connectors (mainly microAB) the class provides user space control via sysfs attribute file that can be used to request role swapping from the switch. Reviewed-by: Hans de Goede Reviewed-by: Andy Shevchenko Signed-off-by: Heikki Krogerus Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-class-usb_role | 21 ++ drivers/usb/Kconfig | 3 + drivers/usb/common/Makefile | 1 + drivers/usb/common/roles.c | 305 +++++++++++++++++++++++++ include/linux/usb/role.h | 53 +++++ 5 files changed, 383 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-class-usb_role create mode 100644 drivers/usb/common/roles.c create mode 100644 include/linux/usb/role.h (limited to 'include/linux') diff --git a/Documentation/ABI/testing/sysfs-class-usb_role b/Documentation/ABI/testing/sysfs-class-usb_role new file mode 100644 index 000000000000..3b810a425a52 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-class-usb_role @@ -0,0 +1,21 @@ +What: /sys/class/usb_role/ +Date: Jan 2018 +Contact: Heikki Krogerus +Description: + Place in sysfs for USB Role Switches. USB Role Switch is a + device that can select the data role (host or device) for USB + port. + +What: /sys/class/usb_role//role +Date: Jan 2018 +Contact: Heikki Krogerus +Description: + The current role of the switch. This attribute can be used for + requesting role swapping with non-USB Type-C ports. With USB + Type-C ports, the ABI defined for USB Type-C connector class + must be used. + + Valid values: + - none + - host + - device diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig index 148f3ee70286..f278958e04ca 100644 --- a/drivers/usb/Kconfig +++ b/drivers/usb/Kconfig @@ -203,4 +203,7 @@ config USB_ULPI_BUS To compile this driver as a module, choose M here: the module will be called ulpi. +config USB_ROLE_SWITCH + tristate + endif # USB_SUPPORT diff --git a/drivers/usb/common/Makefile b/drivers/usb/common/Makefile index 0a7c45e85481..fb4d5ef4165c 100644 --- a/drivers/usb/common/Makefile +++ b/drivers/usb/common/Makefile @@ -9,3 +9,4 @@ usb-common-$(CONFIG_USB_LED_TRIG) += led.o obj-$(CONFIG_USB_OTG_FSM) += usb-otg-fsm.o obj-$(CONFIG_USB_ULPI_BUS) += ulpi.o +obj-$(CONFIG_USB_ROLE_SWITCH) += roles.o diff --git a/drivers/usb/common/roles.c b/drivers/usb/common/roles.c new file mode 100644 index 000000000000..15cc76e22123 --- /dev/null +++ b/drivers/usb/common/roles.c @@ -0,0 +1,305 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * USB Role Switch Support + * + * Copyright (C) 2018 Intel Corporation + * Author: Heikki Krogerus + * Hans de Goede + */ + +#include +#include +#include +#include +#include + +static struct class *role_class; + +struct usb_role_switch { + struct device dev; + struct mutex lock; /* device lock*/ + enum usb_role role; + + /* From descriptor */ + struct device *usb2_port; + struct device *usb3_port; + struct device *udc; + usb_role_switch_set_t set; + usb_role_switch_get_t get; + bool allow_userspace_control; +}; + +#define to_role_switch(d) container_of(d, struct usb_role_switch, dev) + +/** + * usb_role_switch_set_role - Set USB role for a switch + * @sw: USB role switch + * @role: USB role to be switched to + * + * Set USB role @role for @sw. + */ +int usb_role_switch_set_role(struct usb_role_switch *sw, enum usb_role role) +{ + int ret; + + if (IS_ERR_OR_NULL(sw)) + return 0; + + mutex_lock(&sw->lock); + + ret = sw->set(sw->dev.parent, role); + if (!ret) + sw->role = role; + + mutex_unlock(&sw->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(usb_role_switch_set_role); + +/** + * usb_role_switch_get_role - Get the USB role for a switch + * @sw: USB role switch + * + * Depending on the role-switch-driver this function returns either a cached + * value of the last set role, or reads back the actual value from the hardware. + */ +enum usb_role usb_role_switch_get_role(struct usb_role_switch *sw) +{ + enum usb_role role; + + if (IS_ERR_OR_NULL(sw)) + return USB_ROLE_NONE; + + mutex_lock(&sw->lock); + + if (sw->get) + role = sw->get(sw->dev.parent); + else + role = sw->role; + + mutex_unlock(&sw->lock); + + return role; +} +EXPORT_SYMBOL_GPL(usb_role_switch_get_role); + +static int __switch_match(struct device *dev, const void *name) +{ + return !strcmp((const char *)name, dev_name(dev)); +} + +static void *usb_role_switch_match(struct device_connection *con, int ep, + void *data) +{ + struct device *dev; + + dev = class_find_device(role_class, NULL, con->endpoint[ep], + __switch_match); + + return dev ? to_role_switch(dev) : ERR_PTR(-EPROBE_DEFER); +} + +/** + * usb_role_switch_get - Find USB role switch linked with the caller + * @dev: The caller device + * + * Finds and returns role switch linked with @dev. The reference count for the + * found switch is incremented. + */ +struct usb_role_switch *usb_role_switch_get(struct device *dev) +{ + return device_connection_find_match(dev, "usb-role-switch", NULL, + usb_role_switch_match); +} +EXPORT_SYMBOL_GPL(usb_role_switch_get); + +/** + * usb_role_switch_put - Release handle to a switch + * @sw: USB Role Switch + * + * Decrement reference count for @sw. + */ +void usb_role_switch_put(struct usb_role_switch *sw) +{ + if (!IS_ERR_OR_NULL(sw)) + put_device(&sw->dev); +} +EXPORT_SYMBOL_GPL(usb_role_switch_put); + +static umode_t +usb_role_switch_is_visible(struct kobject *kobj, struct attribute *attr, int n) +{ + struct device *dev = container_of(kobj, typeof(*dev), kobj); + struct usb_role_switch *sw = to_role_switch(dev); + + if (sw->allow_userspace_control) + return attr->mode; + + return 0; +} + +static const char * const usb_roles[] = { + [USB_ROLE_NONE] = "none", + [USB_ROLE_HOST] = "host", + [USB_ROLE_DEVICE] = "device", +}; + +static ssize_t +role_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct usb_role_switch *sw = to_role_switch(dev); + enum usb_role role = usb_role_switch_get_role(sw); + + return sprintf(buf, "%s\n", usb_roles[role]); +} + +static ssize_t role_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct usb_role_switch *sw = to_role_switch(dev); + int ret; + + ret = sysfs_match_string(usb_roles, buf); + if (ret < 0) { + bool res; + + /* Extra check if the user wants to disable the switch */ + ret = kstrtobool(buf, &res); + if (ret || res) + return -EINVAL; + } + + ret = usb_role_switch_set_role(sw, ret); + if (ret) + return ret; + + return size; +} +static DEVICE_ATTR_RW(role); + +static struct attribute *usb_role_switch_attrs[] = { + &dev_attr_role.attr, + NULL, +}; + +static const struct attribute_group usb_role_switch_group = { + .is_visible = usb_role_switch_is_visible, + .attrs = usb_role_switch_attrs, +}; + +static const struct attribute_group *usb_role_switch_groups[] = { + &usb_role_switch_group, + NULL, +}; + +static int +usb_role_switch_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + int ret; + + ret = add_uevent_var(env, "USB_ROLE_SWITCH=%s", dev_name(dev)); + if (ret) + dev_err(dev, "failed to add uevent USB_ROLE_SWITCH\n"); + + return ret; +} + +static void usb_role_switch_release(struct device *dev) +{ + struct usb_role_switch *sw = to_role_switch(dev); + + kfree(sw); +} + +static const struct device_type usb_role_dev_type = { + .name = "usb_role_switch", + .groups = usb_role_switch_groups, + .uevent = usb_role_switch_uevent, + .release = usb_role_switch_release, +}; + +/** + * usb_role_switch_register - Register USB Role Switch + * @parent: Parent device for the switch + * @desc: Description of the switch + * + * USB Role Switch is a device capable or choosing the role for USB connector. + * On platforms where the USB controller is dual-role capable, the controller + * driver will need to register the switch. On platforms where the USB host and + * USB device controllers behind the connector are separate, there will be a + * mux, and the driver for that mux will need to register the switch. + * + * Returns handle to a new role switch or ERR_PTR. The content of @desc is + * copied. + */ +struct usb_role_switch * +usb_role_switch_register(struct device *parent, + const struct usb_role_switch_desc *desc) +{ + struct usb_role_switch *sw; + int ret; + + if (!desc || !desc->set) + return ERR_PTR(-EINVAL); + + sw = kzalloc(sizeof(*sw), GFP_KERNEL); + if (!sw) + return ERR_PTR(-ENOMEM); + + mutex_init(&sw->lock); + + sw->allow_userspace_control = desc->allow_userspace_control; + sw->usb2_port = desc->usb2_port; + sw->usb3_port = desc->usb3_port; + sw->udc = desc->udc; + sw->set = desc->set; + sw->get = desc->get; + + sw->dev.parent = parent; + sw->dev.class = role_class; + sw->dev.type = &usb_role_dev_type; + dev_set_name(&sw->dev, "%s-role-switch", dev_name(parent)); + + ret = device_register(&sw->dev); + if (ret) { + put_device(&sw->dev); + return ERR_PTR(ret); + } + + /* TODO: Symlinks for the host port and the device controller. */ + + return sw; +} +EXPORT_SYMBOL_GPL(usb_role_switch_register); + +/** + * usb_role_switch_unregister - Unregsiter USB Role Switch + * @sw: USB Role Switch + * + * Unregister switch that was registered with usb_role_switch_register(). + */ +void usb_role_switch_unregister(struct usb_role_switch *sw) +{ + if (!IS_ERR_OR_NULL(sw)) + device_unregister(&sw->dev); +} +EXPORT_SYMBOL_GPL(usb_role_switch_unregister); + +static int __init usb_roles_init(void) +{ + role_class = class_create(THIS_MODULE, "usb_role"); + return PTR_ERR_OR_ZERO(role_class); +} +subsys_initcall(usb_roles_init); + +static void __exit usb_roles_exit(void) +{ + class_destroy(role_class); +} +module_exit(usb_roles_exit); + +MODULE_AUTHOR("Heikki Krogerus "); +MODULE_AUTHOR("Hans de Goede "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("USB Role Class"); diff --git a/include/linux/usb/role.h b/include/linux/usb/role.h new file mode 100644 index 000000000000..edc51be4a77c --- /dev/null +++ b/include/linux/usb/role.h @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-2.0 + +#ifndef __LINUX_USB_ROLE_H +#define __LINUX_USB_ROLE_H + +#include + +struct usb_role_switch; + +enum usb_role { + USB_ROLE_NONE, + USB_ROLE_HOST, + USB_ROLE_DEVICE, +}; + +typedef int (*usb_role_switch_set_t)(struct device *dev, enum usb_role role); +typedef enum usb_role (*usb_role_switch_get_t)(struct device *dev); + +/** + * struct usb_role_switch_desc - USB Role Switch Descriptor + * @usb2_port: Optional reference to the host controller port device (USB2) + * @usb3_port: Optional reference to the host controller port device (USB3) + * @udc: Optional reference to the peripheral controller device + * @set: Callback for setting the role + * @get: Callback for getting the role (optional) + * @allow_userspace_control: If true userspace may change the role through sysfs + * + * @usb2_port and @usb3_port will point to the USB host port and @udc to the USB + * device controller behind the USB connector with the role switch. If + * @usb2_port, @usb3_port and @udc are included in the description, the + * reference count for them should be incremented by the caller of + * usb_role_switch_register() before registering the switch. + */ +struct usb_role_switch_desc { + struct device *usb2_port; + struct device *usb3_port; + struct device *udc; + usb_role_switch_set_t set; + usb_role_switch_get_t get; + bool allow_userspace_control; +}; + +int usb_role_switch_set_role(struct usb_role_switch *sw, enum usb_role role); +enum usb_role usb_role_switch_get_role(struct usb_role_switch *sw); +struct usb_role_switch *usb_role_switch_get(struct device *dev); +void usb_role_switch_put(struct usb_role_switch *sw); + +struct usb_role_switch * +usb_role_switch_register(struct device *parent, + const struct usb_role_switch_desc *desc); +void usb_role_switch_unregister(struct usb_role_switch *sw); + +#endif /* __LINUX_USB_ROLE_H */ -- cgit v1.2.3-71-gd317 From ceeb162500c3480b660a47d509db7955a7913271 Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Tue, 20 Mar 2018 15:57:05 +0300 Subject: usb: typec: Separate the definitions for data and power roles USB Type-C specification v1.2 separated the power and data roles more clearly. Dual-Role-Data term was introduced, and the meaning of DRP was changed from "Dual-Role-Port" to "Dual-Role-Power". In order to allow the port drivers to describe the capabilities of the ports more clearly according to the newest specifications, introducing separate definitions for the data roles. Reviewed-by: Guenter Roeck Signed-off-by: Heikki Krogerus Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/class.c | 56 ++++++++++++++++++++++--------------- drivers/usb/typec/fusb302/fusb302.c | 1 + drivers/usb/typec/tcpm.c | 9 +++--- drivers/usb/typec/tps6598x.c | 26 +++++++++++------ drivers/usb/typec/typec_wcove.c | 1 + drivers/usb/typec/ucsi/ucsi.c | 13 +++++++-- include/linux/usb/tcpm.h | 1 + include/linux/usb/typec.h | 14 ++++++++-- 8 files changed, 80 insertions(+), 41 deletions(-) (limited to 'include/linux') diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c index 2620a694704f..53df10df2f9d 100644 --- a/drivers/usb/typec/class.c +++ b/drivers/usb/typec/class.c @@ -282,10 +282,10 @@ typec_altmode_roles_show(struct device *dev, struct device_attribute *attr, ssize_t ret; switch (mode->roles) { - case TYPEC_PORT_DFP: + case TYPEC_PORT_SRC: ret = sprintf(buf, "source\n"); break; - case TYPEC_PORT_UFP: + case TYPEC_PORT_SNK: ret = sprintf(buf, "sink\n"); break; case TYPEC_PORT_DRP: @@ -797,14 +797,14 @@ static const char * const typec_data_roles[] = { }; static const char * const typec_port_types[] = { - [TYPEC_PORT_DFP] = "source", - [TYPEC_PORT_UFP] = "sink", + [TYPEC_PORT_SRC] = "source", + [TYPEC_PORT_SNK] = "sink", [TYPEC_PORT_DRP] = "dual", }; static const char * const typec_port_types_drp[] = { - [TYPEC_PORT_DFP] = "dual [source] sink", - [TYPEC_PORT_UFP] = "dual source [sink]", + [TYPEC_PORT_SRC] = "dual [source] sink", + [TYPEC_PORT_SNK] = "dual source [sink]", [TYPEC_PORT_DRP] = "[dual] source sink", }; @@ -875,9 +875,7 @@ static ssize_t data_role_store(struct device *dev, 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_types[port->port_type]); + if (port->cap->data != TYPEC_PORT_DRD) { ret = -EOPNOTSUPP; goto unlock_and_ret; } @@ -897,7 +895,7 @@ static ssize_t data_role_show(struct device *dev, { struct typec_port *port = to_typec_port(dev); - if (port->cap->type == TYPEC_PORT_DRP) + if (port->cap->data == TYPEC_PORT_DRD) return sprintf(buf, "%s\n", port->data_role == TYPEC_HOST ? "[host] device" : "host [device]"); @@ -1328,7 +1326,6 @@ struct typec_port *typec_register_port(struct device *parent, const struct typec_capability *cap) { struct typec_port *port; - int role; int ret; int id; @@ -1354,21 +1351,36 @@ struct typec_port *typec_register_port(struct device *parent, goto err_mux; } - if (cap->type == TYPEC_PORT_DFP) - role = TYPEC_SOURCE; - else if (cap->type == TYPEC_PORT_UFP) - role = TYPEC_SINK; - else - role = cap->prefer_role; - - if (role == TYPEC_SOURCE) { - port->data_role = TYPEC_HOST; + switch (cap->type) { + case TYPEC_PORT_SRC: port->pwr_role = TYPEC_SOURCE; port->vconn_role = TYPEC_SOURCE; - } else { - port->data_role = TYPEC_DEVICE; + 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; } port->id = id; diff --git a/drivers/usb/typec/fusb302/fusb302.c b/drivers/usb/typec/fusb302/fusb302.c index 06794c06330f..82bf7c0ed53c 100644 --- a/drivers/usb/typec/fusb302/fusb302.c +++ b/drivers/usb/typec/fusb302/fusb302.c @@ -1219,6 +1219,7 @@ static const struct tcpc_config fusb302_tcpc_config = { .max_snk_mw = 15000, .operating_snk_mw = 2500, .type = TYPEC_PORT_DRP, + .data = TYPEC_PORT_DRD, .default_role = TYPEC_SINK, .alt_modes = NULL, }; diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c index 4c0fc5493d58..62e710bb6367 100644 --- a/drivers/usb/typec/tcpm.c +++ b/drivers/usb/typec/tcpm.c @@ -345,7 +345,7 @@ static enum tcpm_state tcpm_default_state(struct tcpm_port *port) else if (port->tcpc->config->default_role == TYPEC_SINK) return SNK_UNATTACHED; /* Fall through to return SRC_UNATTACHED */ - } else if (port->port_type == TYPEC_PORT_UFP) { + } else if (port->port_type == TYPEC_PORT_SNK) { return SNK_UNATTACHED; } return SRC_UNATTACHED; @@ -2179,7 +2179,7 @@ static inline enum tcpm_state unattached_state(struct tcpm_port *port) return SRC_UNATTACHED; else return SNK_UNATTACHED; - } else if (port->port_type == TYPEC_PORT_DFP) { + } else if (port->port_type == TYPEC_PORT_SRC) { return SRC_UNATTACHED; } @@ -3469,11 +3469,11 @@ static int tcpm_port_type_set(const struct typec_capability *cap, if (!port->connected) { tcpm_set_state(port, PORT_RESET, 0); - } else if (type == TYPEC_PORT_UFP) { + } else if (type == TYPEC_PORT_SNK) { if (!(port->pwr_role == TYPEC_SINK && port->data_role == TYPEC_DEVICE)) tcpm_set_state(port, PORT_RESET, 0); - } else if (type == TYPEC_PORT_DFP) { + } else if (type == TYPEC_PORT_SRC) { if (!(port->pwr_role == TYPEC_SOURCE && port->data_role == TYPEC_HOST)) tcpm_set_state(port, PORT_RESET, 0); @@ -3641,6 +3641,7 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) port->typec_caps.prefer_role = tcpc->config->default_role; port->typec_caps.type = tcpc->config->type; + port->typec_caps.data = tcpc->config->data; port->typec_caps.revision = 0x0120; /* Type-C spec release 1.2 */ port->typec_caps.pd_revision = 0x0200; /* USB-PD spec release 2.0 */ port->typec_caps.dr_set = tcpm_dr_set; diff --git a/drivers/usb/typec/tps6598x.c b/drivers/usb/typec/tps6598x.c index 37a15c14a6c6..8b8406867c02 100644 --- a/drivers/usb/typec/tps6598x.c +++ b/drivers/usb/typec/tps6598x.c @@ -393,31 +393,39 @@ static int tps6598x_probe(struct i2c_client *client) if (ret < 0) return ret; + tps->typec_cap.revision = USB_TYPEC_REV_1_2; + tps->typec_cap.pd_revision = 0x200; + tps->typec_cap.prefer_role = TYPEC_NO_PREFERRED_ROLE; + tps->typec_cap.pr_set = tps6598x_pr_set; + tps->typec_cap.dr_set = tps6598x_dr_set; + switch (TPS_SYSCONF_PORTINFO(conf)) { case TPS_PORTINFO_SINK_ACCESSORY: case TPS_PORTINFO_SINK: - tps->typec_cap.type = TYPEC_PORT_UFP; + tps->typec_cap.type = TYPEC_PORT_SNK; + tps->typec_cap.data = TYPEC_PORT_UFP; break; case TPS_PORTINFO_DRP_UFP_DRD: case TPS_PORTINFO_DRP_DFP_DRD: - tps->typec_cap.dr_set = tps6598x_dr_set; - /* fall through */ + tps->typec_cap.type = TYPEC_PORT_DRP; + tps->typec_cap.data = TYPEC_PORT_DRD; + break; case TPS_PORTINFO_DRP_UFP: + tps->typec_cap.type = TYPEC_PORT_DRP; + tps->typec_cap.data = TYPEC_PORT_UFP; + break; case TPS_PORTINFO_DRP_DFP: - tps->typec_cap.pr_set = tps6598x_pr_set; tps->typec_cap.type = TYPEC_PORT_DRP; + tps->typec_cap.data = TYPEC_PORT_DFP; break; case TPS_PORTINFO_SOURCE: - tps->typec_cap.type = TYPEC_PORT_DFP; + tps->typec_cap.type = TYPEC_PORT_SRC; + tps->typec_cap.data = TYPEC_PORT_DFP; break; default: return -ENODEV; } - tps->typec_cap.revision = USB_TYPEC_REV_1_2; - tps->typec_cap.pd_revision = 0x200; - tps->typec_cap.prefer_role = TYPEC_NO_PREFERRED_ROLE; - tps->port = typec_register_port(&client->dev, &tps->typec_cap); if (IS_ERR(tps->port)) return PTR_ERR(tps->port); diff --git a/drivers/usb/typec/typec_wcove.c b/drivers/usb/typec/typec_wcove.c index 2e990e0d917d..19cca7f1b2c5 100644 --- a/drivers/usb/typec/typec_wcove.c +++ b/drivers/usb/typec/typec_wcove.c @@ -572,6 +572,7 @@ static struct tcpc_config wcove_typec_config = { .operating_snk_mw = 15000, .type = TYPEC_PORT_DRP, + .data = TYPEC_PORT_DRD, .default_role = TYPEC_SINK, }; diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index 69d544cfcd45..bf0977fbd100 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -592,11 +592,18 @@ static int ucsi_register_port(struct ucsi *ucsi, int index) return ret; if (con->cap.op_mode & UCSI_CONCAP_OPMODE_DRP) - cap->type = TYPEC_PORT_DRP; + cap->data = TYPEC_PORT_DRD; else if (con->cap.op_mode & UCSI_CONCAP_OPMODE_DFP) - cap->type = TYPEC_PORT_DFP; + cap->data = TYPEC_PORT_DFP; else if (con->cap.op_mode & UCSI_CONCAP_OPMODE_UFP) - cap->type = TYPEC_PORT_UFP; + cap->data = TYPEC_PORT_UFP; + + if (con->cap.provider && con->cap.consumer) + cap->type = TYPEC_PORT_DRP; + else if (con->cap.provider) + cap->type = TYPEC_PORT_SRC; + else if (con->cap.consumer) + cap->type = TYPEC_PORT_SNK; cap->revision = ucsi->cap.typec_version; cap->pd_revision = ucsi->cap.pd_version; diff --git a/include/linux/usb/tcpm.h b/include/linux/usb/tcpm.h index ca1c0b57f03f..5a5e1d8c5b65 100644 --- a/include/linux/usb/tcpm.h +++ b/include/linux/usb/tcpm.h @@ -91,6 +91,7 @@ struct tcpc_config { unsigned int operating_snk_mw; enum typec_port_type type; + enum typec_port_data data; enum typec_role default_role; bool try_role_hw; /* try.{src,snk} implemented in hardware */ diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h index 2408e5c2ed91..672b39bb0adc 100644 --- a/include/linux/usb/typec.h +++ b/include/linux/usb/typec.h @@ -22,9 +22,15 @@ struct typec_port; struct fwnode_handle; enum typec_port_type { + TYPEC_PORT_SRC, + TYPEC_PORT_SNK, + TYPEC_PORT_DRP, +}; + +enum typec_port_data { TYPEC_PORT_DFP, TYPEC_PORT_UFP, - TYPEC_PORT_DRP, + TYPEC_PORT_DRD, }; enum typec_plug_type { @@ -186,10 +192,11 @@ struct typec_partner_desc { /* * struct typec_capability - USB Type-C Port Capabilities - * @role: DFP (Host-only), UFP (Device-only) or DRP (Dual Role) + * @type: Supported power role of the port + * @data: Supported data role of the port * @revision: USB Type-C Specification release. Binary coded decimal * @pd_revision: USB Power Delivery Specification revision if supported - * @prefer_role: Initial role preference + * @prefer_role: Initial role preference (DRP ports). * @accessory: Supported Accessory Modes * @sw: Cable plug orientation switch * @mux: Multiplexer switch for Alternate/Accessory Modes @@ -205,6 +212,7 @@ struct typec_partner_desc { */ struct typec_capability { enum typec_port_type type; + enum typec_port_data data; u16 revision; /* 0120H = "1.2" */ u16 pd_revision; /* 0300H = "3.0" */ int prefer_role; -- cgit v1.2.3-71-gd317 From c6962c29729cc64177f56a466766daa7de9f87ac Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 20 Mar 2018 15:57:06 +0300 Subject: usb: typec: tcpm: Set USB role switch to device mode when configured as such Setting the mux to MUX_NONE and the switch to USB_SWITCH_DISCONNECT when the data-role is device is not correct. Plenty of devices support operating as USB device through a (separate) USB device controller. We really need 2 different versions of USB_SWITCH_CONNECT, USB_SWITCH_CONNECT_HOST and USB_SWITCH_DEVICE. Rather then modifying the tcpc_usb_switch enum for this, simply remove it and switch to the usb_role enum which provides exactly this, this will save use needing to convert betweent the 2 enums when calling an usb-role-switch driver later. Besides switching to the usb_role type, this commit also actually sets the mux to TYPEC_MUX_USB and the switch to USB_ROLE_DEVICE instead of setting both to none when the data-role is device. This commit also makes tcpm_reset_port() call tcpm_mux_set(port, TYPEC_MUX_NONE, USB_ROLE_NONE) so that the mux and switch do _not_ stay in their last mode after a detach. Signed-off-by: Hans de Goede Reviewed-by: Guenter Roeck Reviewed-by: Andy Shevchenko Signed-off-by: Heikki Krogerus Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm.c | 22 +++++++++++----------- include/linux/usb/tcpm.h | 8 ++------ 2 files changed, 13 insertions(+), 17 deletions(-) (limited to 'include/linux') diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c index 62e710bb6367..2519b0d17f1f 100644 --- a/drivers/usb/typec/tcpm.c +++ b/drivers/usb/typec/tcpm.c @@ -604,15 +604,15 @@ void tcpm_pd_transmit_complete(struct tcpm_port *port, EXPORT_SYMBOL_GPL(tcpm_pd_transmit_complete); static int tcpm_mux_set(struct tcpm_port *port, enum tcpc_mux_mode mode, - enum tcpc_usb_switch config) + enum usb_role usb_role) { int ret = 0; - tcpm_log(port, "Requesting mux mode %d, config %d, polarity %d", - mode, config, port->polarity); + tcpm_log(port, "Requesting mux mode %d, usb-role %d, polarity %d", + mode, usb_role, port->polarity); if (port->tcpc->mux) - ret = port->tcpc->mux->set(port->tcpc->mux, mode, config, + ret = port->tcpc->mux->set(port->tcpc->mux, mode, usb_role, port->polarity); return ret; @@ -728,14 +728,15 @@ static int tcpm_set_attached_state(struct tcpm_port *port, bool attached) static int tcpm_set_roles(struct tcpm_port *port, bool attached, enum typec_role role, enum typec_data_role data) { + enum usb_role usb_role; int ret; if (data == TYPEC_HOST) - ret = tcpm_mux_set(port, TYPEC_MUX_USB, - TCPC_USB_SWITCH_CONNECT); + usb_role = USB_ROLE_HOST; else - ret = tcpm_mux_set(port, TYPEC_MUX_NONE, - TCPC_USB_SWITCH_DISCONNECT); + usb_role = USB_ROLE_DEVICE; + + ret = tcpm_mux_set(port, TYPEC_MUX_USB, usb_role); if (ret < 0) return ret; @@ -2028,7 +2029,7 @@ out_disable_vconn: out_disable_pd: port->tcpc->set_pd_rx(port->tcpc, false); out_disable_mux: - tcpm_mux_set(port, TYPEC_MUX_NONE, TCPC_USB_SWITCH_DISCONNECT); + tcpm_mux_set(port, TYPEC_MUX_NONE, USB_ROLE_NONE); return ret; } @@ -2072,6 +2073,7 @@ static void tcpm_reset_port(struct tcpm_port *port) tcpm_init_vconn(port); tcpm_set_current_limit(port, 0, 0); tcpm_set_polarity(port, TYPEC_POLARITY_CC1); + tcpm_mux_set(port, TYPEC_MUX_NONE, USB_ROLE_NONE); tcpm_set_attached_state(port, false); port->try_src_count = 0; port->try_snk_count = 0; @@ -2122,8 +2124,6 @@ static int tcpm_snk_attach(struct tcpm_port *port) static void tcpm_snk_detach(struct tcpm_port *port) { tcpm_detach(port); - - /* XXX: (Dis)connect SuperSpeed mux? */ } static int tcpm_acc_attach(struct tcpm_port *port) diff --git a/include/linux/usb/tcpm.h b/include/linux/usb/tcpm.h index 5a5e1d8c5b65..3e8bdaa5085a 100644 --- a/include/linux/usb/tcpm.h +++ b/include/linux/usb/tcpm.h @@ -16,6 +16,7 @@ #define __LINUX_USB_TCPM_H #include +#include #include #include "pd.h" @@ -98,11 +99,6 @@ struct tcpc_config { const struct typec_altmode_desc *alt_modes; }; -enum tcpc_usb_switch { - TCPC_USB_SWITCH_CONNECT, - TCPC_USB_SWITCH_DISCONNECT, -}; - /* Mux state attributes */ #define TCPC_MUX_USB_ENABLED BIT(0) /* USB enabled */ #define TCPC_MUX_DP_ENABLED BIT(1) /* DP enabled */ @@ -119,7 +115,7 @@ enum tcpc_mux_mode { struct tcpc_mux_dev { int (*set)(struct tcpc_mux_dev *dev, enum tcpc_mux_mode mux_mode, - enum tcpc_usb_switch usb_config, + enum usb_role usb_role, enum typec_cc_polarity polarity); bool dfp_only; void *priv_data; -- cgit v1.2.3-71-gd317 From 2000016c94b4f724cb5851486b9f9a94e8da32fc Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 20 Mar 2018 15:57:07 +0300 Subject: usb: typec: tcpm: Use new Type-C switch/mux and usb-role-switch functions Remove the unused (not implemented anywhere) tcpc_mux_dev abstraction and replace it with calling the new typec_set_orientation, usb_role_switch_set and typec_set_mode functions. Signed-off-by: Hans de Goede Reviewed-by: Guenter Roeck Reviewed-by: Andy Shevchenko Signed-off-by: Heikki Krogerus Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/Kconfig | 1 + drivers/usb/typec/fusb302/fusb302.c | 1 - drivers/usb/typec/tcpm.c | 46 ++++++++++++++++++++++++++++--------- include/linux/usb/tcpm.h | 10 -------- 4 files changed, 36 insertions(+), 22 deletions(-) (limited to 'include/linux') diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig index bcb2744c5977..a2a0684e7c82 100644 --- a/drivers/usb/typec/Kconfig +++ b/drivers/usb/typec/Kconfig @@ -48,6 +48,7 @@ if TYPEC config TYPEC_TCPM tristate "USB Type-C Port Controller Manager" depends on USB + select USB_ROLE_SWITCH help The Type-C Port Controller Manager provides a USB PD and USB Type-C state machine for use with Type-C Port Controllers. diff --git a/drivers/usb/typec/fusb302/fusb302.c b/drivers/usb/typec/fusb302/fusb302.c index 82bf7c0ed53c..703617129067 100644 --- a/drivers/usb/typec/fusb302/fusb302.c +++ b/drivers/usb/typec/fusb302/fusb302.c @@ -1239,7 +1239,6 @@ static void init_tcpc_dev(struct tcpc_dev *fusb302_tcpc_dev) fusb302_tcpc_dev->set_roles = tcpm_set_roles; fusb302_tcpc_dev->start_drp_toggling = tcpm_start_drp_toggling; fusb302_tcpc_dev->pd_transmit = tcpm_pd_transmit; - fusb302_tcpc_dev->mux = NULL; } static const char * const cc_polarity_name[] = { diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c index 2519b0d17f1f..677d12138dbd 100644 --- a/drivers/usb/typec/tcpm.c +++ b/drivers/usb/typec/tcpm.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -176,6 +177,7 @@ struct tcpm_port { struct typec_port *typec_port; struct tcpc_dev *tcpc; + struct usb_role_switch *role_sw; enum typec_role vconn_role; enum typec_role pwr_role; @@ -604,18 +606,25 @@ void tcpm_pd_transmit_complete(struct tcpm_port *port, EXPORT_SYMBOL_GPL(tcpm_pd_transmit_complete); static int tcpm_mux_set(struct tcpm_port *port, enum tcpc_mux_mode mode, - enum usb_role usb_role) + enum usb_role usb_role, + enum typec_orientation orientation) { - int ret = 0; + int ret; - tcpm_log(port, "Requesting mux mode %d, usb-role %d, polarity %d", - mode, usb_role, port->polarity); + tcpm_log(port, "Requesting mux mode %d, usb-role %d, orientation %d", + mode, usb_role, orientation); - if (port->tcpc->mux) - ret = port->tcpc->mux->set(port->tcpc->mux, mode, usb_role, - port->polarity); + ret = typec_set_orientation(port->typec_port, orientation); + if (ret) + return ret; - return ret; + if (port->role_sw) { + ret = usb_role_switch_set_role(port->role_sw, usb_role); + if (ret) + return ret; + } + + return typec_set_mode(port->typec_port, mode); } static int tcpm_set_polarity(struct tcpm_port *port, @@ -728,15 +737,21 @@ static int tcpm_set_attached_state(struct tcpm_port *port, bool attached) static int tcpm_set_roles(struct tcpm_port *port, bool attached, enum typec_role role, enum typec_data_role data) { + enum typec_orientation orientation; enum usb_role usb_role; int ret; + if (port->polarity == TYPEC_POLARITY_CC1) + orientation = TYPEC_ORIENTATION_NORMAL; + else + orientation = TYPEC_ORIENTATION_REVERSE; + if (data == TYPEC_HOST) usb_role = USB_ROLE_HOST; else usb_role = USB_ROLE_DEVICE; - ret = tcpm_mux_set(port, TYPEC_MUX_USB, usb_role); + ret = tcpm_mux_set(port, TYPEC_MUX_USB, usb_role, orientation); if (ret < 0) return ret; @@ -2029,7 +2044,8 @@ out_disable_vconn: out_disable_pd: port->tcpc->set_pd_rx(port->tcpc, false); out_disable_mux: - tcpm_mux_set(port, TYPEC_MUX_NONE, USB_ROLE_NONE); + tcpm_mux_set(port, TYPEC_MUX_NONE, USB_ROLE_NONE, + TYPEC_ORIENTATION_NONE); return ret; } @@ -2073,7 +2089,8 @@ static void tcpm_reset_port(struct tcpm_port *port) tcpm_init_vconn(port); tcpm_set_current_limit(port, 0, 0); tcpm_set_polarity(port, TYPEC_POLARITY_CC1); - tcpm_mux_set(port, TYPEC_MUX_NONE, USB_ROLE_NONE); + tcpm_mux_set(port, TYPEC_MUX_NONE, USB_ROLE_NONE, + TYPEC_ORIENTATION_NONE); tcpm_set_attached_state(port, false); port->try_src_count = 0; port->try_snk_count = 0; @@ -3653,6 +3670,12 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) port->partner_desc.identity = &port->partner_ident; port->port_type = tcpc->config->type; + port->role_sw = usb_role_switch_get(port->dev); + if (IS_ERR(port->role_sw)) { + err = PTR_ERR(port->role_sw); + goto out_destroy_wq; + } + port->typec_port = typec_register_port(port->dev, &port->typec_caps); if (IS_ERR(port->typec_port)) { err = PTR_ERR(port->typec_port); @@ -3688,6 +3711,7 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) return port; out_destroy_wq: + usb_role_switch_put(port->role_sw); destroy_workqueue(port->wq); return ERR_PTR(err); } diff --git a/include/linux/usb/tcpm.h b/include/linux/usb/tcpm.h index 3e8bdaa5085a..f0d839daeaea 100644 --- a/include/linux/usb/tcpm.h +++ b/include/linux/usb/tcpm.h @@ -16,7 +16,6 @@ #define __LINUX_USB_TCPM_H #include -#include #include #include "pd.h" @@ -113,14 +112,6 @@ enum tcpc_mux_mode { TCPC_MUX_DP_ENABLED, }; -struct tcpc_mux_dev { - int (*set)(struct tcpc_mux_dev *dev, enum tcpc_mux_mode mux_mode, - enum usb_role usb_role, - enum typec_cc_polarity polarity); - bool dfp_only; - void *priv_data; -}; - /** * struct tcpc_dev - Port configuration and callback functions * @config: Pointer to port configuration @@ -172,7 +163,6 @@ struct tcpc_dev { int (*try_role)(struct tcpc_dev *dev, int role); int (*pd_transmit)(struct tcpc_dev *dev, enum tcpm_transmit_type type, const struct pd_message *msg); - struct tcpc_mux_dev *mux; }; struct tcpm_port; -- cgit v1.2.3-71-gd317 From aaeab02ddcc830e31c33cdb72a3c117b2d499ae2 Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Fri, 23 Mar 2018 13:44:06 +1100 Subject: usb/gadget: Add an EP dispose() callback for EP lifetime tracking Some UDC may want to allocate endpoints dynamically, either because the HW supports an arbitrary large number or because (like the Aspeed BMC SoCs), the pool of HW endpoints is shared between multiple gadgets. The allocation side can be done rather easily using the existing match_ep() UDC hook. However we have no good place to "free" them. This implements a "simple" variant of this, which calls an EP dispose callback on all EPs associated with a gadget when the composite device gets unbound. This is required by my upcoming Aspeed vHub driver. Signed-off-by: Benjamin Herrenschmidt Signed-off-by: Felipe Balbi --- drivers/usb/gadget/composite.c | 16 ++++++++++++++++ include/linux/usb/gadget.h | 1 + 2 files changed, 17 insertions(+) (limited to 'include/linux') diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c index 1924e20f6e8c..63a7cb87514a 100644 --- a/drivers/usb/gadget/composite.c +++ b/drivers/usb/gadget/composite.c @@ -2142,6 +2142,7 @@ end: void composite_dev_cleanup(struct usb_composite_dev *cdev) { struct usb_gadget_string_container *uc, *tmp; + struct usb_ep *ep, *tmp_ep; list_for_each_entry_safe(uc, tmp, &cdev->gstrings, list) { list_del(&uc->list); @@ -2163,6 +2164,21 @@ void composite_dev_cleanup(struct usb_composite_dev *cdev) } cdev->next_string_id = 0; device_remove_file(&cdev->gadget->dev, &dev_attr_suspended); + + /* + * Some UDC backends have a dynamic EP allocation scheme. + * + * In that case, the dispose() callback is used to notify the + * backend that the EPs are no longer in use. + * + * Note: The UDC backend can remove the EP from the ep_list as + * a result, so we need to use the _safe list iterator. + */ + list_for_each_entry_safe(ep, tmp_ep, + &cdev->gadget->ep_list, ep_list) { + if (ep->ops->dispose) + ep->ops->dispose(ep); + } } static int composite_bind(struct usb_gadget *gadget, diff --git a/include/linux/usb/gadget.h b/include/linux/usb/gadget.h index 66a5cff7ee14..e3424234b23a 100644 --- a/include/linux/usb/gadget.h +++ b/include/linux/usb/gadget.h @@ -129,6 +129,7 @@ struct usb_ep_ops { int (*enable) (struct usb_ep *ep, const struct usb_endpoint_descriptor *desc); int (*disable) (struct usb_ep *ep); + void (*dispose) (struct usb_ep *ep); struct usb_request *(*alloc_request) (struct usb_ep *ep, gfp_t gfp_flags); -- cgit v1.2.3-71-gd317